Skip to content

Commit fda054d

Browse files
committed
tapchannel: apply tweaks when sweeping HTLCs
1 parent 0ce755c commit fda054d

File tree

1 file changed

+82
-40
lines changed

1 file changed

+82
-40
lines changed

tapchannel/aux_sweeper.go

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,7 @@ func (a *AuxSweeper) Stop() error {
192192
// set of asset inputs into the backing wallet.
193193
func (a *AuxSweeper) createSweepVpackets(sweepInputs []*cmsg.AssetOutput,
194194
tapscriptDesc lfn.Result[tapscriptSweepDesc],
195-
resReq lnwallet.ResolutionReq,
196-
) lfn.Result[[]*tappsbt.VPacket] {
195+
resReq lnwallet.ResolutionReq) lfn.Result[[]*tappsbt.VPacket] {
197196

198197
type returnType = []*tappsbt.VPacket
199198

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

728727
// remoteHtlcTimeoutSweepDesc creates a sweep desc for an HTLC output that is
729728
// close to timing out on the remote party's commitment transaction.
730-
func remoteHtlcTimeoutSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
729+
func remoteHtlcTimeoutSweepDesc(originalKeyRing *lnwallet.CommitmentKeyRing,
731730
payHash []byte, csvDelay uint32, htlcExpiry uint32,
732-
) lfn.Result[tapscriptSweepDescs] {
731+
index input.HtlcIndex) lfn.Result[tapscriptSweepDescs] {
732+
733+
// We're sweeping an HTLC output, which has a tweaked script key. To be
734+
// able to create the correct control block, we need to tweak the key
735+
// ring with the index of the HTLC.
736+
tweakedKeyRing := TweakedRevocationKeyRing(originalKeyRing, index)
733737

734738
// We're sweeping a timed out HTLC, which means that we'll need to
735739
// create the receiver's HTLC script tree (from the remote party's PoV).
736740
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
737-
htlcExpiry, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
738-
keyRing.RevocationKey, payHash, lntypes.Remote,
739-
input.NoneTapLeaf(),
741+
htlcExpiry, tweakedKeyRing.LocalHtlcKey,
742+
tweakedKeyRing.RemoteHtlcKey, tweakedKeyRing.RevocationKey,
743+
payHash, lntypes.Remote, input.NoneTapLeaf(),
740744
)
741745
if err != nil {
742746
return lfn.Err[tapscriptSweepDescs](err)
743747
}
744748

745-
// TODO(roasbeef): use GenTaprootHtlcScript instead?
746-
747749
// Now that we have the script tree, we'll make the control block needed
748750
// to spend it, but taking the revoked path.
749751
ctrlBlock, err := htlcScriptTree.CtrlBlockForPath(
@@ -770,15 +772,21 @@ func remoteHtlcTimeoutSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
770772
// remoteHtlcSuccessSweepDesc creates a sweep desc for an HTLC output present on
771773
// the remote party's commitment transaction that we can sweep with the
772774
// preimage.
773-
func remoteHtlcSuccessSweepDesc(keyRing *lnwallet.CommitmentKeyRing,
774-
payHash []byte, csvDelay uint32) lfn.Result[tapscriptSweepDescs] {
775+
func remoteHtlcSuccessSweepDesc(originalKeyRing *lnwallet.CommitmentKeyRing,
776+
payHash []byte, csvDelay uint32,
777+
index input.HtlcIndex) lfn.Result[tapscriptSweepDescs] {
778+
779+
// We're sweeping an HTLC output, which has a tweaked script key. To be
780+
// able to create the correct control block, we need to tweak the key
781+
// ring with the index of the HTLC.
782+
tweakedKeyRing := TweakedRevocationKeyRing(originalKeyRing, index)
775783

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

816-
isIncoming := false
824+
const isIncoming = false
817825

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

839+
// We're sweeping an HTLC output, which has a tweaked script key. To be
840+
// able to create the correct control block, we need to tweak the key
841+
// ring with the index of the HTLC.
842+
tweakedKeyRing := TweakedRevocationKeyRing(req.KeyRing, index)
843+
831844
// We'll need to complete the control block to spend the second-level
832845
// HTLC, so first we'll make the script tree for the HTLC.
833846
htlcScriptTree, err := lnwallet.GenTaprootHtlcScript(
834-
isIncoming, lntypes.Local, htlcExpiry,
835-
payHash, req.KeyRing, lfn.None[txscript.TapLeaf](),
847+
isIncoming, lntypes.Local, htlcExpiry, payHash, tweakedKeyRing,
848+
lfn.None[txscript.TapLeaf](),
836849
)
837850
if err != nil {
838851
return lfn.Errf[tapscriptSweepDescs]("error creating "+
@@ -900,13 +913,13 @@ func localHtlcTimeoutSweepDesc(req lnwallet.ResolutionReq,
900913
})
901914
}
902915

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

909-
isIncoming := true
922+
const isIncoming = true
910923

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

937+
// We're sweeping an HTLC output, which has a tweaked script key. To be
938+
// able to create the correct control block, we need to tweak the key
939+
// ring with the index of the HTLC.
940+
tweakedKeyRing := TweakedRevocationKeyRing(req.KeyRing, index)
941+
924942
// We'll need to complete the control block to spend the second-level
925943
// HTLC, so first we'll make the script tree for the HTLC.
926944
htlcScriptTree, err := lnwallet.GenTaprootHtlcScript(
927-
isIncoming, lntypes.Local, htlcExpiry,
928-
payHash, req.KeyRing, lfn.None[txscript.TapLeaf](),
945+
isIncoming, lntypes.Local, htlcExpiry, payHash, tweakedKeyRing,
946+
lfn.None[txscript.TapLeaf](),
929947
)
930948
if err != nil {
931949
return lfn.Errf[tapscriptSweepDescs]("error creating "+
@@ -1707,10 +1725,9 @@ func (a *AuxSweeper) resolveContract(
17071725
// assets for the remote party, which are actually the HTLCs we
17081726
// sent outgoing. We only care about this particular HTLC, so
17091727
// we'll filter out the rest.
1728+
htlcID := req.HtlcID.UnwrapOr(math.MaxUint64)
17101729
htlcOutputs := commitState.OutgoingHtlcAssets.Val
1711-
assetOutputs = htlcOutputs.FilterByHtlcIndex(
1712-
req.HtlcID.UnwrapOr(math.MaxUint64),
1713-
)
1730+
assetOutputs = htlcOutputs.FilterByHtlcIndex(htlcID)
17141731

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

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

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

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

17611776
// With the output and pay desc located, we'll now create the
17621777
// sweep desc.
1763-
sweepDesc = localHtlcTimeoutSweepDesc(req)
1778+
sweepDesc = localHtlcTimeoutSweepDesc(req, htlcID)
17641779

17651780
needsSecondLevel = true
17661781

@@ -1769,21 +1784,26 @@ func (a *AuxSweeper) resolveContract(
17691784
// needed to sweep both this output, as well as the second level
17701785
// output it creates.
17711786
case input.TaprootHtlcAcceptedLocalSuccess:
1787+
htlcID := req.HtlcID.UnwrapOr(math.MaxUint64)
17721788
htlcOutputs := commitState.IncomingHtlcAssets.Val
1773-
assetOutputs = htlcOutputs.FilterByHtlcIndex(
1774-
req.HtlcID.UnwrapOr(math.MaxUint64),
1775-
)
1789+
assetOutputs = htlcOutputs.FilterByHtlcIndex(htlcID)
17761790

17771791
// With the output and pay desc located, we'll now create the
17781792
// sweep desc.
1779-
sweepDesc = localHtlcSucessSweepDesc(req)
1793+
sweepDesc = localHtlcSuccessSweepDesc(req, htlcID)
17801794

17811795
needsSecondLevel = true
17821796

17831797
default:
1798+
// TODO(guggero): Need to do HTLC revocation cases here.
1799+
// IMPORTANT: Remember that we applied the HTLC index as a tweak
1800+
// to the revocation key on the asset level! That means the
1801+
// tweak to the first-level HTLC script key's internal key
1802+
// (which is the revocation key) MUST be applied when creating
1803+
// a breach sweep transaction!
1804+
17841805
return lfn.Errf[returnType]("unknown resolution type: %v",
17851806
req.Type)
1786-
// TODO(roasbeef): need to do HTLC revocation casesj:w
17871807
}
17881808

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

25752595
return resp
25762596
}
2597+
2598+
// TweakedRevocationKeyRing returns a new commitment key ring with the
2599+
// revocation key tweaked by the given HTLC index. The revocation key is tweaked
2600+
// in order to achieve uniqueness for each HTLC output on the asset level. This
2601+
// same tweak will need to be applied to the revocation private key in case of
2602+
// a breach.
2603+
func TweakedRevocationKeyRing(keyRing *lnwallet.CommitmentKeyRing,
2604+
index input.HtlcIndex) *lnwallet.CommitmentKeyRing {
2605+
2606+
return &lnwallet.CommitmentKeyRing{
2607+
CommitPoint: keyRing.CommitPoint,
2608+
LocalCommitKeyTweak: keyRing.LocalCommitKeyTweak,
2609+
LocalHtlcKeyTweak: keyRing.LocalHtlcKeyTweak,
2610+
LocalHtlcKey: keyRing.LocalHtlcKey,
2611+
RemoteHtlcKey: keyRing.RemoteHtlcKey,
2612+
ToLocalKey: keyRing.ToLocalKey,
2613+
ToRemoteKey: keyRing.ToRemoteKey,
2614+
RevocationKey: TweakPubKeyWithIndex(
2615+
keyRing.RevocationKey, index,
2616+
),
2617+
}
2618+
}

0 commit comments

Comments
 (0)