@@ -192,8 +192,7 @@ func (a *AuxSweeper) Stop() error {
192192// set of asset inputs into the backing wallet.
193193func (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.
813821func 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