Skip to content

Commit

Permalink
tapchannel: apply tweaks when sweeping HTLCs
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Nov 25, 2024
1 parent 0ce755c commit fda054d
Showing 1 changed file with 82 additions and 40 deletions.
122 changes: 82 additions & 40 deletions tapchannel/aux_sweeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,7 @@ func (a *AuxSweeper) Stop() error {
// set of asset inputs into the backing wallet.
func (a *AuxSweeper) createSweepVpackets(sweepInputs []*cmsg.AssetOutput,
tapscriptDesc lfn.Result[tapscriptSweepDesc],
resReq lnwallet.ResolutionReq,
) lfn.Result[[]*tappsbt.VPacket] {
resReq lnwallet.ResolutionReq) lfn.Result[[]*tappsbt.VPacket] {

type returnType = []*tappsbt.VPacket

Expand Down Expand Up @@ -727,23 +726,26 @@ func commitRevokeSweepDesc(keyRing *lnwallet.CommitmentKeyRing,

// remoteHtlcTimeoutSweepDesc creates a sweep desc for an HTLC output that is
// close to timing out on the remote party's commitment transaction.
func remoteHtlcTimeoutSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
func remoteHtlcTimeoutSweepDesc(originalKeyRing *lnwallet.CommitmentKeyRing,
payHash []byte, csvDelay uint32, htlcExpiry uint32,
) lfn.Result[tapscriptSweepDescs] {
index input.HtlcIndex) lfn.Result[tapscriptSweepDescs] {

// We're sweeping an HTLC output, which has a tweaked script key. To be
// able to create the correct control block, we need to tweak the key
// ring with the index of the HTLC.
tweakedKeyRing := TweakedRevocationKeyRing(originalKeyRing, index)

// We're sweeping a timed out HTLC, which means that we'll need to
// create the receiver's HTLC script tree (from the remote party's PoV).
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
htlcExpiry, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
keyRing.RevocationKey, payHash, lntypes.Remote,
input.NoneTapLeaf(),
htlcExpiry, tweakedKeyRing.LocalHtlcKey,
tweakedKeyRing.RemoteHtlcKey, tweakedKeyRing.RevocationKey,
payHash, lntypes.Remote, input.NoneTapLeaf(),
)
if err != nil {
return lfn.Err[tapscriptSweepDescs](err)
}

// TODO(roasbeef): use GenTaprootHtlcScript instead?

// Now that we have the script tree, we'll make the control block needed
// to spend it, but taking the revoked path.
ctrlBlock, err := htlcScriptTree.CtrlBlockForPath(
Expand All @@ -770,15 +772,21 @@ func remoteHtlcTimeoutSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
// remoteHtlcSuccessSweepDesc creates a sweep desc for an HTLC output present on
// the remote party's commitment transaction that we can sweep with the
// preimage.
func remoteHtlcSuccessSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
payHash []byte, csvDelay uint32) lfn.Result[tapscriptSweepDescs] {
func remoteHtlcSuccessSweepDesc(originalKeyRing *lnwallet.CommitmentKeyRing,
payHash []byte, csvDelay uint32,
index input.HtlcIndex) lfn.Result[tapscriptSweepDescs] {

// We're sweeping an HTLC output, which has a tweaked script key. To be
// able to create the correct control block, we need to tweak the key
// ring with the index of the HTLC.
tweakedKeyRing := TweakedRevocationKeyRing(originalKeyRing, index)

// We're planning on sweeping an HTLC that we know the preimage to,
// which the remote party sent, so we'll construct the sender version of
// the HTLC script tree (from their PoV, they're the sender).
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
keyRing.RevocationKey, payHash, lntypes.Remote,
tweakedKeyRing.RemoteHtlcKey, tweakedKeyRing.LocalHtlcKey,
tweakedKeyRing.RevocationKey, payHash, lntypes.Remote,
input.NoneTapLeaf(),
)
if err != nil {
Expand Down Expand Up @@ -811,9 +819,9 @@ func remoteHtlcSuccessSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
// present on our local commitment transaction. These are second level HTLCs, so
// we'll need to perform two stages of sweeps.
func localHtlcTimeoutSweepDesc(req lnwallet.ResolutionReq,
) lfn.Result[tapscriptSweepDescs] {
index input.HtlcIndex) lfn.Result[tapscriptSweepDescs] {

isIncoming := false
const isIncoming = false

payHash, err := req.PayHash.UnwrapOrErr(
fmt.Errorf("no pay hash"),
Expand All @@ -828,11 +836,16 @@ func localHtlcTimeoutSweepDesc(req lnwallet.ResolutionReq,
return lfn.Err[tapscriptSweepDescs](err)
}

// We're sweeping an HTLC output, which has a tweaked script key. To be
// able to create the correct control block, we need to tweak the key
// ring with the index of the HTLC.
tweakedKeyRing := TweakedRevocationKeyRing(req.KeyRing, index)

// We'll need to complete the control block to spend the second-level
// HTLC, so first we'll make the script tree for the HTLC.
htlcScriptTree, err := lnwallet.GenTaprootHtlcScript(
isIncoming, lntypes.Local, htlcExpiry,
payHash, req.KeyRing, lfn.None[txscript.TapLeaf](),
isIncoming, lntypes.Local, htlcExpiry, payHash, tweakedKeyRing,
lfn.None[txscript.TapLeaf](),
)
if err != nil {
return lfn.Errf[tapscriptSweepDescs]("error creating "+
Expand Down Expand Up @@ -900,13 +913,13 @@ func localHtlcTimeoutSweepDesc(req lnwallet.ResolutionReq,
})
}

// localHtlcSucessSweepDesc creates a sweep desc for an HTLC output that is
// localHtlcSuccessSweepDesc creates a sweep desc for an HTLC output that is
// present on our local commitment transaction that we can sweep with a
// preimage. These sweeps take two stages, so we'll add that extra information.
func localHtlcSucessSweepDesc(req lnwallet.ResolutionReq,
) lfn.Result[tapscriptSweepDescs] {
func localHtlcSuccessSweepDesc(req lnwallet.ResolutionReq,
index input.HtlcIndex) lfn.Result[tapscriptSweepDescs] {

isIncoming := true
const isIncoming = true

payHash, err := req.PayHash.UnwrapOrErr(
fmt.Errorf("no pay hash"),
Expand All @@ -921,11 +934,16 @@ func localHtlcSucessSweepDesc(req lnwallet.ResolutionReq,
return lfn.Err[tapscriptSweepDescs](err)
}

// We're sweeping an HTLC output, which has a tweaked script key. To be
// able to create the correct control block, we need to tweak the key
// ring with the index of the HTLC.
tweakedKeyRing := TweakedRevocationKeyRing(req.KeyRing, index)

// We'll need to complete the control block to spend the second-level
// HTLC, so first we'll make the script tree for the HTLC.
htlcScriptTree, err := lnwallet.GenTaprootHtlcScript(
isIncoming, lntypes.Local, htlcExpiry,
payHash, req.KeyRing, lfn.None[txscript.TapLeaf](),
isIncoming, lntypes.Local, htlcExpiry, payHash, tweakedKeyRing,
lfn.None[txscript.TapLeaf](),
)
if err != nil {
return lfn.Errf[tapscriptSweepDescs]("error creating "+
Expand Down Expand Up @@ -1707,10 +1725,9 @@ func (a *AuxSweeper) resolveContract(
// assets for the remote party, which are actually the HTLCs we
// sent outgoing. We only care about this particular HTLC, so
// we'll filter out the rest.
htlcID := req.HtlcID.UnwrapOr(math.MaxUint64)
htlcOutputs := commitState.OutgoingHtlcAssets.Val
assetOutputs = htlcOutputs.FilterByHtlcIndex(
req.HtlcID.UnwrapOr(math.MaxUint64),
)
assetOutputs = htlcOutputs.FilterByHtlcIndex(htlcID)

payHash, err := req.PayHash.UnwrapOrErr(errNoPayHash)
if err != nil {
Expand All @@ -1721,7 +1738,7 @@ func (a *AuxSweeper) resolveContract(
// sweep desc for the timeout txn.
sweepDesc = remoteHtlcTimeoutSweepDesc(
req.KeyRing, payHash[:], req.CsvDelay,
req.CltvDelay.UnwrapOr(0),
req.CltvDelay.UnwrapOr(0), htlcID,
)

// The remote party broadcasted a commitment transaction which held an
Expand All @@ -1730,10 +1747,9 @@ func (a *AuxSweeper) resolveContract(
// In this case, it's an outgoing HTLC from the PoV of the
// remote party, which is incoming for us. We'll only sweep this
// HTLC, so we'll filter out the rest.
htlcID := req.HtlcID.UnwrapOr(math.MaxUint64)
htlcOutputs := commitState.IncomingHtlcAssets.Val
assetOutputs = htlcOutputs.FilterByHtlcIndex(
req.HtlcID.UnwrapOr(math.MaxUint64),
)
assetOutputs = htlcOutputs.FilterByHtlcIndex(htlcID)

payHash, err := req.PayHash.UnwrapOrErr(errNoPayHash)
if err != nil {
Expand All @@ -1743,7 +1759,7 @@ func (a *AuxSweeper) resolveContract(
// Now that we know which output we'll be sweeping, we'll make a
// sweep desc for the timeout txn.
sweepDesc = remoteHtlcSuccessSweepDesc(
req.KeyRing, payHash[:], req.CsvDelay,
req.KeyRing, payHash[:], req.CsvDelay, htlcID,
)

// In this case, we broadcast a commitment transaction which held an
Expand All @@ -1753,14 +1769,13 @@ func (a *AuxSweeper) resolveContract(
case input.TaprootHtlcLocalOfferedTimeout:
// Like the other HTLC cases, there's only a single output we
// care about here.
htlcID := req.HtlcID.UnwrapOr(math.MaxUint64)
htlcOutputs := commitState.OutgoingHtlcAssets.Val
assetOutputs = htlcOutputs.FilterByHtlcIndex(
req.HtlcID.UnwrapOr(math.MaxUint64),
)
assetOutputs = htlcOutputs.FilterByHtlcIndex(htlcID)

// With the output and pay desc located, we'll now create the
// sweep desc.
sweepDesc = localHtlcTimeoutSweepDesc(req)
sweepDesc = localHtlcTimeoutSweepDesc(req, htlcID)

needsSecondLevel = true

Expand All @@ -1769,21 +1784,26 @@ func (a *AuxSweeper) resolveContract(
// needed to sweep both this output, as well as the second level
// output it creates.
case input.TaprootHtlcAcceptedLocalSuccess:
htlcID := req.HtlcID.UnwrapOr(math.MaxUint64)
htlcOutputs := commitState.IncomingHtlcAssets.Val
assetOutputs = htlcOutputs.FilterByHtlcIndex(
req.HtlcID.UnwrapOr(math.MaxUint64),
)
assetOutputs = htlcOutputs.FilterByHtlcIndex(htlcID)

// With the output and pay desc located, we'll now create the
// sweep desc.
sweepDesc = localHtlcSucessSweepDesc(req)
sweepDesc = localHtlcSuccessSweepDesc(req, htlcID)

needsSecondLevel = true

default:
// TODO(guggero): Need to do HTLC revocation cases here.
// IMPORTANT: Remember that we applied the HTLC index as a tweak
// to the revocation key on the asset level! That means the
// tweak to the first-level HTLC script key's internal key
// (which is the revocation key) MUST be applied when creating
// a breach sweep transaction!

return lfn.Errf[returnType]("unknown resolution type: %v",
req.Type)
// TODO(roasbeef): need to do HTLC revocation casesj:w
}

tapSweepDesc, err := sweepDesc.Unpack()
Expand Down Expand Up @@ -2574,3 +2594,25 @@ func (a *AuxSweeper) NotifyBroadcast(req *sweep.BumpRequest,

return resp
}

// TweakedRevocationKeyRing returns a new commitment key ring with the
// revocation key tweaked by the given HTLC index. The revocation key is tweaked
// in order to achieve uniqueness for each HTLC output on the asset level. This
// same tweak will need to be applied to the revocation private key in case of
// a breach.
func TweakedRevocationKeyRing(keyRing *lnwallet.CommitmentKeyRing,
index input.HtlcIndex) *lnwallet.CommitmentKeyRing {

return &lnwallet.CommitmentKeyRing{
CommitPoint: keyRing.CommitPoint,
LocalCommitKeyTweak: keyRing.LocalCommitKeyTweak,
LocalHtlcKeyTweak: keyRing.LocalHtlcKeyTweak,
LocalHtlcKey: keyRing.LocalHtlcKey,
RemoteHtlcKey: keyRing.RemoteHtlcKey,
ToLocalKey: keyRing.ToLocalKey,
ToRemoteKey: keyRing.ToRemoteKey,
RevocationKey: TweakPubKeyWithIndex(
keyRing.RevocationKey, index,
),
}
}

0 comments on commit fda054d

Please sign in to comment.