lnwallet: add taproot channel test vector generator
What changed, and why it matters
This commit only adds new test code and a JSON file of expected test outputs for Taproot Lightning channels. It does not change any production logic, network behavior, or wallet handling. There is no security issue in the commit itself.
No action required. This is a test-infrastructure addition. Reviewers may optionally confirm the vectors are deterministic and that the fixed seed/private keys are only used in tests.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The change introduces lnwallet/taproot_test_vectors_test.go and lnwallet/test_vectors_taproot.json. The Go file is a test-only generator/verifier that deterministically derives keys from a fixed 32-byte seed, builds tapscript trees for all channel output types, and produces serialized commitment/HTLC transactions for three scenarios. The JSON file stores the generated vectors. No runtime or consensus code is modified.
Changed components
lnwallet/taproot_test_vectors_test.golnwallet/test_vectors_taproot.jsonInspect captured patch +1570 / −0
diff --git a/lnwallet/taproot_test_vectors_test.go b/lnwallet/taproot_test_vectors_test.go
new file mode 100644
index 0000000..a6894de
--- /dev/null
+++ b/lnwallet/taproot_test_vectors_test.go
@@ -0,0 +1,1261 @@
+package lnwallet
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "net"
+ "os"
+ "sort"
+ "testing"
+
+ "github.com/btcsuite/btcd/btcec/v2"
+ "github.com/btcsuite/btcd/btcutil"
+ "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/fn/v2"
+ "github.com/lightningnetwork/lnd/input"
+ "github.com/lightningnetwork/lnd/keychain"
+ "github.com/lightningnetwork/lnd/lntypes"
+ "github.com/lightningnetwork/lnd/lnwallet/chainfee"
+ "github.com/lightningnetwork/lnd/lnwire"
+ "github.com/lightningnetwork/lnd/shachain"
+ "github.com/stretchr/testify/require"
+)
+
+// generateTaprootVectors controls whether to generate test vectors and write
+// them to disk, or to verify the stored vectors match regenerated values.
+var generateTaprootVectors = flag.Bool(
+ "generate-taproot-vectors", false,
+ "generate taproot test vectors and write to "+taprootVectorFile,
+)
+
+const (
+ // taprootVectorSeedHex is the single deterministic seed from which all
+ // test vector keys are derived.
+ taprootVectorSeedHex = "000102030405060708090a0b0c0d0e0f" +
+ "101112131415161718191a1b1c1d1e1f"
+
+ // taprootVectorFile is the JSON file where test vectors are stored.
+ taprootVectorFile = "test_vectors_taproot.json"
+)
+
+// deriveKeyFromSeed derives a deterministic private key from a seed and a
+// label string. The key is computed as SHA256(seed || label).
+func deriveKeyFromSeed(seed []byte, label string) *btcec.PrivateKey {
+ h := sha256.New()
+ h.Write(seed)
+ h.Write([]byte(label))
+ keyBytes := h.Sum(nil)
+
+ privKey, _ := btcec.PrivKeyFromBytes(keyBytes)
+ return privKey
+}
+
+// pubHex returns the compressed hex encoding of a public key.
+func pubHex(pub *btcec.PublicKey) string {
+ return hex.EncodeToString(pub.SerializeCompressed())
+}
+
+// privHex returns the hex encoding of a private key scalar.
+func privHex(priv *btcec.PrivateKey) string {
+ return hex.EncodeToString(priv.Serialize())
+}
+
+// scriptHex returns the hex encoding of a byte slice (script, hash, etc.).
+func scriptHex(b []byte) string {
+ return hex.EncodeToString(b)
+}
+
+// leafHash computes the TapHash of a tap leaf script.
+func leafHash(script []byte) string {
+ leaf := txscript.NewBaseTapLeaf(script)
+ h := leaf.TapHash()
+ return hex.EncodeToString(h[:])
+}
+
+// taprootTestContext holds all deterministic keys and parameters for taproot
+// test vector generation.
+type taprootTestContext struct {
+ seed []byte
+
+ localFundingPrivkey *btcec.PrivateKey
+ remoteFundingPrivkey *btcec.PrivateKey
+ localPaymentBasepointSecret *btcec.PrivateKey
+ remotePaymentBasepointSecret *btcec.PrivateKey
+ localDelayedPaymentBasepointSecret *btcec.PrivateKey
+ remoteRevocationBasepointSecret *btcec.PrivateKey
+ localHtlcBasepointSecret *btcec.PrivateKey
+ remoteHtlcBasepointSecret *btcec.PrivateKey
+
+ localPerCommitSecret lntypes.Hash
+
+ fundingAmount btcutil.Amount
+ dustLimit btcutil.Amount
+ localCsvDelay uint16
+ commitHeight uint64
+
+ t *testing.T
+}
+
+// newTaprootTestContext creates a new test context with all keys derived
+// deterministically from the single seed.
+func newTaprootTestContext(t *testing.T) *taprootTestContext {
+ seed, err := hex.DecodeString(taprootVectorSeedHex)
+ require.NoError(t, err)
+
+ tc := &taprootTestContext{
+ seed: seed,
+ fundingAmount: 10_000_000,
+ dustLimit: 354,
+ localCsvDelay: 144,
+ commitHeight: 42,
+ t: t,
+ }
+
+ tc.localFundingPrivkey = deriveKeyFromSeed(seed, "local-funding")
+ tc.remoteFundingPrivkey = deriveKeyFromSeed(seed, "remote-funding")
+ tc.localPaymentBasepointSecret = deriveKeyFromSeed(
+ seed, "local-payment-basepoint",
+ )
+ tc.remotePaymentBasepointSecret = deriveKeyFromSeed(
+ seed, "remote-payment-basepoint",
+ )
+ tc.localDelayedPaymentBasepointSecret = deriveKeyFromSeed(
+ seed, "local-delayed-payment-basepoint",
+ )
+ tc.remoteRevocationBasepointSecret = deriveKeyFromSeed(
+ seed, "remote-revocation-basepoint",
+ )
+ tc.localHtlcBasepointSecret = deriveKeyFromSeed(
+ seed, "local-htlc-basepoint",
+ )
+ tc.remoteHtlcBasepointSecret = deriveKeyFromSeed(
+ seed, "remote-htlc-basepoint",
+ )
+
+ // Derive per-commitment secret from the seed as well.
+ h := sha256.New()
+ h.Write(seed)
+ h.Write([]byte("local-per-commit-secret"))
+ copy(tc.localPerCommitSecret[:], h.Sum(nil))
+
+ return tc
+}
+
+// commitPoint returns the per-commitment point derived from the secret.
+func (tc *taprootTestContext) commitPoint() *btcec.PublicKey {
+ return input.ComputeCommitmentPoint(tc.localPerCommitSecret[:])
+}
+
+// ---------------------------------------------------------------------------
+// JSON output types
+// ---------------------------------------------------------------------------
+
+// TaprootTestVectors is the top-level JSON structure for taproot test vectors.
+type TaprootTestVectors struct {
+ Params TestVectorParams `json:"params"`
+ Scripts ScriptVectors `json:"scripts"`
+ Transactions []TransactionTestCase `json:"transactions"`
+}
+
+// TestVectorParams holds the seed, channel parameters, and all keys.
+type TestVectorParams struct {
+ Seed string `json:"seed"`
+ FundingAmountSatoshis int64 `json:"funding_amount_satoshis"`
+ DustLimitSatoshis int64 `json:"dust_limit_satoshis"`
+ CsvDelay uint16 `json:"csv_delay"`
+ CommitHeight uint64 `json:"commit_height"`
+ NumsPoint string `json:"nums_point"`
+ Keys KeySet `json:"keys"`
+}
+
+// KeySet contains all base point keys and derived per-commitment keys.
+type KeySet struct {
+ LocalFundingPrivkey string `json:"local_funding_privkey"`
+ LocalFundingPubkey string `json:"local_funding_pubkey"`
+ RemoteFundingPrivkey string `json:"remote_funding_privkey"`
+ RemoteFundingPubkey string `json:"remote_funding_pubkey"`
+
+ LocalPaymentBasepointSecret string `json:"local_payment_basepoint_secret"`
+ LocalPaymentBasepoint string `json:"local_payment_basepoint"`
+ RemotePaymentBasepointSecret string `json:"remote_payment_basepoint_secret"`
+ RemotePaymentBasepoint string `json:"remote_payment_basepoint"`
+
+ LocalDelayedPaymentBasepointSecret string `json:"local_delayed_payment_basepoint_secret"`
+ LocalDelayedPaymentBasepoint string `json:"local_delayed_payment_basepoint"`
+ RemoteRevocationBasepointSecret string `json:"remote_revocation_basepoint_secret"`
+ RemoteRevocationBasepoint string `json:"remote_revocation_basepoint"`
+
+ LocalHtlcBasepointSecret string `json:"local_htlc_basepoint_secret"`
+ LocalHtlcBasepoint string `json:"local_htlc_basepoint"`
+ RemoteHtlcBasepointSecret string `json:"remote_htlc_basepoint_secret"`
+ RemoteHtlcBasepoint string `json:"remote_htlc_basepoint"`
+
+ LocalPerCommitSecret string `json:"local_per_commit_secret"`
+ LocalPerCommitPoint string `json:"local_per_commit_point"`
+
+ // Derived per-commitment keys.
+ DerivedLocalDelayedPubkey string `json:"derived_local_delayed_pubkey"`
+ DerivedRevocationPubkey string `json:"derived_revocation_pubkey"`
+ DerivedLocalHtlcPubkey string `json:"derived_local_htlc_pubkey"`
+ DerivedRemoteHtlcPubkey string `json:"derived_remote_htlc_pubkey"`
+ DerivedRemotePaymentPubkey string `json:"derived_remote_payment_pubkey"`
+}
+
+// ScriptVectorEntry represents a single tapscript tree decomposition.
+type ScriptVectorEntry struct {
+ // For scripts with named leaves.
+ Scripts map[string]string `json:"scripts,omitempty"`
+ LeafHashes map[string]string `json:"leaf_hashes,omitempty"`
+
+ TapscriptRoot string `json:"tapscript_root"`
+ InternalKey string `json:"internal_key"`
+ OutputKey string `json:"output_key"`
+ PkScript string `json:"pkscript"`
+}
+
+// FundingScriptVector holds the funding output vector.
+type FundingScriptVector struct {
+ FundingTxHex string `json:"funding_tx_hex"`
+ CombinedKey string `json:"combined_key"`
+ PkScript string `json:"pkscript"`
+}
+
+// ScriptVectors holds all script test vectors.
+type ScriptVectors struct {
+ Funding FundingScriptVector `json:"funding"`
+ ToLocal ScriptVectorEntry `json:"to_local"`
+ ToRemote ScriptVectorEntry `json:"to_remote"`
+ LocalAnchor ScriptVectorEntry `json:"local_anchor"`
+ RemoteAnchor ScriptVectorEntry `json:"remote_anchor"`
+ OfferedHtlcLocalCommit ScriptVectorEntry `json:"offered_htlc_local_commit"`
+ OfferedHtlcRemoteCommit ScriptVectorEntry `json:"offered_htlc_remote_commit"`
+ AcceptedHtlcLocalCommit ScriptVectorEntry `json:"accepted_htlc_local_commit"`
+ AcceptedHtlcRemoteCommit ScriptVectorEntry `json:"accepted_htlc_remote_commit"`
+ SecondLevelHtlcSuccess ScriptVectorEntry `json:"second_level_htlc_success"`
+ SecondLevelHtlcTimeout ScriptVectorEntry `json:"second_level_htlc_timeout"`
+}
+
+// HtlcDesc describes an HTLC resolution in the transaction vectors.
+type HtlcDesc struct {
+ RemotePartialSigHex string `json:"remote_partial_sig_hex"`
+ ResolutionTxHex string `json:"resolution_tx_hex"`
+}
+
+// HtlcInput describes an HTLC added to the channel for a test case.
+type HtlcInput struct {
+ Incoming bool `json:"incoming"`
+ AmountMsat uint64 `json:"amount_msat"`
+ Expiry uint32 `json:"expiry"`
+ Preimage string `json:"preimage"`
+}
+
+// TransactionTestCase is one transaction test vector.
+type TransactionTestCase struct {
+ Name string `json:"name"`
+ LocalBalanceMsat uint64 `json:"local_balance_msat"`
+ RemoteBalanceMsat uint64 `json:"remote_balance_msat"`
+ FeePerKw int64 `json:"fee_per_kw"`
+ DustLimitSatoshis int64 `json:"dust_limit_satoshis,omitempty"`
+ Htlcs []HtlcInput `json:"htlcs"`
+ RemotePartialSig string `json:"remote_partial_sig"`
+ ExpectedCommitmentTxHex string `json:"expected_commitment_tx_hex"`
+ HtlcDescs []HtlcDesc `json:"htlc_descs"`
+}
+
+// ---------------------------------------------------------------------------
+// Script vector generation (Section A)
+// ---------------------------------------------------------------------------
+
+// generateParams populates the params section of the test vectors.
+func (tc *taprootTestContext) generateParams() TestVectorParams {
+ commitPt := tc.commitPoint()
+
+ // Derive per-commitment tweaked keys.
+ localDelayedPubkey := input.TweakPubKey(
+ tc.localDelayedPaymentBasepointSecret.PubKey(), commitPt,
+ )
+ revocationPubkey := input.DeriveRevocationPubkey(
+ tc.remoteRevocationBasepointSecret.PubKey(), commitPt,
+ )
+ localHtlcPubkey := input.TweakPubKey(
+ tc.localHtlcBasepointSecret.PubKey(), commitPt,
+ )
+ remoteHtlcPubkey := input.TweakPubKey(
+ tc.remoteHtlcBasepointSecret.PubKey(), commitPt,
+ )
+ // For tweakless channels, the remote payment key is untweaked.
+ remotePaymentPubkey := tc.remotePaymentBasepointSecret.PubKey()
+
+ return TestVectorParams{
+ Seed: taprootVectorSeedHex,
+ FundingAmountSatoshis: int64(tc.fundingAmount),
+ DustLimitSatoshis: int64(tc.dustLimit),
+ CsvDelay: tc.localCsvDelay,
+ CommitHeight: tc.commitHeight,
+ NumsPoint: input.TaprootNUMSHex,
+ Keys: KeySet{
+ LocalFundingPrivkey: privHex(tc.localFundingPrivkey),
+ LocalFundingPubkey: pubHex(tc.localFundingPrivkey.PubKey()),
+ RemoteFundingPrivkey: privHex(tc.remoteFundingPrivkey),
+ RemoteFundingPubkey: pubHex(tc.remoteFundingPrivkey.PubKey()),
+
+ LocalPaymentBasepointSecret: privHex(tc.localPaymentBasepointSecret),
+ LocalPaymentBasepoint: pubHex(tc.localPaymentBasepointSecret.PubKey()),
+ RemotePaymentBasepointSecret: privHex(tc.remotePaymentBasepointSecret),
+ RemotePaymentBasepoint: pubHex(tc.remotePaymentBasepointSecret.PubKey()),
+
+ LocalDelayedPaymentBasepointSecret: privHex(tc.localDelayedPaymentBasepointSecret),
+ LocalDelayedPaymentBasepoint: pubHex(tc.localDelayedPaymentBasepointSecret.PubKey()),
+ RemoteRevocationBasepointSecret: privHex(tc.remoteRevocationBasepointSecret),
+ RemoteRevocationBasepoint: pubHex(tc.remoteRevocationBasepointSecret.PubKey()),
+
+ LocalHtlcBasepointSecret: privHex(tc.localHtlcBasepointSecret),
+ LocalHtlcBasepoint: pubHex(tc.localHtlcBasepointSecret.PubKey()),
+ RemoteHtlcBasepointSecret: privHex(tc.remoteHtlcBasepointSecret),
+ RemoteHtlcBasepoint: pubHex(tc.remoteHtlcBasepointSecret.PubKey()),
+
+ LocalPerCommitSecret: hex.EncodeToString(tc.localPerCommitSecret[:]),
+ LocalPerCommitPoint: pubHex(commitPt),
+
+ DerivedLocalDelayedPubkey: pubHex(localDelayedPubkey),
+ DerivedRevocationPubkey: pubHex(revocationPubkey),
+ DerivedLocalHtlcPubkey: pubHex(localHtlcPubkey),
+ DerivedRemoteHtlcPubkey: pubHex(remoteHtlcPubkey),
+ DerivedRemotePaymentPubkey: pubHex(remotePaymentPubkey),
+ },
+ }
+}
+
+// generateFundingVector generates the funding output script vector.
+func (tc *taprootTestContext) generateFundingVector() FundingScriptVector {
+ t := tc.t
+
+ pkScript, _, err := input.GenTaprootFundingScript(
+ tc.localFundingPrivkey.PubKey(),
+ tc.remoteFundingPrivkey.PubKey(),
+ int64(tc.fundingAmount),
+ fn.None[chainhash.Hash](),
+ )
+ require.NoError(t, err)
+
+ // Build a minimal funding transaction with the P2TR output.
+ fundingTx := wire.NewMsgTx(2)
+ fundingTx.AddTxIn(&wire.TxIn{
+ PreviousOutPoint: wire.OutPoint{
+ Hash: chainhash.Hash{},
+ Index: 0,
+ },
+ })
+ fundingTx.AddTxOut(&wire.TxOut{
+ Value: int64(tc.fundingAmount),
+ PkScript: pkScript,
+ })
+
+ var txBuf bytes.Buffer
+ require.NoError(t, fundingTx.Serialize(&txBuf))
+
+ // Extract the combined key from the pkScript. For P2TR, the pkScript
+ // is OP_1 <32-byte-key>, so the key starts at byte 2.
+ combinedKeyBytes := pkScript[2:]
+
+ return FundingScriptVector{
+ FundingTxHex: hex.EncodeToString(txBuf.Bytes()),
+ CombinedKey: hex.EncodeToString(combinedKeyBytes),
+ PkScript: scriptHex(pkScript),
+ }
+}
+
+// commitScriptTreeToEntry converts a CommitScriptTree into a ScriptVectorEntry.
+func commitScriptTreeToEntry(
+ tree *input.CommitScriptTree) ScriptVectorEntry {
+
+ scripts := make(map[string]string)
+ leafHashes := make(map[string]string)
+
+ settleScript := tree.SettleLeaf.Script
+ scripts["settle"] = scriptHex(settleScript)
+ leafHashes["settle"] = leafHash(settleScript)
+
+ if tree.RevocationLeaf.Script != nil {
+ revokeScript := tree.RevocationLeaf.Script
+ scripts["revocation"] = scriptHex(revokeScript)
+ leafHashes["revocation"] = leafHash(revokeScript)
+ }
+
+ return ScriptVectorEntry{
+ Scripts: scripts,
+ LeafHashes: leafHashes,
+ TapscriptRoot: scriptHex(tree.TapscriptRoot),
+ InternalKey: pubHex(tree.InternalKey),
+ OutputKey: pubHex(tree.TaprootKey),
+ PkScript: scriptHex(tree.PkScript()),
+ }
+}
+
+// htlcScriptTreeToEntry converts an HtlcScriptTree into a ScriptVectorEntry.
+func htlcScriptTreeToEntry(tree *input.HtlcScriptTree) ScriptVectorEntry {
+ scripts := make(map[string]string)
+ leafHashes := make(map[string]string)
+
+ successScript := tree.SuccessTapLeaf.Script
+ scripts["success"] = scriptHex(successScript)
+ leafHashes["success"] = leafHash(successScript)
+
+ timeoutScript := tree.TimeoutTapLeaf.Script
+ scripts["timeout"] = scriptHex(timeoutScript)
+ leafHashes["timeout"] = leafHash(timeoutScript)
+
+ return ScriptVectorEntry{
+ Scripts: scripts,
+ LeafHashes: leafHashes,
+ TapscriptRoot: scriptHex(tree.TapscriptRoot),
+ InternalKey: pubHex(tree.InternalKey),
+ OutputKey: pubHex(tree.TaprootKey),
+ PkScript: scriptHex(tree.PkScript()),
+ }
+}
+
+// secondLevelScriptTreeToEntry converts a SecondLevelScriptTree into a
+// ScriptVectorEntry.
+func secondLevelScriptTreeToEntry(
+ tree *input.SecondLevelScriptTree) ScriptVectorEntry {
+
+ scripts := make(map[string]string)
+ leafHashes := make(map[string]string)
+
+ successScript := tree.SuccessTapLeaf.Script
+ scripts["success"] = scriptHex(successScript)
+ leafHashes["success"] = leafHash(successScript)
+
+ return ScriptVectorEntry{
+ Scripts: scripts,
+ LeafHashes: leafHashes,
+ TapscriptRoot: scriptHex(tree.TapscriptRoot),
+ InternalKey: pubHex(tree.InternalKey),
+ OutputKey: pubHex(tree.TaprootKey),
+ PkScript: scriptHex(tree.PkScript()),
+ }
+}
+
+// anchorScriptTreeToEntry converts an AnchorScriptTree into a
+// ScriptVectorEntry.
+func anchorScriptTreeToEntry(
+ tree *input.AnchorScriptTree) ScriptVectorEntry {
+
+ scripts := make(map[string]string)
+ leafHashes := make(map[string]string)
+
+ sweepScript := tree.SweepLeaf.Script
+ scripts["sweep"] = scriptHex(sweepScript)
+ leafHashes["sweep"] = leafHash(sweepScript)
+
+ return ScriptVectorEntry{
+ Scripts: scripts,
+ LeafHashes: leafHashes,
+ TapscriptRoot: scriptHex(tree.TapscriptRoot),
+ InternalKey: pubHex(tree.InternalKey),
+ OutputKey: pubHex(tree.TaprootKey),
+ PkScript: scriptHex(tree.PkScript()),
+ }
+}
+
+// generateScriptVectors generates all script-only test vectors.
+func (tc *taprootTestContext) generateScriptVectors() ScriptVectors {
+ t := tc.t
+ commitPt := tc.commitPoint()
+
+ // Derive per-commitment tweaked keys.
+ localDelayedPubkey := input.TweakPubKey(
+ tc.localDelayedPaymentBasepointSecret.PubKey(), commitPt,
+ )
+ revocationPubkey := input.DeriveRevocationPubkey(
+ tc.remoteRevocationBasepointSecret.PubKey(), commitPt,
+ )
+ localHtlcPubkey := input.TweakPubKey(
+ tc.localHtlcBasepointSecret.PubKey(), commitPt,
+ )
+ remoteHtlcPubkey := input.TweakPubKey(
+ tc.remoteHtlcBasepointSecret.PubKey(), commitPt,
+ )
+ remotePaymentPubkey := tc.remotePaymentBasepointSecret.PubKey()
+
+ noAux := fn.None[txscript.TapLeaf]()
+
+ // 1. to_local script tree.
+ toLocalTree, err := input.NewLocalCommitScriptTree(
+ uint32(tc.localCsvDelay), localDelayedPubkey,
+ revocationPubkey, noAux, input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ // 2. to_remote script tree.
+ toRemoteTree, err := input.NewRemoteCommitScriptTree(
+ remotePaymentPubkey, noAux, input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ // 3. Anchor script trees.
+ localAnchorTree, err := input.NewAnchorScriptTree(
+ localDelayedPubkey,
+ )
+ require.NoError(t, err)
+
+ remoteAnchorTree, err := input.NewAnchorScriptTree(
+ remotePaymentPubkey,
+ )
+ require.NoError(t, err)
+
+ // Use HTLC 0 for offered/accepted HTLC vectors.
+ preimage0, err := lntypes.MakePreimageFromStr(
+ "0000000000000000000000000000000000000000000000000000000000000000",
+ )
+ require.NoError(t, err)
+ payHash0 := preimage0.Hash()
+
+ // 4. Offered HTLC (local commit).
+ offeredLocalTree, err := input.SenderHTLCScriptTaproot(
+ localHtlcPubkey, remoteHtlcPubkey, revocationPubkey,
+ payHash0[:], lntypes.Local, noAux,
+ input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ // 5. Offered HTLC (remote commit).
+ offeredRemoteTree, err := input.SenderHTLCScriptTaproot(
+ localHtlcPubkey, remoteHtlcPubkey, revocationPubkey,
+ payHash0[:], lntypes.Remote, noAux,
+ input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ // 6. Accepted HTLC (local commit).
+ acceptedLocalTree, err := input.ReceiverHTLCScriptTaproot(
+ 500, localHtlcPubkey, remoteHtlcPubkey, revocationPubkey,
+ payHash0[:], lntypes.Local, noAux,
+ input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ // 7. Accepted HTLC (remote commit).
+ acceptedRemoteTree, err := input.ReceiverHTLCScriptTaproot(
+ 500, localHtlcPubkey, remoteHtlcPubkey, revocationPubkey,
+ payHash0[:], lntypes.Remote, noAux,
+ input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ // 8. Second-level HTLC success.
+ secondLevelSuccess, err := input.TaprootSecondLevelScriptTree(
+ revocationPubkey, localDelayedPubkey,
+ uint32(tc.localCsvDelay), noAux,
+ input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ // 9. Second-level HTLC timeout (same function, different keys in a
+ // real scenario, but for vectors we show the construction with the
+ // same delay key since second-level success and timeout share the
+ // same script tree structure).
+ secondLevelTimeout, err := input.TaprootSecondLevelScriptTree(
+ revocationPubkey, localDelayedPubkey,
+ uint32(tc.localCsvDelay), noAux,
+ input.WithProdScripts(),
+ )
+ require.NoError(t, err)
+
+ return ScriptVectors{
+ Funding: tc.generateFundingVector(),
+ ToLocal: commitScriptTreeToEntry(toLocalTree),
+ ToRemote: commitScriptTreeToEntry(toRemoteTree),
+ LocalAnchor: anchorScriptTreeToEntry(localAnchorTree),
+ RemoteAnchor: anchorScriptTreeToEntry(remoteAnchorTree),
+ OfferedHtlcLocalCommit: htlcScriptTreeToEntry(offeredLocalTree),
+ OfferedHtlcRemoteCommit: htlcScriptTreeToEntry(offeredRemoteTree),
+ AcceptedHtlcLocalCommit: htlcScriptTreeToEntry(acceptedLocalTree),
+ AcceptedHtlcRemoteCommit: htlcScriptTreeToEntry(acceptedRemoteTree),
+ SecondLevelHtlcSuccess: secondLevelScriptTreeToEntry(secondLevelSuccess),
+ SecondLevelHtlcTimeout: secondLevelScriptTreeToEntry(secondLevelTimeout),
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Transaction vector generation (Section B)
+// ---------------------------------------------------------------------------
+
+// taprootChanType is the channel type used for taproot test vectors.
+var taprootChanType = channeldb.SingleFunderTweaklessBit |
+ channeldb.AnchorOutputsBit |
+ channeldb.ZeroHtlcTxFeeBit |
+ channeldb.SimpleTaprootFeatureBit |
+ channeldb.TaprootFinalBit
+
+// createTaprootTestChannelsForVectors creates a pair of LightningChannel
+// instances configured for taproot test vector generation. All keys are
+// deterministic.
+func createTaprootTestChannelsForVectors(tc *taprootTestContext,
+ feeRate btcutil.Amount, remoteBalance,
+ localBalance btcutil.Amount) (*LightningChannel, *LightningChannel) {
+
+ t := tc.t
+
+ // Build the funding transaction with a P2TR output.
+ pkScript, _, err := input.GenTaprootFundingScript(
+ tc.localFundingPrivkey.PubKey(),
+ tc.remoteFundingPrivkey.PubKey(),
+ int64(tc.fundingAmount),
+ fn.None[chainhash.Hash](),
+ )
+ require.NoError(t, err)
+
+ fundingTx := wire.NewMsgTx(2)
+ fundingTx.AddTxIn(&wire.TxIn{
+ PreviousOutPoint: wire.OutPoint{
+ Hash: chainhash.Hash{},
+ Index: 0,
+ },
+ })
+ fundingTx.AddTxOut(&wire.TxOut{
+ Value: int64(tc.fundingAmount),
+ PkScript: pkScript,
+ })
+ btcFundingTx := btcutil.NewTx(fundingTx)
+
+ prevOut := &wire.OutPoint{
+ Hash: *btcFundingTx.Hash(),
+ Index: 0,
+ }
+ fundingTxIn := wire.NewTxIn(prevOut, nil, nil)
+
+ chanType := taprootChanType
+
+ // Channel configurations using all deterministic keys.
+ remoteCfg := channeldb.ChannelConfig{
+ ChannelStateBounds: channeldb.ChannelStateBounds{
+ MaxPendingAmount: lnwire.NewMSatFromSatoshis(
+ tc.fundingAmount,
+ ),
+ ChanReserve: 0,
+ MinHTLC: 0,
+ MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
+ },
+ CommitmentParams: channeldb.CommitmentParams{
+ DustLimit: tc.dustLimit,
+ CsvDelay: tc.localCsvDelay,
+ },
+ MultiSigKey: keychain.KeyDescriptor{
+ PubKey: tc.remoteFundingPrivkey.PubKey(),
+ },
+ PaymentBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.remotePaymentBasepointSecret.PubKey(),
+ },
+ HtlcBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.remoteHtlcBasepointSecret.PubKey(),
+ },
+ DelayBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.remotePaymentBasepointSecret.PubKey(),
+ },
+ RevocationBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.remoteRevocationBasepointSecret.PubKey(),
+ },
+ }
+ localCfg := channeldb.ChannelConfig{
+ ChannelStateBounds: channeldb.ChannelStateBounds{
+ MaxPendingAmount: lnwire.NewMSatFromSatoshis(
+ tc.fundingAmount,
+ ),
+ ChanReserve: 0,
+ MinHTLC: 0,
+ MaxAcceptedHtlcs: input.MaxHTLCNumber / 2,
+ },
+ CommitmentParams: channeldb.CommitmentParams{
+ DustLimit: tc.dustLimit,
+ CsvDelay: tc.localCsvDelay,
+ },
+ MultiSigKey: keychain.KeyDescriptor{
+ PubKey: tc.localFundingPrivkey.PubKey(),
+ },
+ PaymentBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.localPaymentBasepointSecret.PubKey(),
+ },
+ HtlcBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.localHtlcBasepointSecret.PubKey(),
+ },
+ DelayBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.localDelayedPaymentBasepointSecret.PubKey(),
+ },
+ RevocationBasePoint: keychain.KeyDescriptor{
+ PubKey: tc.localPaymentBasepointSecret.PubKey(),
+ },
+ }
+
+ // Create mock producers for deterministic revocation secrets.
+ remotePreimageProducer := &mockProducer{
+ secret: chainhash.Hash(tc.localPerCommitSecret),
+ }
+ remoteCommitPoint := input.ComputeCommitmentPoint(
+ tc.localPerCommitSecret[:],
+ )
+
+ localPreimageProducer := &mockProducer{
+ secret: chainhash.Hash(tc.localPerCommitSecret),
+ }
+ localCommitPoint := input.ComputeCommitmentPoint(
+ tc.localPerCommitSecret[:],
+ )
+
+ // Create temporary databases.
+ dbRemote := channeldb.OpenForTesting(t, t.TempDir())
+ dbLocal := channeldb.OpenForTesting(t, t.TempDir())
+
+ // Create initial commitment transactions.
+ feePerKw := chainfee.SatPerKWeight(feeRate)
+ commitWeight := lntypes.WeightUnit(input.AnchorCommitWeight)
+ commitFee := feePerKw.FeeForWeight(commitWeight)
+ anchorAmt := btcutil.Amount(2 * AnchorSize)
+
+ remoteCommitTx, localCommitTx, err := CreateCommitmentTxns(
+ remoteBalance, localBalance-commitFee,
+ &remoteCfg, &localCfg, remoteCommitPoint,
+ localCommitPoint, *fundingTxIn, chanType, true, 0,
+ )
+ require.NoError(t, err)
+
+ var commitHeight = tc.commitHeight - 1
+
+ remoteCommit := channeldb.ChannelCommitment{
+ CommitHeight: commitHeight,
+ LocalBalance: lnwire.NewMSatFromSatoshis(remoteBalance),
+ RemoteBalance: lnwire.NewMSatFromSatoshis(localBalance - commitFee - anchorAmt),
+ CommitFee: commitFee,
+ FeePerKw: btcutil.Amount(feePerKw),
+ CommitTx: remoteCommitTx,
+ CommitSig: testSigBytes,
+ }
+ localCommit := channeldb.ChannelCommitment{
+ CommitHeight: commitHeight,
+ LocalBalance: lnwire.NewMSatFromSatoshis(localBalance - commitFee - anchorAmt),
+ RemoteBalance: lnwire.NewMSatFromSatoshis(remoteBalance),
+ CommitFee: commitFee,
+ FeePerKw: btcutil.Amount(feePerKw),
+ CommitTx: localCommitTx,
+ CommitSig: testSigBytes,
+ }
+
+ shortChanID := lnwire.NewShortChanIDFromInt(0xdeadbeef)
+
+ remoteChannelState := &channeldb.OpenChannel{
+ LocalChanCfg: remoteCfg,
+ RemoteChanCfg: localCfg,
+ IdentityPub: tc.remoteFundingPrivkey.PubKey(),
+ FundingOutpoint: *prevOut,
+ ShortChannelID: shortChanID,
+ ChanType: chanType,
+ IsInitiator: false,
+ Capacity: tc.fundingAmount,
+ RemoteCurrentRevocation: localCommitPoint,
+ RevocationProducer: remotePreimageProducer,
+ RevocationStore: shachain.NewRevocationStore(),
+ LocalCommitment: remoteCommit,
+ RemoteCommitment: remoteCommit,
+ Db: dbRemote.ChannelStateDB(),
+ Packager: channeldb.NewChannelPackager(shortChanID),
+ FundingTxn: fundingTx,
+ }
+ localChannelState := &channeldb.OpenChannel{
+ LocalChanCfg: localCfg,
+ RemoteChanCfg: remoteCfg,
+ IdentityPub: tc.localFundingPrivkey.PubKey(),
+ FundingOutpoint: *prevOut,
+ ShortChannelID: shortChanID,
+ ChanType: chanType,
+ IsInitiator: true,
+ Capacity: tc.fundingAmount,
+ RemoteCurrentRevocation: remoteCommitPoint,
+ RevocationProducer: localPreimageProducer,
+ RevocationStore: shachain.NewRevocationStore(),
+ LocalCommitment: localCommit,
+ RemoteCommitment: localCommit,
+ Db: dbLocal.ChannelStateDB(),
+ Packager: channeldb.NewChannelPackager(shortChanID),
+ FundingTxn: fundingTx,
+ }
+
+ // Create mock signers with all deterministic keys. The funding key must
+ // be at index 0 because the MusigSessionManager's key fetcher always
+ // returns Privkeys[0] as the MuSig2 signing key.
+ localSigner := input.NewMockSigner([]*btcec.PrivateKey{
+ tc.localFundingPrivkey,
+ tc.localPaymentBasepointSecret,
+ tc.localDelayedPaymentBasepointSecret,
+ tc.localHtlcBasepointSecret,
+ }, nil)
+
+ remoteSigner := input.NewMockSigner([]*btcec.PrivateKey{
+ tc.remoteFundingPrivkey,
+ tc.remoteRevocationBasepointSecret,
+ tc.remotePaymentBasepointSecret,
+ tc.remoteHtlcBasepointSecret,
+ }, nil)
+
+ // Derive deterministic signing rand for JIT nonces so MuSig2
+ // signatures are reproducible across runs.
+ localRandHash := sha256.Sum256(append(tc.seed, []byte("local-signing-rand")...))
+ remoteRandHash := sha256.Sum256(append(tc.seed, []byte("remote-signing-rand")...))
+
+ auxSigner := NewDefaultAuxSignerMock(t)
+ remotePool := NewSigPool(1, remoteSigner)
+ channelRemote, err := NewLightningChannel(
+ remoteSigner, remoteChannelState, remotePool,
+ WithLeafStore(&MockAuxLeafStore{}),
+ WithAuxSigner(auxSigner),
+ WithCustomSigningRand(bytes.NewReader(remoteRandHash[:])),
+ )
+ require.NoError(t, err)
+ require.NoError(t, remotePool.Start())
+
+ localPool := NewSigPool(1, localSigner)
+ channelLocal, err := NewLightningChannel(
+ localSigner, localChannelState, localPool,
+ WithLeafStore(&MockAuxLeafStore{}),
+ WithAuxSigner(auxSigner),
+ WithCustomSigningRand(bytes.NewReader(localRandHash[:])),
+ )
+ require.NoError(t, err)
+ require.NoError(t, localPool.Start())
+
+ // Create state hint obfuscator.
+ obfuscator := createStateHintObfuscator(remoteChannelState)
+ err = SetStateNumHint(remoteCommitTx, commitHeight, obfuscator)
+ require.NoError(t, err)
+ err = SetStateNumHint(localCommitTx, commitHeight, obfuscator)
+ require.NoError(t, err)
+
+ // Initialize the databases.
+ addr := &net.TCPAddr{
+ IP: net.ParseIP("127.0.0.1"),
+ Port: 18556,
+ }
+ require.NoError(t, channelRemote.channelState.SyncPending(addr, 101))
+
+ addr = &net.TCPAddr{
+ IP: net.ParseIP("127.0.0.1"),
+ Port: 18555,
+ }
+ require.NoError(t, channelLocal.channelState.SyncPending(addr, 101))
+
+ // Initialize revocation windows and musig nonces.
+ err = initRevocationWindows(channelRemote, channelLocal)
+ require.NoError(t, err)
+
+ t.Cleanup(func() {
+ dbLocal.Close()
+ dbRemote.Close()
+
+ require.NoError(t, remotePool.Stop())
+ require.NoError(t, localPool.Stop())
+ })
+
+ return channelRemote, channelLocal
+}
+
+// taprootTransactionTestCases defines the set of transaction test cases.
+var taprootTransactionTestCases = []struct {
+ name string
+ localBalance lnwire.MilliSatoshi
+ remoteBalance lnwire.MilliSatoshi
+ feePerKw btcutil.Amount
+ dustLimit btcutil.Amount
+ useTestHtlcs bool
+}{
+ {
+ name: "simple commitment tx with no HTLCs",
+ localBalance: 7_000_000_000,
+ remoteBalance: 3_000_000_000,
+ feePerKw: 15_000,
+ useTestHtlcs: false,
+ },
+ {
+ name: "commitment tx with five HTLCs untrimmed",
+ localBalance: 6_988_000_000,
+ remoteBalance: 3_000_000_000,
+ feePerKw: 644,
+ useTestHtlcs: true,
+ },
+ {
+ name: "commitment tx with some HTLCs trimmed",
+ localBalance: 6_988_000_000,
+ remoteBalance: 3_000_000_000,
+ feePerKw: 100_000,
+ dustLimit: 546,
+ useTestHtlcs: true,
+ },
+}
+
+// generateTransactionVectors generates all transaction test vectors.
+func (tc *taprootTestContext) generateTransactionVectors() []TransactionTestCase {
+ t := tc.t
+ var results []TransactionTestCase
+
+ for _, testCase := range taprootTransactionTestCases {
+ // Override dust limit if specified in the test case.
+ origDust := tc.dustLimit
+ if testCase.dustLimit != 0 {
+ tc.dustLimit = testCase.dustLimit
+ }
+
+ // Compute spendable balances by adding back in-flight HTLCs.
+ remoteBalance := testCase.remoteBalance
+ localBalance := testCase.localBalance
+ if testCase.useTestHtlcs {
+ for _, htlc := range testHtlcsSet1 {
+ if htlc.incoming {
+ remoteBalance += htlc.amount
+ } else {
+ localBalance += htlc.amount
+ }
+ }
+ }
+
+ // Verify balances add up to channel capacity.
+ require.EqualValues(t,
+ lnwire.NewMSatFromSatoshis(tc.fundingAmount),
+ remoteBalance+localBalance,
+ )
+
+ remoteChannel, localChannel := createTaprootTestChannelsForVectors(
+ tc, testCase.feePerKw,
+ remoteBalance.ToSatoshis(),
+ localBalance.ToSatoshis(),
+ )
+
+ // Add HTLCs if needed.
+ var hash160map map[[20]byte]lntypes.Preimage
+ if testCase.useTestHtlcs {
+ hash160map = addTestHtlcs(
+ t, remoteChannel, localChannel,
+ testHtlcsSet1,
+ )
+ }
+
+ // Execute commit dance.
+ localNewCommit, err := localChannel.SignNextCommitment(ctxb)
+ require.NoError(t, err)
+
+ err = remoteChannel.ReceiveNewCommitment(
+ localNewCommit.CommitSigs,
+ )
+ require.NoError(t, err)
+
+ revMsg, _, _, err := remoteChannel.RevokeCurrentCommitment()
+ require.NoError(t, err)
+
+ _, _, err = localChannel.ReceiveRevocation(revMsg)
+ require.NoError(t, err)
+
+ remoteNewCommit, err := remoteChannel.SignNextCommitment(ctxb)
+ require.NoError(t, err)
+
+ // Capture remote partial signature.
+ remoteSigHex := hex.EncodeToString(
+ remoteNewCommit.CommitSig.ToSignatureBytes(),
+ )
+
+ err = localChannel.ReceiveNewCommitment(
+ remoteNewCommit.CommitSigs,
+ )
+ require.NoError(t, err)
+
+ _, _, _, err = localChannel.RevokeCurrentCommitment()
+ require.NoError(t, err)
+
+ // Force close to get the commitment transaction.
+ forceCloseSum, err := localChannel.ForceClose()
+ require.NoError(t, err)
+
+ var txBytes bytes.Buffer
+ require.NoError(t, forceCloseSum.CloseTx.Serialize(&txBytes))
+
+ // Collect HTLC resolution transactions.
+ var htlcDescs []HtlcDesc
+ if testCase.useTestHtlcs {
+ resolutions := forceCloseSum.ContractResolutions.UnwrapOrFail(t)
+ htlcResolutions := resolutions.HtlcResolutions
+
+ secondLevelTxes := map[uint32]*wire.MsgTx{}
+ secondLevelSigs := map[uint32]string{}
+ storeTx := func(
+ index uint32, tx *wire.MsgTx, sig string,
+ ) {
+ secondLevelTxes[index] = tx
+ secondLevelSigs[index] = sig
+ }
+
+ for i, r := range htlcResolutions.IncomingHTLCs {
+ successTx := r.SignedSuccessTx
+ // Complete the witness with the preimage.
+ witnessScript := successTx.TxIn[0].Witness[4]
+ var hash160 [20]byte
+ copy(hash160[:], witnessScript[69:69+20])
+ preimage := hash160map[hash160]
+ successTx.TxIn[0].Witness[3] = preimage[:]
+
+ sigHex := hex.EncodeToString(
+ remoteNewCommit.HtlcSigs[i].ToSignatureBytes(),
+ )
+ storeTx(
+ r.HtlcPoint().Index, successTx, sigHex,
+ )
+ }
+ for i, r := range htlcResolutions.OutgoingHTLCs {
+ sigIdx := len(htlcResolutions.IncomingHTLCs) + i
+ sigHex := hex.EncodeToString(
+ remoteNewCommit.HtlcSigs[sigIdx].ToSignatureBytes(),
+ )
+ storeTx(
+ r.HtlcPoint().Index,
+ r.SignedTimeoutTx, sigHex,
+ )
+ }
+
+ var keys []uint32
+ for k := range secondLevelTxes {
+ keys = append(keys, k)
+ }
+ sort.Slice(keys, func(a, b int) bool {
+ return keys[a] < keys[b]
+ })
+
+ for _, idx := range keys {
+ tx := secondLevelTxes[idx]
+ var b bytes.Buffer
+ err := tx.Serialize(&b)
+ require.NoError(t, err)
+
+ htlcDescs = append(htlcDescs, HtlcDesc{
+ RemotePartialSigHex: secondLevelSigs[idx],
+ ResolutionTxHex: hex.EncodeToString(b.Bytes()),
+ })
+ }
+ }
+
+ // Build the HTLC input list.
+ var htlcInputs []HtlcInput
+ if testCase.useTestHtlcs {
+ for _, h := range testHtlcsSet1 {
+ htlcInputs = append(htlcInputs, HtlcInput{
+ Incoming: h.incoming,
+ AmountMsat: uint64(h.amount),
+ Expiry: h.expiry,
+ Preimage: h.preimage,
+ })
+ }
+ }
+
+ result := TransactionTestCase{
+ Name: testCase.name,
+ LocalBalanceMsat: uint64(testCase.localBalance),
+ RemoteBalanceMsat: uint64(testCase.remoteBalance),
+ FeePerKw: int64(testCase.feePerKw),
+ Htlcs: htlcInputs,
+ RemotePartialSig: remoteSigHex,
+ ExpectedCommitmentTxHex: hex.EncodeToString(txBytes.Bytes()),
+ HtlcDescs: htlcDescs,
+ }
+ if testCase.dustLimit != 0 {
+ result.DustLimitSatoshis = int64(testCase.dustLimit)
+ }
+
+ results = append(results, result)
+
+ // Restore dust limit.
+ tc.dustLimit = origDust
+ }
+
+ return results
+}
+
+// ---------------------------------------------------------------------------
+// Main test entry point
+// ---------------------------------------------------------------------------
+
+// TestTaprootVectors either generates or verifies taproot test vectors
+// depending on the -generate-taproot-vectors flag.
+func TestTaprootVectors(t *testing.T) {
+ if *generateTaprootVectors {
+ t.Log("Generating taproot test vectors...")
+ generateAndWriteTaprootVectors(t)
+ return
+ }
+
+ t.Log("Verifying taproot test vectors...")
+ verifyTaprootVectors(t)
+}
+
+// generateAndWriteTaprootVectors generates all taproot test vectors and writes
+// them to the JSON file.
+func generateAndWriteTaprootVectors(t *testing.T) {
+ tc := newTaprootTestContext(t)
+
+ vectors := TaprootTestVectors{
+ Params: tc.generateParams(),
+ Scripts: tc.generateScriptVectors(),
+ Transactions: tc.generateTransactionVectors(),
+ }
+
+ jsonData, err := json.MarshalIndent(vectors, "", " ")
+ require.NoError(t, err)
+
+ err = os.WriteFile(taprootVectorFile, jsonData, 0644)
+ require.NoError(t, err)
+
+ t.Logf("Wrote taproot test vectors to %s (%d bytes)",
+ taprootVectorFile, len(jsonData))
+}
+
+// verifyTaprootVectors reads the stored test vectors and verifies them by
+// regenerating all values from the seed.
+func verifyTaprootVectors(t *testing.T) {
+ jsonData, err := os.ReadFile(taprootVectorFile)
+ require.NoError(t, err, "test vectors file not found, run with "+
+ "-generate-taproot-vectors first")
+
+ var stored TaprootTestVectors
+ err = json.Unmarshal(jsonData, &stored)
+ require.NoError(t, err)
+
+ tc := newTaprootTestContext(t)
+
+ // Verify params.
+ t.Run("params", func(t *testing.T) {
+ params := tc.generateParams()
+ require.Equal(t, stored.Params, params)
+ })
+
+ // Verify script vectors.
+ t.Run("scripts", func(t *testing.T) {
+ scripts := tc.generateScriptVectors()
+
+ t.Run("funding", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.Funding.CombinedKey,
+ scripts.Funding.CombinedKey,
+ )
+ require.Equal(t,
+ stored.Scripts.Funding.PkScript,
+ scripts.Funding.PkScript,
+ )
+ })
+
+ t.Run("to_local", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.ToLocal, scripts.ToLocal,
+ )
+ })
+
+ t.Run("to_remote", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.ToRemote, scripts.ToRemote,
+ )
+ })
+
+ t.Run("local_anchor", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.LocalAnchor,
+ scripts.LocalAnchor,
+ )
+ })
+
+ t.Run("remote_anchor", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.RemoteAnchor,
+ scripts.RemoteAnchor,
+ )
+ })
+
+ t.Run("offered_htlc_local_commit", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.OfferedHtlcLocalCommit,
+ scripts.OfferedHtlcLocalCommit,
+ )
+ })
+
+ t.Run("offered_htlc_remote_commit", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.OfferedHtlcRemoteCommit,
+ scripts.OfferedHtlcRemoteCommit,
+ )
+ })
+
+ t.Run("accepted_htlc_local_commit", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.AcceptedHtlcLocalCommit,
+ scripts.AcceptedHtlcLocalCommit,
+ )
+ })
+
+ t.Run("accepted_htlc_remote_commit", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.AcceptedHtlcRemoteCommit,
+ scripts.AcceptedHtlcRemoteCommit,
+ )
+ })
+
+ t.Run("second_level_htlc_success", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.SecondLevelHtlcSuccess,
+ scripts.SecondLevelHtlcSuccess,
+ )
+ })
+
+ t.Run("second_level_htlc_timeout", func(t *testing.T) {
+ require.Equal(t,
+ stored.Scripts.SecondLevelHtlcTimeout,
+ scripts.SecondLevelHtlcTimeout,
+ )
+ })
+ })
+
+ // Verify transaction vectors.
+ t.Run("transactions", func(t *testing.T) {
+ txVectors := tc.generateTransactionVectors()
+ require.Equal(t, len(stored.Transactions), len(txVectors))
+
+ for i, storedTx := range stored.Transactions {
+ genTx := txVectors[i]
+ t.Run(storedTx.Name, func(t *testing.T) {
+ require.Equal(t,
+ storedTx.ExpectedCommitmentTxHex,
+ genTx.ExpectedCommitmentTxHex,
+ "commitment tx mismatch",
+ )
+ require.Equal(t,
+ storedTx.RemotePartialSig,
+ genTx.RemotePartialSig,
+ "remote partial sig mismatch",
+ )
+ require.Equal(t,
+ len(storedTx.HtlcDescs),
+ len(genTx.HtlcDescs),
+ "htlc desc count mismatch",
+ )
+ for j, storedHtlc := range storedTx.HtlcDescs {
+ require.Equal(t,
+ storedHtlc.ResolutionTxHex,
+ genTx.HtlcDescs[j].ResolutionTxHex,
+ fmt.Sprintf(
+ "htlc %d resolution tx mismatch", j,
+ ),
+ )
+ }
+ })
+ }
+ })
+}
+
diff --git a/lnwallet/test_vectors_taproot.json b/lnwallet/test_vectors_taproot.json
new file mode 100644
index 0000000..6de7141
--- /dev/null
+++ b/lnwallet/test_vectors_taproot.json
@@ -0,0 +1,309 @@
+{
+ "params": {
+ "seed": "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
+ "funding_amount_satoshis": 10000000,
+ "dust_limit_satoshis": 354,
+ "csv_delay": 144,
+ "commit_height": 42,
+ "nums_point": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279",
+ "keys": {
+ "local_funding_privkey": "20ae2d254ab29afd3dcbf8744a5b88d06070f55a4bd5532483a093ac4db91277",
+ "local_funding_pubkey": "03b7203dec7c13896b6ff1f58b24f84458c441720a12b5a57426397e22f0a8c78b",
+ "remote_funding_privkey": "f0c5500a9dbd7cdcd46ced7bdeb937d4dcbf90f9b9357626e7ee54ab024c3df0",
+ "remote_funding_pubkey": "02956e6845a6f346f97c5e028c0f8ab38a76b0124fd7184deab60f682b3e657fdb",
+ "local_payment_basepoint_secret": "277975b5b081a9cbc4834e066d7bb494e4fde4f7637257dd3d312a0ae7cb7754",
+ "local_payment_basepoint": "03955b6085296cbd2447a1dde0f7e273e19b83e83de1814993b1517aaf193b7f33",
+ "remote_payment_basepoint_secret": "f1cd3a5ca44b52baf4eacb849fbf06e75aace97477b8bfe31d2b814dbbb562b1",
+ "remote_payment_basepoint": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9",
+ "local_delayed_payment_basepoint_secret": "83ccf0b638c514db5ebefdc6cbf901505e2bb20edb2bb7248ce1a51523325f9b",
+ "local_delayed_payment_basepoint": "02ae68d8ff4c59864c03a42bbff6c07f9ae18047e0daa9bc40d07c410f9a0f7899",
+ "remote_revocation_basepoint_secret": "36c4175b91cff9731a63d1472b5b1c4cf3e7b688e87d5fb806b2e8350484e68d",
+ "remote_revocation_basepoint": "02c354121ef71922b5cb32fa685c08ac0014b558f96e28f383c45eb28b7da264c3",
+ "local_htlc_basepoint_secret": "786eb5024e4851bea3ddc6e40036c81b1efcf50eeed440eedefe5245bde6fc14",
+ "local_htlc_basepoint": "033ce88bf3c8333e242996964ac91ee7cd945bfe4c49668ea10f3211f3d418fbc8",
+ "remote_htlc_basepoint_secret": "51c9b6cf8279def85e3925bc8f16fc0ff100ee7b03ce7c954149ca29c834b684",
+ "remote_htlc_basepoint": "02932dfbf6737001e3c516696ae3dcd323fd91a01ce7898f7f91ab98eebacc323e",
+ "local_per_commit_secret": "037b507180b3985cea6396d6a70987cea11ccd05fde49e943a3ea0fe56ee33ed",
+ "local_per_commit_point": "02a0f5a09017c1dec2d30dd54a25dc4037fc5a2aa3832ee3c7b58f3a88a0836287",
+ "derived_local_delayed_pubkey": "0315ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05",
+ "derived_revocation_pubkey": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "derived_local_htlc_pubkey": "0271e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739e",
+ "derived_remote_htlc_pubkey": "032deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47d",
+ "derived_remote_payment_pubkey": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9"
+ }
+ },
+ "scripts": {
+ "funding": {
+ "funding_tx_hex": "02000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000018096980000000000225120d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e00000000",
+ "combined_key": "d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e",
+ "pkscript": "5120d0ebb4909d563a7ae1213fddede4ae54132fba0ef0b97ee3f8469191fecd348e"
+ },
+ "to_local": {
+ "scripts": {
+ "revocation": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c057520d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0ac",
+ "settle": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2"
+ },
+ "leaf_hashes": {
+ "revocation": "8fcd64d212bbbf1bcec2360bbf229963240d05992fc2efb482fe6dca85b9469a",
+ "settle": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62"
+ },
+ "tapscript_root": "b8b76c2e893ca785072f0d7393e35d5bd72adf8b7ff2a53538aa664378a38a36",
+ "internal_key": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279",
+ "output_key": "023e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff3",
+ "pkscript": "51203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff3"
+ },
+ "to_remote": {
+ "scripts": {
+ "settle": "20595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9ad51b2"
+ },
+ "leaf_hashes": {
+ "settle": "63ce35b16eb8f8687293d5a88c1d8ada3236843b79ca315fe9dd7c47f30f2bc9"
+ },
+ "tapscript_root": "63ce35b16eb8f8687293d5a88c1d8ada3236843b79ca315fe9dd7c47f30f2bc9",
+ "internal_key": "02dca094751109d0bd055d03565874e8276dd53e926b44e3bd1bb6bf4bc130a279",
+ "output_key": "023609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408",
+ "pkscript": "51203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408"
+ },
+ "local_anchor": {
+ "scripts": {
+ "sweep": "60b2"
+ },
+ "leaf_hashes": {
+ "sweep": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912"
+ },
+ "tapscript_root": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912",
+ "internal_key": "0315ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05",
+ "output_key": "02f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344e",
+ "pkscript": "5120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344e"
+ },
+ "remote_anchor": {
+ "scripts": {
+ "sweep": "60b2"
+ },
+ "leaf_hashes": {
+ "sweep": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912"
+ },
+ "tapscript_root": "2b88a8f3f52386d61d5b3f2d822df659c35214d7360ed05352ad7ddc1ab03912",
+ "internal_key": "03595f2ef2a51d2250a21077dbea4a7fc3ce550f10676996bf63719e2a71d1f4c9",
+ "output_key": "021249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac4",
+ "pkscript": "51201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac4"
+ },
+ "offered_htlc_local_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad51b2",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac"
+ },
+ "leaf_hashes": {
+ "success": "cd4b7ba74d132998f2bcea85f76082f5018e614c86f27f2631b6569c4914320f",
+ "timeout": "dd0bd08b3df902c399f5493a682f6c50c476c89e233ba454e89a234d2d16ffe3"
+ },
+ "tapscript_root": "f36c8bd45002c5264cfce9944211e7bc6ea974a6b90cf99a87812d18acf28a2a",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "033e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0",
+ "pkscript": "51203e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0"
+ },
+ "offered_htlc_remote_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad51b2",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac"
+ },
+ "leaf_hashes": {
+ "success": "cd4b7ba74d132998f2bcea85f76082f5018e614c86f27f2631b6569c4914320f",
+ "timeout": "dd0bd08b3df902c399f5493a682f6c50c476c89e233ba454e89a234d2d16ffe3"
+ },
+ "tapscript_root": "f36c8bd45002c5264cfce9944211e7bc6ea974a6b90cf99a87812d18acf28a2a",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "033e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0",
+ "pkscript": "51203e5c3be9f4ce7ae07c28ad5e0eb0ab617c06eeb82b8d6ef10a5bf561848df5f0"
+ },
+ "accepted_htlc_local_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739eac",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead51b26902f401b1"
+ },
+ "leaf_hashes": {
+ "success": "69192ca730d4480044ade8741b8bd0845a32880aebaf58bc6f9186f8d2be8cbf",
+ "timeout": "4da43c795365bf757ed1e9656d12ea744b4cf52b01719a3ea94e6569115623f0"
+ },
+ "tapscript_root": "1a990caa4bb0ed41ceb19e7466fcea5d9b31e3da968f348f6223201c5831d0a3",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "029aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea",
+ "pkscript": "51209aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea"
+ },
+ "accepted_htlc_remote_commit": {
+ "scripts": {
+ "success": "82012088a914b8bcb07f6344b42ab04250c86a6e8b75d3fdbbc688202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dad2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739eac",
+ "timeout": "2071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead51b26902f401b1"
+ },
+ "leaf_hashes": {
+ "success": "69192ca730d4480044ade8741b8bd0845a32880aebaf58bc6f9186f8d2be8cbf",
+ "timeout": "4da43c795365bf757ed1e9656d12ea744b4cf52b01719a3ea94e6569115623f0"
+ },
+ "tapscript_root": "1a990caa4bb0ed41ceb19e7466fcea5d9b31e3da968f348f6223201c5831d0a3",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "029aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea",
+ "pkscript": "51209aadbdd9aff986e5ea086cf53ae062972d33d0a5c7f5fb986dafec7fa6d7e6ea"
+ },
+ "second_level_htlc_success": {
+ "scripts": {
+ "success": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2"
+ },
+ "leaf_hashes": {
+ "success": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62"
+ },
+ "tapscript_root": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "02df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0",
+ "pkscript": "5120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0"
+ },
+ "second_level_htlc_timeout": {
+ "scripts": {
+ "success": "2015ec0138eb42f1ab4603042123988d53c854e89d1d87aa4dbb97a57482029c05ad029000b2"
+ },
+ "leaf_hashes": {
+ "success": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62"
+ },
+ "tapscript_root": "dbf0400e9c7c57f30b6ad0b0677e396b5a002cbf050d873c8925b966048e6a62",
+ "internal_key": "03d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0",
+ "output_key": "02df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0",
+ "pkscript": "5120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0"
+ }
+ },
+ "transactions": [
+ {
+ "name": "simple commitment tx with no HTLCs",
+ "local_balance_msat": 7000000000,
+ "remote_balance_msat": 3000000000,
+ "fee_per_kw": 15000,
+ "htlcs": null,
+ "remote_partial_sig": "3006020100020100",
+ "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780044a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344ec0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec3017440874946a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff30140a4a9eb512a2f4094efdd2c566f1f20cc8a6e2c307a4a44cc3f9fea7fa147dd7038f1b048aa43fa0b4009175c1c37c37b96c01058541f9e1b61110fce4e831d9f55dc1920",
+ "htlc_descs": null
+ },
+ {
+ "name": "commitment tx with five HTLCs untrimmed",
+ "local_balance_msat": 6988000000,
+ "remote_balance_msat": 3000000000,
+ "fee_per_kw": 644,
+ "htlcs": [
+ {
+ "incoming": true,
+ "amount_msat": 1000000,
+ "expiry": 500,
+ "preimage": "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 2000000,
+ "expiry": 501,
+ "preimage": "0101010101010101010101010101010101010101010101010101010101010101"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 2000000,
+ "expiry": 502,
+ "preimage": "0202020202020202020202020202020202020202020202020202020202020202"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 3000000,
+ "expiry": 503,
+ "preimage": "0303030303030303030303030303030303030303030303030303030303030303"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 4000000,
+ "expiry": 504,
+ "preimage": "0404040404040404040404040404040404040404040404040404040404040404"
+ }
+ ],
+ "remote_partial_sig": "3006020100020100",
+ "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780094a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344ee8030000000000002251209ce82cd1b1f6f975049d58019a7145a3ec9680079969cf929d7d2c4bc9b30637d0070000000000002251208937f8afbc80cf4ba773f1adc3d63ea26259f80f5a3ba622211906d2e7e6e23dd007000000000000225120bf9ae94dda9b5b88485cc67a966ec946b237d19626916dee034b789ebd7fd5fcb80b0000000000002251208fe2e1306e414e896dfd879475b5c1a6a01d4e79b32c0544aa185ccb73c392aaa00f000000000000225120d93389ba5cdde8570d3ba73487ff7fc9f8c3816645009e42110fe5239f5a3e62c0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec30174408b3996a00000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff301409dfe3b178022d975e4b86bd1f04bccfc7576363dbaf58f2ac682136ad89cbeb1a1d07eca1e0bc547b5c5c1133214565e5dfdc230bc7d4736aa7e1be3fb8269d355dc1920",
+ "htlc_descs": [
+ {
+ "remote_partial_sig_hex": "ba244e80d7043172804bd1b8c8fc26328b4ca0379611892c9d311ac97802af6849541cac18f51071d9aa57dceb7bdfa72544cbab6527e7a47bf1e9507dc51683",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f02000000000100000001e803000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541ba244e80d7043172804bd1b8c8fc26328b4ca0379611892c9d311ac97802af6849541cac18f51071d9aa57dceb7bdfa72544cbab6527e7a47bf1e9507dc516838340f75d8a3d94db2d5aaefc4949b3c10fe2163ebd953b636b6488cc2fe9a988bd6b966fc0904557044510b6650bc624bdfa0298e3bb18c4f403996e0a46bdf37b720020000000000000000000000000000000000000000000000000000000000000000041c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0e5e8fd071b9ade6367122afbd8acacc1a6727ddb6d478612af30827590027e0300000000"
+ },
+ {
+ "remote_partial_sig_hex": "790f2e9c117c83c3a0c207dfa9cbfe8a955717854e96e966f428dbce816a408c3434839e522031064f0cb3ebb15c8c030d20dc5859a0c99a78a5f795d32b693f",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f03000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541790f2e9c117c83c3a0c207dfa9cbfe8a955717854e96e966f428dbce816a408c3434839e522031064f0cb3ebb15c8c030d20dc5859a0c99a78a5f795d32b693f8340f417b7b78b52f00a2c76586c3555b66cc87207fdf2995db255d232d9bfdad33bc1e8ee80898c4c2e6c9856158999abe762bfd6d17933dec9b6d522dc3b9fec3b0020000000000000000000000000000000000000000000000000000000000000000041c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0127d1790461eff920f14ba7cff2093c44b8a83e6f0a959fa60e04cf8c435cf4b00000000"
+ },
+ {
+ "remote_partial_sig_hex": "aa6d10a611d9d34fb22e02aa9d1cce2b85cd10f16651fa67e415a787e0a9d2f14eb5845c4e1990af4dac7d4d865d3da7afd049cbded334f46f174e8474a16e12",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f04000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba004410967aa040669318987029fba55410f9a43c4dd57da9e3a60e74b83a35abea76e813fbbdaa8baae38e096a6b7718d75838cb33e264814678823f55ee6fb9cdd0f8340c52f017d42f140311c33a8bb94329f77f40245880b6ecc736735056c581fc7ceaad686deb2fe4359f257defa175fc4ce07558825500c1ef8d25ead5ac495d5b0442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a040b30263c4d7cd1fa6544e8bc8cd9efe857d7b5fd691c958936c3a2e0df2232ef6010000"
+ },
+ {
+ "remote_partial_sig_hex": "81dd0918b0e01f4c1f701a5689f3c5c076bf71d4f365f21b67707527c9e71fe40e0c6fee2f3e46ee297d542e883f5ba5004b53b04cda060b6f99d3a8b2c3f488",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f05000000000100000001b80b000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00441aa6d10a611d9d34fb22e02aa9d1cce2b85cd10f16651fa67e415a787e0a9d2f14eb5845c4e1990af4dac7d4d865d3da7afd049cbded334f46f174e8474a16e1283403c03b026ef105fe37ec3710fe2b81844531cfb080ad3b7290c7045c6ffa29e3d60a680ce9f539e4b89b37e5818545efbf2a94cb0c1b893c4a4a6b65703b137b9442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a064c44563d1bd58fa25c5c3ca7303c75849b6b3d91bf2e28f27068db4319b4c2ff7010000"
+ },
+ {
+ "remote_partial_sig_hex": "0967aa040669318987029fba55410f9a43c4dd57da9e3a60e74b83a35abea76e813fbbdaa8baae38e096a6b7718d75838cb33e264814678823f55ee6fb9cdd0f",
+ "resolution_tx_hex": "02000000000101ec4c0a34c981864f9badcb8383bbe42ec6b32e68c2aa1a7c7c2e8422adde673f06000000000100000001a00f000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba0054181dd0918b0e01f4c1f701a5689f3c5c076bf71d4f365f21b67707527c9e71fe40e0c6fee2f3e46ee297d542e883f5ba5004b53b04cda060b6f99d3a8b2c3f48883409c3edb75f915ab9573cd44d9500e630fb6c2388c21733be54bce3824e66df526bf73df2206ccb8fbd125a4f0025e5bae8ecd7ea586d62ff341cead78c0e2528b0020000000000000000000000000000000000000000000000000000000000000000041c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a06c3390c812b2596986592f02c7f22e4f857fb553805ff9ac1c2bda361c47c3fb00000000"
+ }
+ ]
+ },
+ {
+ "name": "commitment tx with some HTLCs trimmed",
+ "local_balance_msat": 6988000000,
+ "remote_balance_msat": 3000000000,
+ "fee_per_kw": 100000,
+ "dust_limit_satoshis": 546,
+ "htlcs": [
+ {
+ "incoming": true,
+ "amount_msat": 1000000,
+ "expiry": 500,
+ "preimage": "0000000000000000000000000000000000000000000000000000000000000000"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 2000000,
+ "expiry": 501,
+ "preimage": "0101010101010101010101010101010101010101010101010101010101010101"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 2000000,
+ "expiry": 502,
+ "preimage": "0202020202020202020202020202020202020202020202020202020202020202"
+ },
+ {
+ "incoming": false,
+ "amount_msat": 3000000,
+ "expiry": 503,
+ "preimage": "0303030303030303030303030303030303030303030303030303030303030303"
+ },
+ {
+ "incoming": true,
+ "amount_msat": 4000000,
+ "expiry": 504,
+ "preimage": "0404040404040404040404040404040404040404040404040404040404040404"
+ }
+ ],
+ "remote_partial_sig": "3006020100020100",
+ "expected_commitment_tx_hex": "020000000001015474cba49124ab0c4327c244bb2907059585c4af3fa5f3469701534120fec0170000000000c5fe1780094a010000000000002251201249c50576fdf914caa14f9221370b986df520bdbc73f57d5056a86ee03e5ac44a01000000000000225120f67ab012701705f3203d132f909a6810ef18c5da4c11d986cb50818803b8344ee8030000000000002251209ce82cd1b1f6f975049d58019a7145a3ec9680079969cf929d7d2c4bc9b30637d0070000000000002251208937f8afbc80cf4ba773f1adc3d63ea26259f80f5a3ba622211906d2e7e6e23dd007000000000000225120bf9ae94dda9b5b88485cc67a966ec946b237d19626916dee034b789ebd7fd5fcb80b0000000000002251208fe2e1306e414e896dfd879475b5c1a6a01d4e79b32c0544aa185ccb73c392aaa00f000000000000225120d93389ba5cdde8570d3ba73487ff7fc9f8c3816645009e42110fe5239f5a3e62c0c62d00000000002251203609bb705034e5629aa6ec05c5ca906ac89ac08b34c4583c259521ec301744083cd46700000000002251203e1fcbbd06c8a7414704612c72be9834a75d86ed85b29f0ef0c52e1950afaff30140c06828b729b180dc98cefecaa42f05215016a0b5a3232ce86773b5fc08bedc1112ff1ceddb9fa7dd0740f1641d80114630017badc95f32480a15e435ed569d6555dc1920",
+ "htlc_descs": [
+ {
+ "remote_partial_sig_hex": "f96a7376f50c3a2ee763bbeec232798458c50a8a6fab0333275c169f113d6ae8a5b37b61a9fc2eecbd0a0493418a1a8dd48fd5d161a410a57970f72a97fcee6f",
+ "resolution_tx_hex": "0200000000010171d9133e6692c6a995317b4d388f613d84c99195362ad8742e8f0c3bc7dda51502000000000100000001e803000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541f96a7376f50c3a2ee763bbeec232798458c50a8a6fab0333275c169f113d6ae8a5b37b61a9fc2eecbd0a0493418a1a8dd48fd5d161a410a57970f72a97fcee6f834036b2363b0f0f478a564a88052a9a7df89d00d18a449bb9f17c527a4049f6abd4f7e851b7294360613a01d1480726617cf754b3f26a28d45c44ea1abe7b317aa20020000000000000000000000000000000000000000000000000000000000000000041c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0e5e8fd071b9ade6367122afbd8acacc1a6727ddb6d478612af30827590027e0300000000"
+ },
+ {
+ "remote_partial_sig_hex": "1e82f5cfb5d2a87418d1ca3cc3abf9f18198692aba1a31ced763a8a9ed0ecd4252fd2996b89089aa69958cf2f41dda83d04d8d5644443de32b6e614105e833fc",
+ "resolution_tx_hex": "0200000000010171d9133e6692c6a995317b4d388f613d84c99195362ad8742e8f0c3bc7dda51503000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba005411e82f5cfb5d2a87418d1ca3cc3abf9f18198692aba1a31ced763a8a9ed0ecd4252fd2996b89089aa69958cf2f41dda83d04d8d5644443de32b6e614105e833fc83402ff61ab8f640fe9f9ef159b0e2a9fc17d0b4d15da59eed6b44fdff55e80d3b3d45aea7362cae2dfb2ae1c92a7d87790fce1e8cc157edadb9882823bfe299bca90020000000000000000000000000000000000000000000000000000000000000000041c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a0127d1790461eff920f14ba7cff2093c44b8a83e6f0a959fa60e04cf8c435cf4b00000000"
+ },
+ {
+ "remote_partial_sig_hex": "d1fc7f73da9f09780568db36b9d1d5b0555be656ac2913beeffc1a48105a9e7aefe36d83cfeba66df106f65e81a5d5bae078d3f103da7e855b9f244d3a9df538",
+ "resolution_tx_hex": "0200000000010171d9133e6692c6a995317b4d388f613d84c99195362ad8742e8f0c3bc7dda51504000000000100000001d007000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba004417fe6ed9d80a0d3e7c315ac36c6ec234f510515bd2b1812b54da0e93a75a5ce4fce67839dfebca8746d94d343e2610bc92c22c9f4bcf637de461dd6fa0acb768f83407635f668f41362106a98c94ff0cdf66f9a8a57a141240096a1d5bb5a8531e91381a24b48c2c86010cb68891a17aca616e6e476dab53890ef63374e2cfa0929d9442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a040b30263c4d7cd1fa6544e8bc8cd9efe857d7b5fd691c958936c3a2e0df2232ef6010000"
+ },
+ {
+ "remote_partial_sig_hex": "754bcd14983ecb5a864ce03f1f1a6f986e7d23bd0fa92505929a5595cf56199a3a520cd56bd15f6b28f78f148e3eff5df7a3336a9f05396b5f4a67ac3e2ed747",
+ "resolution_tx_hex": "0200000000010171d9133e6692c6a995317b4d388f613d84c99195362ad8742e8f0c3bc7dda51505000000000100000001b80b000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00441d1fc7f73da9f09780568db36b9d1d5b0555be656ac2913beeffc1a48105a9e7aefe36d83cfeba66df106f65e81a5d5bae078d3f103da7e855b9f244d3a9df53883407f6bd7945f87d3a6a6f3700d756757e2cd03a897d8e01145fa2217d2741f3422776ef1f8b337e9f3f78a29060bedea63a29e574764a49a25acef1d8fe0b50370442071e82ef65d5c667159036bfcf662cac2f6c41e38323d148bbbd00fdcd923739ead202deba21cf03c42362c9f912094f62ba045a040a2060882ba1ed3abf1f664a47dac41c0d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a064c44563d1bd58fa25c5c3ca7303c75849b6b3d91bf2e28f27068db4319b4c2ff7010000"
+ },
+ {
+ "remote_partial_sig_hex": "7fe6ed9d80a0d3e7c315ac36c6ec234f510515bd2b1812b54da0e93a75a5ce4fce67839dfebca8746d94d343e2610bc92c22c9f4bcf637de461dd6fa0acb768f",
+ "resolution_tx_hex": "0200000000010171d9133e6692c6a995317b4d388f613d84c99195362ad8742e8f0c3bc7dda51506000000000100000001a00f000000000000225120df20bcec43daa75161f7d013254e401812e0fee8bc3369220b6a33672fc18ba00541754bcd14983ecb5a864ce03f1f1a6f986e7d23bd0fa92505929a5595cf56199a3a520cd56bd15f6b28f78f148e3eff5df7a3336a9f05396b5f4a67ac3e2ed747834016f15c4e15363903022afa572110909661a5b6da439507d49980585a7a2c4a23c91cae4a9c20ebc5a4051d47ed7e9a5c087f31f981652b5bc4e4018bb58e1f9f0020000000000000000000000000000000000000000000000000000000000000000041c1d4c77088d346bce67c13bbbf82ca112588f4b1c9595a1f8af3be9b2f95a109a06c3390c812b2596986592f02c7f22e4f857fb553805ff9ac1c2bda361c47c3fb00000000"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.