From 6a54be23ae47a4dc4a4a953e238a2e147b0bd9f8 Mon Sep 17 00:00:00 2001 From: Andras Banki-Horvath Date: Mon, 14 Jul 2025 23:18:46 +0200 Subject: [PATCH] assets: add no-csv option to the asset HTLC to support package relay This commit enables package relayed HTLCs by making the CSV check in the success path optional. --- assets/htlc/script.go | 30 ++++++++++++++++++++++++------ assets/htlc/swapkit.go | 15 +++++++++++---- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/assets/htlc/script.go b/assets/htlc/script.go index 842bbc66..80f6707b 100644 --- a/assets/htlc/script.go +++ b/assets/htlc/script.go @@ -11,9 +11,12 @@ import ( "github.com/lightningnetwork/lnd/lntypes" ) -// GenSuccessPathScript constructs an HtlcScript for the success payment path. +// GenSuccessPathScript constructs a script for the success path of the HTLC +// payment. Optionally includes a CHECKSEQUENCEVERIFY (CSV) of 1 if `csv` is +// true, to prevent potential pinning attacks when the HTLC is not part of a +// package relay. func GenSuccessPathScript(receiverHtlcKey *btcec.PublicKey, - swapHash lntypes.Hash) ([]byte, error) { + swapHash lntypes.Hash, csvOne bool) ([]byte, error) { builder := txscript.NewScriptBuilder() @@ -24,9 +27,22 @@ func GenSuccessPathScript(receiverHtlcKey *btcec.PublicKey, builder.AddOp(txscript.OP_EQUALVERIFY) builder.AddOp(txscript.OP_HASH160) builder.AddData(input.Ripemd160H(swapHash[:])) - builder.AddOp(txscript.OP_EQUALVERIFY) - builder.AddInt64(1) - builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + // OP_EQUAL will leave 0 or 1 on the stack depending on whether the hash + // matches. + // - If it matches and CSV is not used, the script will + // evaulate to true. + // - If it matches and CSV is used, we'll have 1 on the stack which is + // used to verify the CSV condition. + // - If it does not match, we'll have 0 on the stack which will cause + // the script to fail even if CSV is used. + builder.AddOp(txscript.OP_EQUAL) + + if csvOne { + // If csvOne is true, we add a CHECKSEQUENCEVERIFY to ensure + // that the HTLC can only be claimed after at least one + // confirmation. + builder.AddOp(txscript.OP_CHECKSEQUENCEVERIFY) + } return builder.Script() } @@ -61,7 +77,9 @@ func CreateOpTrueLeaf() (asset.ScriptKey, txscript.TapLeaf, tapLeaf := txscript.NewBaseTapLeaf(tapScript) tree := txscript.AssembleTaprootScriptTree(tapLeaf) rootHash := tree.RootNode.TapHash() - tapKey := txscript.ComputeTaprootOutputKey(asset.NUMSPubKey, rootHash[:]) + tapKey := txscript.ComputeTaprootOutputKey( + asset.NUMSPubKey, rootHash[:], + ) merkleRootHash := tree.RootNode.TapHash() diff --git a/assets/htlc/swapkit.go b/assets/htlc/swapkit.go index 335eb20c..7d86008d 100644 --- a/assets/htlc/swapkit.go +++ b/assets/htlc/swapkit.go @@ -47,11 +47,16 @@ type SwapKit struct { // AddressParams is the chain parameters of the chain the deposit is // being created on. AddressParams *address.ChainParams + + // CheckCSV indicates whether the success path script should include a + // CHECKSEQUENCEVERIFY check. This is used to prevent potential pinning + // attacks when the HTLC is not part of a package relay. + CheckCSV bool } // GetSuccessScript returns the success path script of the swap HTLC. func (s *SwapKit) GetSuccessScript() ([]byte, error) { - return GenSuccessPathScript(s.ReceiverPubKey, s.SwapHash) + return GenSuccessPathScript(s.ReceiverPubKey, s.SwapHash, s.CheckCSV) } // GetTimeoutScript returns the timeout path script of the swap HTLC. @@ -192,7 +197,7 @@ func (s *SwapKit) GenTimeoutBtcControlBlock(taprootAssetRoot []byte) ( InternalKey: internalKey, LeafVersion: txscript.BaseLeafVersion, InclusionProof: append( - successLeafHash[:], taprootAssetRoot[:]..., + successLeafHash[:], taprootAssetRoot..., ), } @@ -233,7 +238,7 @@ func (s *SwapKit) GenSuccessBtcControlBlock(taprootAssetRoot []byte) ( InternalKey: internalKey, LeafVersion: txscript.BaseLeafVersion, InclusionProof: append( - timeOutLeafHash[:], taprootAssetRoot[:]..., + timeOutLeafHash[:], taprootAssetRoot..., ), } @@ -322,7 +327,9 @@ func (s *SwapKit) CreatePreimageWitness(ctx context.Context, Value: sweepBtcPacket.Inputs[1].WitnessUtxo.Value, } - //sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = 1 + if s.CheckCSV { + sweepBtcPacket.UnsignedTx.TxIn[0].Sequence = 1 + } successScript, err := s.GetSuccessScript() if err != nil {