package tx import ( "encoding/hex" "encoding/json" "os" "strings" "testing" ) // bip143Fixture is a spend the rig node signed: an unsigned transaction, the prevout it // spends, and the finished signed transaction. It is the end-to-end anchor for this package — // the serialiser has to rebuild the unsigned bytes exactly, and the signed form has to be the // one the node produced for those bytes. type bip143Fixture struct { Secret string `json:"secret"` Pubkey string `json:"pubkey"` ScriptPubKey string `json:"scriptPubKey"` Sequence uint32 `json:"sequence"` Prevout struct { TxID string `json:"txid"` Vout uint32 `json:"vout"` Amount uint64 `json:"amount"` } `json:"prevout"` Unsigned string `json:"unsigned"` Signed string `json:"signed"` Complete bool `json:"complete"` } // loadFixture reads it. func loadFixture(t *testing.T) bip143Fixture { t.Helper() raw, err := os.ReadFile("testdata/bip143.json") if err != nil { t.Fatal(err) } var fixture bip143Fixture if err := json.Unmarshal(raw, &fixture); err != nil { t.Fatal(err) } return fixture } // TestTheFixtureRebuilds: the package rebuilds the node's unsigned transaction byte for byte // from the fields a wallet holds, which is what makes the fixture usable for the sighash. func TestTheFixtureRebuilds(t *testing.T) { fixture := loadFixture(t) hash, err := hex.DecodeString(fixture.Prevout.TxID) if err != nil { t.Fatal(err) } var outpoint [32]byte copy(outpoint[:], hash) payScript, err := hex.DecodeString(fixture.PayScript(t)) if err != nil { t.Fatal(err) } transaction := New() transaction.Version = 2 transaction.AddInput(Outpoint{Hash: outpoint, Index: fixture.Prevout.Vout}, fixture.Sequence) transaction.AddOutput(50000, payScript) raw, err := transaction.Serialize() if err != nil { t.Fatal(err) } if got := hex.EncodeToString(raw); got != fixture.Unsigned { t.Fatalf("rebuilt\n%s\nwant\n%s", got, fixture.Unsigned) } } // PayScript reads the output script out of the fixture's own unsigned transaction, so the test // does not need a second copy of it. func (f bip143Fixture) PayScript(t *testing.T) string { t.Helper() raw, err := hex.DecodeString(f.Unsigned) if err != nil { t.Fatal(err) } // The output script is the last varint-prefixed field before the locktime; for this // fixture it is a 22-byte P2WPKH script, so it is the 22 bytes that follow the 0x16 length // in the output section. index := strings.LastIndex(f.Unsigned, "160014") if index < 0 { t.Fatal("the fixture has no P2WPKH output") } start := index / 2 return hex.EncodeToString(raw[start+1 : start+23]) } // TestTheSignedFormIsPinned: the node's signed transaction has the marker, the flag, and this // key in its witness. func TestTheSignedFormIsPinned(t *testing.T) { fixture := loadFixture(t) if !fixture.Complete { t.Fatal("the fixture is not a complete signature") } if !strings.HasPrefix(fixture.Signed, "020000000001") { t.Fatalf("the signed form starts %s", fixture.Signed[:12]) } if !strings.Contains(fixture.Signed, fixture.Pubkey) { t.Fatal("the witness does not carry the signing key") } }