package input_test import ( "testing" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" "github.com/stretchr/testify/require" ) const ( testCSVDelay = (1 << 31) - 1 testCLTVExpiry = 500000000 // maxDERSignatureSize is the largest possible DER-encoded signature // without the trailing sighash flag. maxDERSignatureSize = 72 // maxSchnorrSignature is the largest possilbe schnorr sig w/o a non // default sighash. maxSchnorrSignatureSize = 64 testAmt = btcutil.MaxSatoshi ) var ( testPubkeyBytes = make([]byte, 33) testHash160 = make([]byte, 20) testPreimage = make([]byte, 32) // testPubkey is a pubkey used in script size calculation. testPubkey = &btcec.PublicKey{} testPrivkey, _ = btcec.PrivKeyFromBytes(make([]byte, 32)) testTx = wire.NewMsgTx(2) testOutPoint = wire.OutPoint{ Hash: chainhash.Hash{}, Index: 1, } ) // TestTxWeightEstimator tests that transaction weight estimates are calculated // correctly by comparing against an actual (though invalid) transaction // matching the template. func TestTxWeightEstimator(t *testing.T) { netParams := &chaincfg.MainNetParams p2pkhAddr, err := btcutil.NewAddressPubKeyHash( make([]byte, 20), netParams) require.NoError(t, err, "Failed to generate address") p2pkhScript, err := txscript.PayToAddrScript(p2pkhAddr) require.NoError(t, err, "Failed to generate scriptPubKey") p2wkhAddr, err := btcutil.NewAddressWitnessPubKeyHash( make([]byte, 20), netParams) require.NoError(t, err, "Failed to generate address") p2wkhScript, err := txscript.PayToAddrScript(p2wkhAddr) require.NoError(t, err, "Failed to generate scriptPubKey") p2wshAddr, err := btcutil.NewAddressWitnessScriptHash( make([]byte, 32), netParams) require.NoError(t, err, "Failed to generate address") p2wshScript, err := txscript.PayToAddrScript(p2wshAddr) require.NoError(t, err, "Failed to generate scriptPubKey") p2shAddr, err := btcutil.NewAddressScriptHash([]byte{0}, netParams) require.NoError(t, err, "Failed to generate address") p2shScript, err := txscript.PayToAddrScript(p2shAddr) require.NoError(t, err, "Failed to generate scriptPubKey") testCases := []struct { numP2PKHInputs int numP2WKHInputs int numP2WSHInputs int numNestedP2WKHInputs int numNestedP2WSHInputs int numP2PKHOutputs int numP2WKHOutputs int numP2WSHOutputs int numP2SHOutputs int }{ // Assert base txn size. {}, // Assert single input/output sizes. { numP2PKHInputs: 1, }, { numP2WKHInputs: 1, }, { numP2WSHInputs: 1, }, { numNestedP2WKHInputs: 1, }, { numNestedP2WSHInputs: 1, }, { numP2WKHOutputs: 1, }, { numP2PKHOutputs: 1, }, { numP2WSHOutputs: 1, }, { numP2SHOutputs: 1, }, // Assert each input/output increments input/output counts. { numP2PKHInputs: 253, }, { numP2WKHInputs: 253, }, { numP2WSHInputs: 253, }, { numNestedP2WKHInputs: 253, }, { numNestedP2WSHInputs: 253, }, { numP2WKHOutputs: 253, }, { numP2PKHOutputs: 253, }, { numP2WSHOutputs: 253, }, { numP2SHOutputs: 253, }, // Assert basic combinations of inputs and outputs. { numP2PKHInputs: 1, numP2PKHOutputs: 2, }, { numP2PKHInputs: 1, numP2WKHInputs: 1, numP2WKHOutputs: 1, numP2WSHOutputs: 1, }, { numP2WKHInputs: 1, numP2WKHOutputs: 1, numP2WSHOutputs: 1, }, { numP2WKHInputs: 2, numP2WKHOutputs: 1, numP2WSHOutputs: 1, }, { numP2WSHInputs: 1, numP2WKHOutputs: 1, }, { numP2PKHInputs: 1, numP2SHOutputs: 1, }, { numNestedP2WKHInputs: 1, numP2WKHOutputs: 1, }, { numNestedP2WSHInputs: 1, numP2WKHOutputs: 1, }, // Assert disparate input/output types increment total // input/output counts. { numP2PKHInputs: 50, numP2WKHInputs: 50, numP2WSHInputs: 51, numNestedP2WKHInputs: 51, numNestedP2WSHInputs: 51, numP2WKHOutputs: 1, }, { numP2WKHInputs: 1, numP2WKHOutputs: 63, numP2PKHOutputs: 63, numP2WSHOutputs: 63, numP2SHOutputs: 64, }, { numP2PKHInputs: 50, numP2WKHInputs: 50, numP2WSHInputs: 51, numNestedP2WKHInputs: 51, numNestedP2WSHInputs: 51, numP2WKHOutputs: 63, numP2PKHOutputs: 63, numP2WSHOutputs: 63, numP2SHOutputs: 64, }, } for i, test := range testCases { var weightEstimate input.TxWeightEstimator tx := wire.NewMsgTx(1) for j := 0; j < test.numP2PKHInputs; j++ { weightEstimate.AddP2PKHInput() signature := make([]byte, maxDERSignatureSize+1) compressedPubKey := make([]byte, 33) scriptSig, err := txscript.NewScriptBuilder().AddData(signature). AddData(compressedPubKey).Script() if err != nil { t.Fatalf("Failed to generate scriptSig: %v", err) } tx.AddTxIn(&wire.TxIn{SignatureScript: scriptSig}) } for j := 0; j < test.numP2WKHInputs; j++ { weightEstimate.AddP2WKHInput() signature := make([]byte, maxDERSignatureSize+1) compressedPubKey := make([]byte, 33) witness := wire.TxWitness{signature, compressedPubKey} tx.AddTxIn(&wire.TxIn{Witness: witness}) } for j := 0; j < test.numP2WSHInputs; j++ { weightEstimate.AddWitnessInput(42) witnessScript := make([]byte, 40) witness := wire.TxWitness{witnessScript} tx.AddTxIn(&wire.TxIn{Witness: witness}) } for j := 0; j < test.numNestedP2WKHInputs; j++ { weightEstimate.AddNestedP2WKHInput() signature := make([]byte, maxDERSignatureSize+1) compressedPubKey := make([]byte, 33) witness := wire.TxWitness{signature, compressedPubKey} scriptSig, err := txscript.NewScriptBuilder().AddData(p2wkhScript). Script() if err != nil { t.Fatalf("Failed to generate scriptSig: %v", err) } tx.AddTxIn(&wire.TxIn{SignatureScript: scriptSig, Witness: witness}) } for j := 0; j < test.numNestedP2WSHInputs; j++ { weightEstimate.AddNestedP2WSHInput(42) witnessScript := make([]byte, 40) witness := wire.TxWitness{witnessScript} scriptSig, err := txscript.NewScriptBuilder().AddData(p2wshScript). Script() if err != nil { t.Fatalf("Failed to generate scriptSig: %v", err) } tx.AddTxIn(&wire.TxIn{SignatureScript: scriptSig, Witness: witness}) } for j := 0; j < test.numP2PKHOutputs; j++ { weightEstimate.AddP2PKHOutput() tx.AddTxOut(&wire.TxOut{PkScript: p2pkhScript}) } for j := 0; j < test.numP2WKHOutputs; j++ { weightEstimate.AddP2WKHOutput() tx.AddTxOut(&wire.TxOut{PkScript: p2wkhScript}) } for j := 0; j < test.numP2WSHOutputs; j++ { weightEstimate.AddP2WSHOutput() tx.AddTxOut(&wire.TxOut{PkScript: p2wshScript}) } for j := 0; j < test.numP2SHOutputs; j++ { weightEstimate.AddP2SHOutput() tx.AddTxOut(&wire.TxOut{PkScript: p2shScript}) } expectedWeight := blockchain.GetTransactionWeight( btcutil.NewTx(tx), ) require.EqualValuesf(t, expectedWeight, weightEstimate.Weight(), "Case %d: Got wrong weight: expected %d, got %d", i, expectedWeight, weightEstimate.Weight()) } } type maxDERSignature struct{} func (s *maxDERSignature) Serialize() []byte { // Always return worst-case signature length, excluding the one byte // sighash flag. return make([]byte, maxDERSignatureSize) } func (s *maxDERSignature) Verify(_ []byte, _ *btcec.PublicKey) bool { return true } type maxSchnorrSignature struct{} func (s *maxSchnorrSignature) Serialize() []byte { // Always return worst-case signature length, including a non-default // sighash type. return make([]byte, maxSchnorrSignatureSize) } func (s *maxSchnorrSignature) Verify(_ []byte, _ *btcec.PublicKey) bool { return true } // dummySigner is a fake signer used for size (upper bound) calculations. type dummySigner struct { input.Signer } // SignOutputRaw generates a signature for the passed transaction according to // the data within the passed SignDescriptor. func (s *dummySigner) SignOutputRaw(tx *wire.MsgTx, signDesc *input.SignDescriptor) (input.Signature, error) { switch signDesc.SignMethod { case input.TaprootKeySpendBIP0086SignMethod: fallthrough case input.TaprootKeySpendSignMethod: fallthrough case input.TaprootScriptSpendSignMethod: return &maxSchnorrSignature{}, nil } return &maxDERSignature{}, nil } type witnessSizeTest struct { name string expSize int genWitness func(t *testing.T) wire.TxWitness } var witnessSizeTests = []witnessSizeTest{ { name: "funding", expSize: input.MultiSigWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witnessScript, _, err := input.GenFundingPkScript( testPubkeyBytes, testPubkeyBytes, 1, ) if err != nil { t.Fatal(err) } return input.SpendMultiSig( witnessScript, testPubkeyBytes, &maxDERSignature{}, testPubkeyBytes, &maxDERSignature{}, ) }, }, { name: "to local timeout", expSize: input.ToLocalTimeoutWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witnessScript, err := input.CommitScriptToSelf( testCSVDelay, testPubkey, testPubkey, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witnessScript, } witness, err := input.CommitSpendTimeout( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "to local revoke", expSize: input.ToLocalPenaltyWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witnessScript, err := input.CommitScriptToSelf( testCSVDelay, testPubkey, testPubkey, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witnessScript, } witness, err := input.CommitSpendRevoke( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "to remote confirmed", expSize: input.ToRemoteConfirmedWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.CommitScriptToRemoteConfirmed( testPubkey, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, } witness, err := input.CommitSpendToRemoteConfirmed( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "anchor", expSize: input.AnchorWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.CommitScriptAnchor( testPubkey, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, } witness, err := input.CommitSpendAnchor( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "anchor anyone", expSize: 43, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.CommitScriptAnchor( testPubkey, ) if err != nil { t.Fatal(err) } witness, _ := input.CommitSpendAnchorAnyone(witScript) return witness }, }, { name: "offered htlc revoke", expSize: input.OfferedHtlcPenaltyWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.SenderHTLCScript( testPubkey, testPubkey, testPubkey, testHash160, false, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, DoubleTweak: testPrivkey, } witness, err := input.SenderHtlcSpendRevoke( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "offered htlc revoke confirmed", expSize: input.OfferedHtlcPenaltyWitnessSizeConfirmed, genWitness: func(t *testing.T) wire.TxWitness { hash := make([]byte, 20) witScript, err := input.SenderHTLCScript( testPubkey, testPubkey, testPubkey, hash, true, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, DoubleTweak: testPrivkey, } witness, err := input.SenderHtlcSpendRevoke( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "offered htlc timeout", expSize: input.OfferedHtlcTimeoutWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.SenderHTLCScript( testPubkey, testPubkey, testPubkey, testHash160, false, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, } witness, err := input.SenderHtlcSpendTimeout( &maxDERSignature{}, txscript.SigHashAll, &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "offered htlc timeout confirmed", expSize: input.OfferedHtlcTimeoutWitnessSizeConfirmed, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.SenderHTLCScript( testPubkey, testPubkey, testPubkey, testHash160, true, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, } witness, err := input.SenderHtlcSpendTimeout( &maxDERSignature{}, txscript.SigHashAll, &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "offered htlc success", expSize: input.OfferedHtlcSuccessWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.SenderHTLCScript( testPubkey, testPubkey, testPubkey, testHash160, false, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, } witness, err := input.SenderHtlcSpendRedeem( &dummySigner{}, signDesc, testTx, testPreimage, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "offered htlc success confirmed", expSize: input.OfferedHtlcSuccessWitnessSizeConfirmed, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.SenderHTLCScript( testPubkey, testPubkey, testPubkey, testHash160, true, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, } witness, err := input.SenderHtlcSpendRedeem( &dummySigner{}, signDesc, testTx, testPreimage, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "accepted htlc revoke", expSize: input.AcceptedHtlcPenaltyWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.ReceiverHTLCScript( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, false, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, DoubleTweak: testPrivkey, } witness, err := input.ReceiverHtlcSpendRevoke( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "accepted htlc revoke confirmed", expSize: input.AcceptedHtlcPenaltyWitnessSizeConfirmed, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.ReceiverHTLCScript( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, true, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, DoubleTweak: testPrivkey, } witness, err := input.ReceiverHtlcSpendRevoke( &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "accepted htlc timeout", expSize: input.AcceptedHtlcTimeoutWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.ReceiverHTLCScript( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, false, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, } witness, err := input.ReceiverHtlcSpendTimeout( &dummySigner{}, signDesc, testTx, testCLTVExpiry, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "accepted htlc timeout confirmed", expSize: input.AcceptedHtlcTimeoutWitnessSizeConfirmed, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.ReceiverHTLCScript( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, true, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, } witness, err := input.ReceiverHtlcSpendTimeout( &dummySigner{}, signDesc, testTx, testCLTVExpiry, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "accepted htlc success", expSize: input.AcceptedHtlcSuccessWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.ReceiverHTLCScript( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, false, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, } witness, err := input.ReceiverHtlcSpendRedeem( &maxDERSignature{}, txscript.SigHashAll, testPreimage, &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "accepted htlc success confirmed", expSize: input.AcceptedHtlcSuccessWitnessSizeConfirmed, genWitness: func(t *testing.T) wire.TxWitness { witScript, err := input.ReceiverHTLCScript( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, true, ) if err != nil { t.Fatal(err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, } witness, err := input.ReceiverHtlcSpendRedeem( &maxDERSignature{}, txscript.SigHashAll, testPreimage, &dummySigner{}, signDesc, testTx, ) if err != nil { t.Fatal(err) } return witness }, }, { name: "taproot to local sweep", expSize: input.TaprootToLocalWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { testKey, err := btcec.NewPrivateKey() require.NoError(t, err) signer := &dummySigner{} commitScriptTree, err := input.NewLocalCommitScriptTree( testCSVDelay, testKey.PubKey(), testKey.PubKey(), input.NoneTapLeaf(), ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: testKey.PubKey(), }, //nolint:lll WitnessScript: commitScriptTree.SettleLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } witness, err := input.TaprootCommitSpendSuccess( signer, signDesc, testTx, commitScriptTree.TapscriptTree, ) require.NoError(t, err) return witness }, }, { name: "taproot to local revocation", expSize: input.TaprootToLocalRevokeWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { testKey, err := btcec.NewPrivateKey() require.NoError(t, err) signer := &dummySigner{} commitScriptTree, err := input.NewLocalCommitScriptTree( testCSVDelay, testKey.PubKey(), testKey.PubKey(), input.NoneTapLeaf(), ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: testKey.PubKey(), }, //nolint:lll WitnessScript: commitScriptTree.RevocationLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } witness, err := input.TaprootCommitSpendRevoke( signer, signDesc, testTx, commitScriptTree.TapscriptTree, ) require.NoError(t, err) return witness }, }, { name: "taproot to remote sweep", expSize: input.TaprootToRemoteWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { testKey, err := btcec.NewPrivateKey() require.NoError(t, err) signer := &dummySigner{} //nolint:lll commitScriptTree, err := input.NewRemoteCommitScriptTree( testKey.PubKey(), input.NoneTapLeaf(), ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: testKey.PubKey(), }, //nolint:lll WitnessScript: commitScriptTree.SettleLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } witness, err := input.TaprootCommitRemoteSpend( signer, signDesc, testTx, commitScriptTree.TapscriptTree, ) require.NoError(t, err) return witness }, }, { name: "taproot anchor sweep", expSize: input.TaprootAnchorWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { testKey, err := btcec.NewPrivateKey() require.NoError(t, err) signer := &dummySigner{} anchorScriptTree, err := input.NewAnchorScriptTree( testKey.PubKey(), ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: testKey.PubKey(), }, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootKeySpendSignMethod, TapTweak: anchorScriptTree.TapscriptRoot, } witness, err := input.TaprootAnchorSpend( signer, signDesc, testTx, ) require.NoError(t, err) return witness }, }, { name: "taproot second level htlc success+timeout", expSize: input.TaprootSecondLevelHtlcWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { testKey, err := btcec.NewPrivateKey() require.NoError(t, err) signer := &dummySigner{} scriptTree, err := input.SecondLevelHtlcTapscriptTree( testKey.PubKey(), testCSVDelay, input.NoneTapLeaf(), ) require.NoError(t, err) tapScriptRoot := scriptTree.RootNode.TapHash() revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) tapLeaf := scriptTree.LeafMerkleProofs[0].TapLeaf witnessScript := tapLeaf.Script signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: revokeKey.PubKey(), }, WitnessScript: witnessScript, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootKeySpendSignMethod, TapTweak: tapScriptRoot[:], } witness, err := input.TaprootHtlcSpendSuccess( signer, signDesc, testTx, revokeKey.PubKey(), scriptTree, ) require.NoError(t, err) return witness }, }, { name: "taproot second level htlc revoke", expSize: input.TaprootSecondLevelRevokeWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { testKey, err := btcec.NewPrivateKey() require.NoError(t, err) scriptTree, err := input.SecondLevelHtlcTapscriptTree( testKey.PubKey(), testCSVDelay, input.NoneTapLeaf(), ) require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) signer := &dummySigner{} tapScriptRoot := scriptTree.RootNode.TapHash() signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: revokeKey.PubKey(), }, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootKeySpendSignMethod, TapTweak: tapScriptRoot[:], } witness, err := input.TaprootHtlcSpendRevoke( signer, signDesc, testTx, ) require.NoError(t, err) return witness }, }, { name: "taproot offered htlc revoke", expSize: input.TaprootOfferedRevokeWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { senderKey, err := btcec.NewPrivateKey() require.NoError(t, err) receiverKey, err := btcec.NewPrivateKey() require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) var payHash [32]byte signer := &dummySigner{} htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: revokeKey.PubKey(), }, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootKeySpendSignMethod, TapTweak: htlcScriptTree.TapscriptRoot, } witness, err := input.SenderHTLCScriptTaprootRevoke( signer, signDesc, testTx, ) require.NoError(t, err) return witness }, }, { name: "taproot accepted htlc revoke", expSize: input.TaprootAcceptedRevokeWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { senderKey, err := btcec.NewPrivateKey() require.NoError(t, err) receiverKey, err := btcec.NewPrivateKey() require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) var payHash [32]byte signer := &dummySigner{} htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: revokeKey.PubKey(), }, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootKeySpendSignMethod, TapTweak: htlcScriptTree.TapscriptRoot, } witness, err := input.ReceiverHTLCScriptTaprootRevoke( signer, signDesc, testTx, ) require.NoError(t, err) return witness }, }, { name: "taproot offered remote timeout", expSize: input.TaprootHtlcOfferedRemoteTimeoutWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { senderKey, err := btcec.NewPrivateKey() require.NoError(t, err) receiverKey, err := btcec.NewPrivateKey() require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) var payHash [32]byte signer := &dummySigner{} htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) timeoutLeaf := htlcScriptTree.TimeoutTapLeaf signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: senderKey.PubKey(), }, WitnessScript: timeoutLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } witness, err := input.ReceiverHTLCScriptTaprootTimeout( signer, signDesc, testTx, testCLTVExpiry, revokeKey.PubKey(), htlcScriptTree.TapscriptTree, ) require.NoError(t, err) return witness }, }, { name: "taproot offered local timeout", expSize: input.TaprootOfferedLocalTimeoutWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { senderKey, err := btcec.NewPrivateKey() require.NoError(t, err) receiverKey, err := btcec.NewPrivateKey() require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) var payHash [32]byte signer := &dummySigner{} htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) timeoutLeaf := htlcScriptTree.TimeoutTapLeaf scriptTree := htlcScriptTree.TapscriptTree receiverDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: receiverKey.PubKey(), }, WitnessScript: timeoutLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } receiverSig, err := signer.SignOutputRaw( testTx, receiverDesc, ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: senderKey.PubKey(), }, WitnessScript: timeoutLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } witness, err := input.SenderHTLCScriptTaprootTimeout( receiverSig, txscript.SigHashAll, signer, signDesc, testTx, revokeKey.PubKey(), scriptTree, ) require.NoError(t, err) return witness }, }, { name: "taproot accepted remote success", expSize: input.TaprootHtlcAcceptedRemoteSuccessWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { senderKey, err := btcec.NewPrivateKey() require.NoError(t, err) receiverKey, err := btcec.NewPrivateKey() require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) var payHash [32]byte signer := &dummySigner{} htlcScriptTree, err := input.SenderHTLCScriptTaproot( senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) successLeaf := htlcScriptTree.SuccessTapLeaf scriptTree := htlcScriptTree.TapscriptTree signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: receiverKey.PubKey(), }, WitnessScript: successLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } witness, err := input.SenderHTLCScriptTaprootRedeem( signer, signDesc, testTx, testPreimage, revokeKey.PubKey(), scriptTree, ) require.NoError(t, err) return witness }, }, { name: "taproot accepted local success", expSize: input.TaprootHtlcAcceptedLocalSuccessWitnessSize, genWitness: func(t *testing.T) wire.TxWitness { senderKey, err := btcec.NewPrivateKey() require.NoError(t, err) receiverKey, err := btcec.NewPrivateKey() require.NoError(t, err) revokeKey, err := btcec.NewPrivateKey() require.NoError(t, err) var payHash [32]byte signer := &dummySigner{} htlcScriptTree, err := input.ReceiverHTLCScriptTaproot( testCLTVExpiry, senderKey.PubKey(), receiverKey.PubKey(), revokeKey.PubKey(), payHash[:], lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) successsLeaf := htlcScriptTree.SuccessTapLeaf scriptTree := htlcScriptTree.TapscriptTree senderDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: senderKey.PubKey(), }, WitnessScript: successsLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } senderSig, err := signer.SignOutputRaw( testTx, senderDesc, ) require.NoError(t, err) signDesc := &input.SignDescriptor{ KeyDesc: keychain.KeyDescriptor{ PubKey: receiverKey.PubKey(), }, WitnessScript: successsLeaf.Script, HashType: txscript.SigHashAll, InputIndex: 0, SignMethod: input.TaprootScriptSpendSignMethod, //nolint:lll } witness, err := input.ReceiverHTLCScriptTaprootRedeem( senderSig, txscript.SigHashAll, testPreimage, signer, signDesc, testTx, revokeKey.PubKey(), scriptTree, ) require.NoError(t, err) return witness }, }, } // TestWitnessSizes asserts the correctness of our magic witness constants. // Witnesses involving signatures will have maxDERSignatures injected so that we // can determine upper bounds for the witness sizes. These constants are // predominately used for fee estimation, so we want to be certain that we // aren't under estimating or our transactions could get stuck. func TestWitnessSizes(t *testing.T) { for _, test := range witnessSizeTests { test := test t.Run(test.name, func(t *testing.T) { size := test.genWitness(t).SerializeSize() if size != test.expSize { t.Fatalf("size mismatch, want: %v, got: %v", test.expSize, size) } }) } } // genTimeoutTx creates a signed HTLC second level timeout tx. func genTimeoutTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx { testKeyPriv, err := btcec.NewPrivateKey() require.NoError(t, err) testPubkey := testKeyPriv.PubKey() // Create the unsigned timeout tx. timeoutTx, err := lnwallet.CreateHtlcTimeoutTx( chanType, false, testOutPoint, testAmt, testCLTVExpiry, testCSVDelay, 0, testPubkey, testPubkey, input.NoneTapLeaf(), ) require.NoError(t, err) // In order to sign the transaction, generate the script for the output // it spends. var ( witScript []byte tapscriptTree *input.HtlcScriptTree ) if chanType.IsTaproot() { tapscriptTree, err = input.SenderHTLCScriptTaproot( testPubkey, testPubkey, testPubkey, testHash160, lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) witScript = tapscriptTree.TimeoutTapLeaf.Script } else { witScript, err = input.SenderHTLCScript( testPubkey, testPubkey, testPubkey, testHash160, chanType.HasAnchors(), ) require.NoError(t, err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, } sigHashType := lnwallet.HtlcSigHashType(chanType) // Sign the timeout tx and add the witness. var timeoutWitness [][]byte if chanType.IsTaproot() { signDesc.SignMethod = input.TaprootScriptSpendSignMethod timeoutWitness, err = input.SenderHTLCScriptTaprootTimeout( &maxSchnorrSignature{}, sigHashType, &dummySigner{}, signDesc, timeoutTx, testPubkey, tapscriptTree.TapscriptTree, ) require.NoError(t, err) } else { timeoutWitness, err = input.SenderHtlcSpendTimeout( &maxDERSignature{}, sigHashType, &dummySigner{}, signDesc, timeoutTx, ) require.NoError(t, err) } timeoutTx.TxIn[0].Witness = timeoutWitness return timeoutTx } // genSuccessTx creates a signed HTLC second level success tx. func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx { testKeyPriv, err := btcec.NewPrivateKey() require.NoError(t, err) testPubkey := testKeyPriv.PubKey() // Create the unsigned success tx. successTx, err := lnwallet.CreateHtlcSuccessTx( chanType, false, testOutPoint, testAmt, testCSVDelay, 0, testPubkey, testPubkey, input.NoneTapLeaf(), ) require.NoError(t, err) // In order to sign the transaction, generate the script for the output // it spends. var ( witScript []byte tapscriptTree *input.HtlcScriptTree ) if chanType.IsTaproot() { tapscriptTree, err = input.ReceiverHTLCScriptTaproot( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, lntypes.Remote, input.NoneTapLeaf(), ) require.NoError(t, err) witScript = tapscriptTree.SuccessTapLeaf.Script } else { witScript, err = input.ReceiverHTLCScript( testCLTVExpiry, testPubkey, testPubkey, testPubkey, testHash160, chanType.HasAnchors(), ) require.NoError(t, err) } signDesc := &input.SignDescriptor{ WitnessScript: witScript, KeyDesc: keychain.KeyDescriptor{ PubKey: testPubkey, }, } sigHashType := lnwallet.HtlcSigHashType(channeldb.SingleFunderBit) var successWitness [][]byte // Sign the success tx and add the witness. if chanType.IsTaproot() { signDesc.SignMethod = input.TaprootScriptSpendSignMethod successWitness, err = input.ReceiverHTLCScriptTaprootRedeem( &maxSchnorrSignature{}, sigHashType, testPreimage, &dummySigner{}, signDesc, successTx, testPubkey, tapscriptTree.TapscriptTree, ) require.NoError(t, err) } else { successWitness, err = input.ReceiverHtlcSpendRedeem( &maxDERSignature{}, sigHashType, testPreimage, &dummySigner{}, signDesc, successTx, ) require.NoError(t, err) } successTx.TxIn[0].Witness = successWitness return successTx } type txSizeTest struct { name string expWeight int64 genTx func(t *testing.T) *wire.MsgTx } var txSizeTests = []txSizeTest{ { name: "htlc timeout regular", expWeight: input.HtlcTimeoutWeight, genTx: func(t *testing.T) *wire.MsgTx { return genTimeoutTx(t, channeldb.SingleFunderBit) }, }, { name: "htlc timeout confirmed", expWeight: input.HtlcTimeoutWeightConfirmed, genTx: func(t *testing.T) *wire.MsgTx { return genTimeoutTx(t, channeldb.AnchorOutputsBit) }, }, { name: "taproot htlc timeout", expWeight: input.TaprootHtlcTimeoutWeight, genTx: func(t *testing.T) *wire.MsgTx { return genTimeoutTx( t, channeldb.SimpleTaprootFeatureBit, ) }, }, { name: "htlc success regular", // The weight estimate from the spec is off by one, but it's // okay since we overestimate the weight. expWeight: input.HtlcSuccessWeight - 1, genTx: func(t *testing.T) *wire.MsgTx { return genSuccessTx(t, channeldb.SingleFunderBit) }, }, { name: "htlc success confirmed", // The weight estimate from the spec is off by one, but it's // okay since we overestimate the weight. expWeight: input.HtlcSuccessWeightConfirmed - 1, genTx: func(t *testing.T) *wire.MsgTx { return genSuccessTx(t, channeldb.AnchorOutputsBit) }, }, { name: "taproot htlc success", expWeight: input.TaprootHtlcSuccessWeight, genTx: func(t *testing.T) *wire.MsgTx { return genSuccessTx( t, channeldb.SimpleTaprootFeatureBit, ) }, }, } // TestTxSizes asserts the correctness of our magic tx size constants. func TestTxSizes(t *testing.T) { for _, test := range txSizeTests { test := test t.Run(test.name, func(t *testing.T) { tx := test.genTx(t) weight := blockchain.GetTransactionWeight( btcutil.NewTx(tx), ) if weight != test.expWeight { t.Fatalf("size mismatch, want: %v, got: %v", test.expWeight, weight) } }) } }