From 51c43faba873b8b5522ae6e3c229a75d4cdc2e0e Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sun, 25 Aug 2024 15:09:36 +0200 Subject: [PATCH 1/6] silentpayments: add test vectors, implement addr struct --- btcutil/silentpayments/address.go | 321 ++ btcutil/silentpayments/address_test.go | 69 + btcutil/silentpayments/test_vectors_test.go | 121 + .../silentpayments/testdata/test-vectors.json | 2760 +++++++++++++++++ 4 files changed, 3271 insertions(+) create mode 100644 btcutil/silentpayments/address.go create mode 100644 btcutil/silentpayments/address_test.go create mode 100644 btcutil/silentpayments/test_vectors_test.go create mode 100644 btcutil/silentpayments/testdata/test-vectors.json diff --git a/btcutil/silentpayments/address.go b/btcutil/silentpayments/address.go new file mode 100644 index 0000000000..47a9842177 --- /dev/null +++ b/btcutil/silentpayments/address.go @@ -0,0 +1,321 @@ +package silentpayments + +import ( + "encoding/binary" + "errors" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcutil/bech32" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +// Version is the address version. +type Version byte + +const ( + // Version0 is the address version for the first version of the address + // format. + Version0 Version = 0 + + // MainNetHRP is the human-readable part for mainnet addresses. + MainNetHRP = "sp" + + // TestNetHRP is the human-readable part for testnet addresses. + TestNetHRP = "tsp" + + // pubKeyLength is the length of a compressed public key. + pubKeyLength = btcec.PubKeyBytesLenCompressed +) + +var ( + // ErrUnsupportedNet is returned when trying to create an address for an + // unsupported network. + ErrUnsupportedNet = errors.New("unsupported network") + + // TagBIP0352Label is the BIP-0352 tag for a label. + TagBIP0352Label = []byte("BIP0352/Label") +) + +// Address is a struct that holds the public keys and tweak used to generate a +// silent payment address. +type Address struct { + // Hrp is the human-readable part for the address. + Hrp string + + // Version is the address version. + Version Version + + // ScanKey is the public scan key. + ScanKey btcec.PublicKey + + // SpendKey is the public spend key. + SpendKey btcec.PublicKey + + // LabelTweak is an optional tweak to apply to the spend key. This is + // calculated using: + // tweak = TaggedHash_BIP0352/Label(scanPrivKey || m) + LabelTweak *btcec.ModNScalar +} + +// NewAddress creates a new silent payment address. +func NewAddress(hrp string, scanKey, spendKey btcec.PublicKey, + labelTweak *btcec.ModNScalar) *Address { + + return &Address{ + Version: Version0, + Hrp: hrp, + ScanKey: scanKey, + SpendKey: spendKey, + LabelTweak: labelTweak, + } +} + +// NewAddressForNet creates a new silent payment address for a given network. +func NewAddressForNet(params *chaincfg.Params, scanKey, + spendKey btcec.PublicKey, labelTweak *btcec.ModNScalar) (*Address, + error) { + + var hrp string + switch params.Name { + case chaincfg.MainNetParams.Name: + hrp = MainNetHRP + + case chaincfg.TestNet3Params.Name, + chaincfg.SigNetParams.Name, + chaincfg.RegressionNetParams.Name: + + hrp = TestNetHRP + + default: + return nil, ErrUnsupportedNet + } + + return NewAddress(hrp, scanKey, spendKey, labelTweak), nil +} + +// String returns the address as a string. +func (a *Address) String() string { + return a.EncodeAddress() +} + +// Equal returns whether the address is equal to another address. +func (a *Address) Equal(other *Address) bool { + if a.Hrp != other.Hrp { + return false + } + + if a.Version != other.Version { + return false + } + + if !a.ScanKey.IsEqual(&other.ScanKey) { + return false + } + + if !a.SpendKey.IsEqual(&other.SpendKey) { + return false + } + + if a.LabelTweak == nil && other.LabelTweak != nil { + return false + } + + if a.LabelTweak != nil && other.LabelTweak == nil { + return false + } + + if a.LabelTweak != nil && other.LabelTweak != nil { + if !a.LabelTweak.Equals(other.LabelTweak) { + return false + } + } + + return true +} + +// TweakedSpendKey returns the spend key with the label tweak applied to it (if +// it exists). +func (a *Address) TweakedSpendKey() *btcec.PublicKey { + // Create a copy, just in case. + spendKey := a.SpendKey + + // If there is no tweak, just return the bare spend key. + if a.LabelTweak == nil { + return &spendKey + } + + // Apply the tweak to the spend key now and return it. + var bSpend, bTweak, result btcec.JacobianPoint + spendKey.AsJacobian(&bSpend) + + // Calculate B_tweak = tweak * G. + btcec.ScalarBaseMultNonConst(a.LabelTweak, &bTweak) + + // Calculate result = B_spend + B_tweak. + btcec.AddNonConst(&bSpend, &bTweak, &result) + + result.ToAffine() + return btcec.NewPublicKey(&result.X, &result.Y) +} + +// EncodeAddress encodes the address into a bech32m string. +func (a *Address) EncodeAddress() string { + // Copy the scan key into the payload unchanged. + var payload [2 * pubKeyLength]byte + copy(payload[:pubKeyLength], a.ScanKey.SerializeCompressed()) + copy(payload[pubKeyLength:], a.TweakedSpendKey().SerializeCompressed()) + + // Group the address bytes into 5 bit groups, as this is what is used to + // encode each character in the address string. + converted, err := bech32.ConvertBits(payload[:], 8, 5, true) + if err != nil { + return "" + } + + // Concatenate the address version and program, and encode the resulting + // bytes using bech32m encoding. + combined := make([]byte, len(converted)+1) + combined[0] = byte(a.Version) + copy(combined[1:], converted) + + addr, err := bech32.EncodeM(a.Hrp, combined) + if err != nil { + return "" + } + + return addr +} + +// IsForNet returns whether the address is for the given network. +func (a *Address) IsForNet(params *chaincfg.Params) bool { + switch params.Name { + case chaincfg.MainNetParams.Name: + return a.Hrp == MainNetHRP + + default: + return a.Hrp == TestNetHRP + } +} + +// DecodeAddress decodes a bech32m string into an address. +func DecodeAddress(addr string) (*Address, error) { + // Spec: BIP173 imposes a 90-character limit for Bech32 segwit addresses + // and limits versions to 0 through 16, whereas a silent payment address + // requires at least 117 characters and allows versions up to 31. + // Additionally, since higher versions may add to the data field, it is + // recommended implementations use a limit of 1023 characters (see + // BIP173: Checksum design for more details). + if len(addr) > 1023 { + return nil, bech32.ErrInvalidLength(len(addr)) + } + + hrp, data, bechVersion, err := bech32.DecodeNoLimitWithVersion(addr) + if err != nil { + return nil, err + } + + if bechVersion != bech32.VersionM { + return nil, errors.New("invalid bech32 version") + } + + regrouped, err := bech32.ConvertBits(data[1:], 5, 8, false) + if err != nil { + return nil, err + } + + // Spec: If the receiver's silent payment address version is: + // v0: check that the data part is exactly 66-bytes. Otherwise, fail. + // v1 through v30: read the first 66-bytes of the data part and + // discard the remaining bytes. + // v31: fail. + addrVersion := data[0] + switch { + case addrVersion == 0: + // Version zero addresses must be exactly 66 bytes. + if len(regrouped) != 2*pubKeyLength { + return nil, errors.New("invalid data length") + } + + case addrVersion > 30: + // Version 31 and above are not supported. + return nil, fmt.Errorf("invalid silent address version: %v", + addrVersion) + + default: + // Any version between 1 and 29 is allowed, but we only read the + // first 66 bytes. + if len(regrouped) < 2*pubKeyLength { + return nil, errors.New("invalid data length") + } + } + + scanKey, err := btcec.ParsePubKey(regrouped[:pubKeyLength]) + if err != nil { + return nil, fmt.Errorf("error parsing scan key: %w", err) + } + + spendKey, err := btcec.ParsePubKey( + regrouped[pubKeyLength : 2*pubKeyLength], + ) + if err != nil { + return nil, fmt.Errorf("error parsing spend key: %w", err) + } + + return NewAddress(hrp, *scanKey, *spendKey, nil), nil +} + +// ParseAddress parses the scan and spend keys into an address for the given +// network. +func ParseAddress(params *chaincfg.Params, scanKey, + spendKey []byte) (*Address, error) { + + scanPubKey, err := btcec.ParsePubKey(scanKey) + if err != nil { + return nil, err + } + + spendPubKey, err := btcec.ParsePubKey(spendKey) + if err != nil { + return nil, err + } + + return NewAddressForNet(params, *scanPubKey, *spendPubKey, nil) +} + +// LabelTweak calculates the label tweak for a given scan private key and m +// integer value. +func LabelTweak(scanPrivKey *btcec.PrivateKey, m uint32) *btcec.ModNScalar { + var data [36]byte + copy(data[:], scanPrivKey.Serialize()) + + binary.BigEndian.PutUint32(data[32:], m) + + taggedHash := chainhash.TaggedHash(TagBIP0352Label, data[:]) + + var scalar btcec.ModNScalar + scalar.SetByteSlice(taggedHash[:]) + + return &scalar +} + +// GroupByScanKey groups a list of addresses by their scan key. +func GroupByScanKey(recipients []Address) [][]Address { + groups := make(map[[33]byte][]Address) + for _, recipient := range recipients { + var key [33]byte + copy(key[:], recipient.ScanKey.SerializeCompressed()) + + groups[key] = append(groups[key], recipient) + } + + idx := 0 + grouped := make([][]Address, len(groups)) + for _, group := range groups { + grouped[idx] = group + idx++ + } + + return grouped +} diff --git a/btcutil/silentpayments/address_test.go b/btcutil/silentpayments/address_test.go new file mode 100644 index 0000000000..5b5575f22a --- /dev/null +++ b/btcutil/silentpayments/address_test.go @@ -0,0 +1,69 @@ +package silentpayments + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +// TestAddresses tests the generation of silent payment addresses. +func TestAddresses(t *testing.T) { + vectors, err := ReadTestVectors() + require.NoError(t, err) + + for _, vector := range vectors { + vector := vector + t.Run(vector.Comment, func(tt *testing.T) { + runAddressesTest(tt, vector) + }) + } +} + +// runAddressesTest tests the generation and parsing of silent payment +// addresses. +func runAddressesTest(t *testing.T, vector *TestVector) { + // We first check that we can create the receiving address correctly. + for _, receiving := range vector.Receiving { + mat := receiving.Given.KeyMaterial + + scanPrivKey, spendPrivKey, err := mat.Parse() + require.NoError(t, err) + + scanPubKey := scanPrivKey.PubKey() + spendPubKey := spendPrivKey.PubKey() + + addrNoLabel := NewAddress( + MainNetHRP, *scanPubKey, *spendPubKey, nil, + ) + + require.Equal( + t, receiving.Expected.Addresses[0], + addrNoLabel.EncodeAddress(), + ) + + for idx, label := range receiving.Given.Labels { + tweak := LabelTweak(scanPrivKey, label) + + addrWithLabel := NewAddress( + MainNetHRP, *scanPubKey, *spendPubKey, tweak, + ) + + require.Equalf( + t, receiving.Expected.Addresses[idx+1], + addrWithLabel.EncodeAddress(), + "with label %d", label, + ) + } + } + + // We then also check that we can successfully parse all sending + // addresses. + for _, sending := range vector.Sending { + for _, recipient := range sending.Given.Recipients { + addr, err := DecodeAddress(recipient) + require.NoError(t, err) + + require.Equal(t, MainNetHRP, addr.Hrp) + } + } +} diff --git a/btcutil/silentpayments/test_vectors_test.go b/btcutil/silentpayments/test_vectors_test.go new file mode 100644 index 0000000000..4cf9b80f4b --- /dev/null +++ b/btcutil/silentpayments/test_vectors_test.go @@ -0,0 +1,121 @@ +package silentpayments + +import ( + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + + "github.com/btcsuite/btcd/btcec/v2" +) + +var ( + testVectorFileName = "test-vectors.json" + + testdataDir = "testdata" +) + +type TestVector struct { + Comment string `json:"comment"` + + Sending []*Sending `json:"sending"` + + Receiving []*Receiving `json:"receiving"` +} + +type Sending struct { + Given *SendingGiven `json:"given"` + + Expected *SendingExpected `json:"expected"` +} + +type SendingGiven struct { + Vin []*VIn `json:"vin"` + + Recipients []string `json:"recipients"` +} + +type VIn struct { + Txid string `json:"txid"` + Vout uint32 `json:"vout"` + ScriptSig string `json:"scriptSig"` + TxInWitness string `json:"txinwitness"` + PrevOut *PrevOut `json:"prevout"` + PrivateKey string `json:"private_key"` +} + +type PrevOut struct { + ScriptPubKey *ScriptPubKey `json:"scriptPubKey"` +} + +type ScriptPubKey struct { + Hex string `json:"hex,omitempty"` +} + +type SendingExpected struct { + Outputs [][]string `json:"outputs"` + NumOutputs uint32 `json:"n_outputs"` +} + +type Receiving struct { + Given *ReceivingGiven `json:"given"` + Expected *ReceivingExpected `json:"expected"` +} + +type ReceivingGiven struct { + Vin []*VIn `json:"vin"` + Outputs []string `json:"outputs"` + KeyMaterial *KeyMaterial `json:"key_material"` + Labels []uint32 `json:"labels"` +} + +type KeyMaterial struct { + ScanPrivKey string `json:"scan_priv_key"` + SpendPrivKey string `json:"spend_priv_key"` +} + +func (k *KeyMaterial) Parse() (*btcec.PrivateKey, *btcec.PrivateKey, error) { + scanPrivKeyBytes, err := hex.DecodeString(k.ScanPrivKey) + if err != nil { + return nil, nil, err + } + + spendPrivKeyBytes, err := hex.DecodeString(k.SpendPrivKey) + if err != nil { + return nil, nil, err + } + + scanPrivKey, _ := btcec.PrivKeyFromBytes(scanPrivKeyBytes) + spendPrivKey, _ := btcec.PrivKeyFromBytes(spendPrivKeyBytes) + + return scanPrivKey, spendPrivKey, nil +} + +type ReceivingExpected struct { + Addresses []string `json:"addresses"` + Outputs []*Output `json:"outputs"` + NumOutputs uint32 `json:"n_outputs"` +} + +type Output struct { + PrivKeyTweak string `json:"priv_key_tweak"` + PubKey string `json:"pub_key"` + Signature string `json:"signature"` +} + +// ReadTestVectors reads the test vectors from the test vector file. +func ReadTestVectors() ([]*TestVector, error) { + // Open the test vector file. + file, err := os.Open(filepath.Join(testdataDir, testVectorFileName)) + if err != nil { + return nil, err + } + + // Decode the test vectors. + var testVectors []*TestVector + if err := json.NewDecoder(file).Decode(&testVectors); err != nil { + return nil, err + } + + return testVectors, nil +} diff --git a/btcutil/silentpayments/testdata/test-vectors.json b/btcutil/silentpayments/testdata/test-vectors.json new file mode 100644 index 0000000000..4aa0e79f6e --- /dev/null +++ b/btcutil/silentpayments/testdata/test-vectors.json @@ -0,0 +1,2760 @@ +[ + { + "comment": "Simple send: two inputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ] + } + } + ] + }, + { + "comment": "Simple send: two inputs, order reversed", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "signature": "74f85b856337fbe837643b86f462118159f93ac4acc2671522f27e8f67b079959195ccc7a5dbee396d2909f5d680d6e30cda7359aa2755822509b70d6b0687a1" + } + ] + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 3, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 7, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 3, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 7, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "4851455bfbe1ab4f80156570aa45063201aa5c9e1b1dcd29f0f8c33d10bf77ae", + "pub_key": "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", + "signature": "10332eea808b6a13f70059a8a73195808db782012907f5ba32b6eae66a2f66b4f65147e2b968a1678c5f73d57d5d195dbaf667b606ff80c8490eac1f3b710657" + } + ] + } + } + ] + }, + { + "comment": "Simple send: two inputs from the same transaction, order reversed", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 7, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 3, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 7, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 3, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "ab0c9b87181bf527879f48db9f14a02233619b986f8e8f2d5d408ce68a709f51", + "pub_key": "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", + "signature": "398a9790865791a9db41a8015afad3a47d60fec5086c50557806a49a1bc038808632b8fe679a7bb65fc6b455be994502eed849f1da3729cd948fc7be73d67295" + } + ] + } + } + ] + }, + { + "comment": "Outpoint ordering byte-lexicographically vs. vout-integer", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 256, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + }, + "private_key": "93f5ed907ad5b2bdbbdcb5d9116ebc0a4e1f92f910d5260237fa45a9408aad16" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 256, + "scriptSig": "48304602210086783ded73e961037e77d49d9deee4edc2b23136e9728d56e4491c80015c3a63022100fda4c0f21ea18de29edbce57f7134d613e044ee150a89e2e64700de2d4e83d4e2103bd85685d03d111699b15d046319febe77f8de5286e9e512703cdee1bf3be3792", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914d9317c66f54ff0a152ec50b1d19c25be50c8e15988ac" + } + } + } + ], + "outputs": [ + "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "c8ac0292997b5bca98b3ebd99a57e253071137550f270452cd3df8a3e2266d36", + "pub_key": "a85ef8701394b517a4b35217c4bd37ac01ebeed4b008f8d0879f9e09ba95319c", + "signature": "c036ee38bfe46aba03234339ae7219b31b824b52ef9d5ce05810a0d6f62330dedc2b55652578aa5bdabf930fae941acd839d5a66f8fce7caa9710ccb446bddd1" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: multiple UTXOs from the same public key", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + } + ], + "outputs": [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f032695e2636619efa523fffaa9ef93c8802299181fd0461913c1b8daf9784cd", + "pub_key": "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", + "signature": "f238386c5d5e5444f8d2c75aabbcb28c346f208c76f60823f5de3b67b79e0ec72ea5de2d7caec314e0971d3454f122dda342b3eede01b3857e83654e36b25f76" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot only inputs with even y-values", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + }, + "private_key": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + } + ], + "outputs": [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "3fb9ce5ce1746ced103c8ed254e81f6690764637ddbc876ec1f9b3ddab776b03", + "pub_key": "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", + "signature": "c5acd25a8f021a4192f93bc34403fd8b76484613466336fb259c72d04c169824f2690ca34e96cee86b69f376c8377003268fda56feeb1b873e5783d7e19bcca5" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot only with mixed even/odd y-values", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + }, + "private_key": "1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + } + } + ], + "outputs": [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "f5382508609771068ed079b24e1f72e4a17ee6d1c979066bf1d4e2a5676f09d4", + "pub_key": "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", + "signature": "ff65833b8fd1ed3ef9d0443b4f702b45a3f2dd457ba247687e8207745c3be9d2bdad0ab3f07118f8b2efc6a04b95f7b3e218daf8a64137ec91bd2fc67fc137a5" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot input with even y-value and non-taproot input", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + } + } + ], + "outputs": [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "b40017865c79b1fcbed68896791be93186d08f47e416b289b8c063777e14e8df", + "pub_key": "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", + "signature": "d1edeea28cf1033bcb3d89376cabaaaa2886cbd8fda112b5c61cc90a4e7f1878bdd62180b07d1dfc8ffee1863c525a0c7b5bcd413183282cfda756cb65787266" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot input with odd y-value and non-taproot input", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + }, + "private_key": "1d37787c2b7116ee983e9f9c13269df29091b391c04db94239e0d2bc2182c3bf" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "01400a4d0dca6293f40499394d7eefe14a1de11e0e3454f51de2e802592abf5ee549042a1b1a8fb2e149ee9dd3f086c1b69b2f182565ab6ecf599b1ec9ebadfda6c5", + "prevout": { + "scriptPubKey": { + "hex": "51208c8d23d4764feffcd5e72e380802540fa0f88e3d62ad5e0b47955f74d7b283c4" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "463044021f24e010c6e475814740ba24c8cf9362c4db1276b7f46a7b1e63473159a80ec30221008198e8ece7b7f88e6c6cc6bb8c86f9f00b7458222a8c91addf6e1577bcf7697e2103e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9148cbc7dfe44f1579bff3340bbef1eddeaeb1fc97788ac" + } + } + } + ], + "outputs": [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "a2f9dd05d1d398347c885d9c61a64d18a264de6d49cea4326bafc2791d627fa7", + "pub_key": "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", + "signature": "96038ad233d8befe342573a6e54828d863471fb2afbad575cc65271a2a649480ea14912b6abbd3fbf92efc1928c036f6e3eef927105af4ec1dd57cb909f360b8" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "d97e442d110c0bdd31161a7bb6e7862e038d02a09b1484dfbb463f2e0f7c9230", + "pub_key": "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "signature": "29bd25d0f808d7fcd2aa6d5ed206053899198397506c301b218a9e47a3d7070af03e903ff718978d50d1b6b9af8cc0e313d84eda5d5b1e8e85e5516d630bbeb9" + }, + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs: multiple outputs, multiple recipients", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ] + }, + "expected": { + "outputs": [ + [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9902c3c56e84002a7cd410113a9ab21d142be7f53cf5200720bb01314c5eb920", + "scan_priv_key": "060b751d7892149006ed7b98606955a29fe284a1e900070c0971f5fb93dbf422" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ], + "outputs": [ + { + "priv_key_tweak": "72cd082cccb633bf85240a83494b32dc943a4d05647a6686d23ad4ca59c0ebe4", + "pub_key": "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "signature": "38745f3d9f5eef0b1cfb17ca314efa8c521efab28a23aa20ec5e3abb561d42804d539906dce60c4ee7977966184e6f2cab1faa0e5377ceb7148ec5218b4e7878" + }, + { + "priv_key_tweak": "2f17ea873a0047fc01ba8010fef0969e76d0e4283f600d48f735098b1fee6eb9", + "pub_key": "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "signature": "c26f4e3cf371b90b840f48ea0e761b5ec31883ed55719f9ef06a90e282d85f565790ab780a3f491bc2668cc64e944dca849d1022a878cdadb8d168b8da4a6da3" + } + ] + } + } + ] + }, + { + "comment": "Receiving with labels: label with even parity", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq" + ] + }, + "expected": { + "outputs": [ + [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "51d4e9d0d482b5700109b4b2e16ff508269b03d800192a043d61dca4a0a72a52", + "pub_key": "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a", + "signature": "c30fa63bad6f0a317f39a773a5cbf0b0f8193c71dfebba05ee6ae4ed28e3775e6e04c3ea70a83703bb888122855dc894cab61692e7fd10c9b3494d479a60785e" + } + ] + } + } + ] + }, + { + "comment": "Receiving with labels: label with odd parity", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n" + ] + }, + "expected": { + "outputs": [ + [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "6024ae214876356b8d917716e7707d267ae16a0fdb07de2a786b74a7bbcddead", + "pub_key": "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c", + "signature": "a86d554d0d6b7aa0907155f7e0b47f0182752472fffaeddd68da90e99b9402f166fd9b33039c302c7115098d971c1399e67c19e9e4de180b10ea0b9d6f0db832" + } + ] + } + } + ] + }, + { + "comment": "Receiving with labels: large label integer", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ] + }, + "expected": { + "outputs": [ + [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 2, + 3, + 1001337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqsg59z2rppn4qlkx0yz9sdltmjv3j8zgcqadjn4ug98m3t6plujsq9qvu5n", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5" + ], + "outputs": [ + { + "priv_key_tweak": "e336b92330c33030285ce42e4115ad92d5197913c88e06b9072b4a9b47c664a2", + "pub_key": "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951", + "signature": "c9e80dd3bdd25ca2d352ce77510f1aed37ba3509dc8cc0677f2d7c2dd04090707950ce9dd6c83d2a428063063aff5c04f1744e334f661f2fc01b4ef80b50f739" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled and labeled address; same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs with labels: multiple outputs for labeled address; same recipient", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ] + }, + "expected": { + "outputs": [ + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "9d5fd3b91cac9ddfea6fc2e6f9386f680e6cee623cda02f53706306c081de87f", + "pub_key": "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "signature": "db0dfacc98b6a6fcc67cc4631f080b1ca38c60d8c397f2f19843f8f95ec91594b24e47c5bd39480a861c1209f7e3145c440371f9191fb96e324690101eac8e8e" + } + ] + } + } + ] + }, + { + "comment": "Multiple outputs with labels: un-labeled, labeled, and multiple outputs for labeled address; same recipients", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5" + ] + }, + "expected": { + "outputs": [ + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701" + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + [ + "3c54444944d176437644378c23efb999ab6ab1cacdfe1dc1537b607e3df330e2", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "3edf1ff6657c6e69568811bd726a7a7f480493aa42161acfe8dd4f44521f99ed", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + [ + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "7ee1543ed5d123ffa66fbebc128c020173eb490d5fa2ba306e0c9573a77db8f3", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + [ + "602e10e6944107c9b48bd885b493676578c935723287e0ab2f8b7f136862568e", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca" + ], + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1, + 1337 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5" + ], + "outputs": [ + { + "priv_key_tweak": "4e3352fbe0505c25e718d96007c259ef08db34f8c844e4ff742d9855ff03805a", + "pub_key": "006a02c308ccdbf3ac49f0638f6de128f875db5a213095cf112b3b77722472ae", + "signature": "6eeae1ea9eb826e3d0e812f65937100e0836ea188c04f36fabc4981eda29de8d3d3529390a0a8b3d830f7bca4f5eae5994b9788ddaf05ad259ffe26d86144b4b" + }, + { + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + }, + { + "priv_key_tweak": "bf709f98d4418f8a67e738154ae48818dad44689cd37fbc070891a396dd1c633", + "pub_key": "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "signature": "42a19fd8a63dde1824966a95d65a28203e631e49bf96ca5dae1b390e7a0ace2cc8709c9b0c5715047032f57f536a3c80273cbecf4c05be0b5456c183fa122c06" + }, + { + "priv_key_tweak": "736f05e4e3072c3b8656bedef2e9bf54cbcaa2b6fe5320d3e86f5b96874dda71", + "pub_key": "ca64abe1e0f737823fb9a94f597eed418fb2df77b1317e26b881a14bb594faaa", + "signature": "2e61bb3d79418ecf55f68847cf121bfc12d397b39d1da8643246b2f0a9b96c3daa4bfe9651beb5c9ce20e1f29282c4566400a4b45ee6657ec3b18fdc554da0b4" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: use silent payments for sender change", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr" + ] + }, + "expected": { + "outputs": [ + [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "b8f87388cbb41934c50daca018901b00070a5ff6cc25a7e9e716a9d5b9e4d664", + "scan_priv_key": "11b7a82e06ca2648d5fded2366478078ec4fc9dc1d8ff487518226f229d768fd" + }, + "labels": [ + 0 + ] + }, + "expected": { + "addresses": [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqauj52ymtc4xdkmx3tgyhrsemg2g3303xk2gtzfy8h8ejet8fz8jcw23zua", + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr" + ], + "outputs": [ + { + "priv_key_tweak": "80cd767ed20bd0bb7d8ea5e803f8c381293a62e8a073cf46fb0081da46e64e1f", + "pub_key": "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "signature": "7fbd5074cf1377273155eefafc7c330cb61b31da252f22206ac27530d2b2567040d9af7808342ed4a09598c26d8307446e4ed77079e6a2e61fea736e44da5f5a" + } + ] + } + }, + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ] + } + } + ] + }, + { + "comment": "Single recipient: taproot input with NUMS point", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0440c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b22205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00150", + "prevout": { + "scriptPubKey": { + "hex": "5120da6f0595ecb302bbe73e2f221f05ab10f336b06817d36fd28fc6691725ddaa85" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + }, + "private_key": "fc8716a97a48ba9a05a98ae47b5cd201a25a7fd5d8b73c203c5f7b6b6b3b6ad7" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "0340268d31a9276f6380107d5321cafa6d9e8e5ea39204318fdc8206b31507c891c3bbcea3c99e2208d73bd127a8e8c5f1e45a54f1bd217205414ddb566ab7eda0092220e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85dac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "prevout": { + "scriptPubKey": { + "hex": "51200a3c9365ceb131f89b0a4feb6896ebd67bb15a98c31eaa3da143bb955a0f3fcb" + } + }, + "private_key": "8d4751f6e8a3586880fb66c19ae277969bd5aa06f61c4ee2f1e2486efdf666d3" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0440c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b22205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5ac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac00150", + "prevout": { + "scriptPubKey": { + "hex": "5120da6f0595ecb302bbe73e2f221f05ab10f336b06817d36fd28fc6691725ddaa85" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140bd1e708f92dbeaf24a6b8dd22e59c6274355424d62baea976b449e220fd75b13578e262ab11b7aa58e037f0c6b0519b66803b7d9decaa1906dedebfb531c56c1", + "prevout": { + "scriptPubKey": { + "hex": "5120782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "0340268d31a9276f6380107d5321cafa6d9e8e5ea39204318fdc8206b31507c891c3bbcea3c99e2208d73bd127a8e8c5f1e45a54f1bd217205414ddb566ab7eda0092220e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85dac21c150929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0", + "prevout": { + "scriptPubKey": { + "hex": "51200a3c9365ceb131f89b0a4feb6896ebd67bb15a98c31eaa3da143bb955a0f3fcb" + } + } + } + ], + "outputs": [ + "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "3ddec3232609d348d6b8b53123b4f40f6d4f5398ca586f087b0416ec3b851496", + "pub_key": "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d", + "signature": "d7d06e3afb68363031e4eb18035c46ceae41bdbebe7888a4754bc9848c596436869aeaecff0527649a1f458b71c9ceecec10b535c09d01d720229aa228547706" + } + ] + } + } + ] + }, + { + "comment": "Pubkey extraction from malleated p2pkh", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "0075473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "5163473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187372102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d67483046022100c0d3c851d3bd562ae93d56bcefd735ea57c027af46145a4d5e9cac113bfeb0c2022100ee5b2239af199fa9b7aa1d98da83a29d0a2cf1e4f29e2f37134ce386d51c544c2102ad0f26ddc7b3fcc340155963b3051b85289c1869612ecb290184ac952e2864ec68", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914c82c5ec473cbc6c86e5ef410e36f9495adcf979988ac" + } + }, + "private_key": "72b8ae09175ca7977f04993e651d88681ed932dfb92c5158cdf0161dd23fda6e" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "0075473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "5163473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187372102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d67483046022100c0d3c851d3bd562ae93d56bcefd735ea57c027af46145a4d5e9cac113bfeb0c2022100ee5b2239af199fa9b7aa1d98da83a29d0a2cf1e4f29e2f37134ce386d51c544c2102ad0f26ddc7b3fcc340155963b3051b85289c1869612ecb290184ac952e2864ec68", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914c82c5ec473cbc6c86e5ef410e36f9495adcf979988ac" + } + } + } + ], + "outputs": [ + "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "10bde9781def20d7701e7603ef1b1e5e71c67bae7154818814e3c81ef5b1a3d3", + "pub_key": "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f", + "signature": "6137969f810e9e8ef6c9755010e808f5dd1aed705882e44d7f0ae64eb0c509ec8b62a0671bee0d5914ac27d2c463443e28e999d82dc3d3a4919f093872d947bb" + } + ] + } + } + ] + }, + { + "comment": "P2PKH and P2WPKH Uncompressed Keys are skipped", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "02473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187374104e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d6fe8190e189be57d0d5bcd17dbcbcd04c9b4a1c5f605b10d5c90abfcc0d12884", + "prevout": { + "scriptPubKey": { + "hex": "00140423f731a07491364e8dce98b7c00bda63336950" + } + }, + "private_key": "72b8ae09175ca7977f04993e651d88681ed932dfb92c5158cdf0161dd23fda6e" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 1, + "scriptSig": "", + "txinwitness": "02473045022100e7d26e77290b37128f5215ade25b9b908ce87cc9a4d498908b5bb8fd6daa1b8d022002568c3a8226f4f0436510283052bfb780b76f3fe4aa60c4c5eb118e43b187374104e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d6fe8190e189be57d0d5bcd17dbcbcd04c9b4a1c5f605b10d5c90abfcc0d12884", + "prevout": { + "scriptPubKey": { + "hex": "00140423f731a07491364e8dce98b7c00bda63336950" + } + } + } + ], + "outputs": [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "signature": "72e7ad573ac23255d4651d5b0326a200496588acb7a4894b22092236d5eda6a0a9a4d8429b022c2219081fefce5b33795cae488d10f5ea9438849ed8353624f2" + } + ] + } + } + ] + }, + { + "comment": "Skip invalid P2SH inputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "16001419c2f3ae0ca3b642bd3e49598b8da89f50c14161", + "txinwitness": "02483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "prevout": { + "scriptPubKey": { + "hex": "a9148629db5007d5fcfbdbb466637af09daf9125969387" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "1600144b92ac4ac6fe6212393894addda332f2e47a3156", + "txinwitness": "02473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "prevout": { + "scriptPubKey": { + "hex": "a9146c9bf136fbb7305fd99d771a95127fcf87dedd0d87" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "00493046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d601483045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b97014c695221025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be52103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233382102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d53ae", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "a9141044ddc6cea09e4ac40fbec2ba34ad62de6db25b87" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "16001419c2f3ae0ca3b642bd3e49598b8da89f50c14161", + "txinwitness": "02483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "prevout": { + "scriptPubKey": { + "hex": "a9148629db5007d5fcfbdbb466637af09daf9125969387" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 1, + "scriptSig": "1600144b92ac4ac6fe6212393894addda332f2e47a3156", + "txinwitness": "02473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "prevout": { + "scriptPubKey": { + "hex": "a9146c9bf136fbb7305fd99d771a95127fcf87dedd0d87" + } + } + }, + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 2, + "scriptSig": "00493046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d601483045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b97014c695221025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be52103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233382102e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d53ae", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "a9141044ddc6cea09e4ac40fbec2ba34ad62de6db25b87" + } + } + } + ], + "outputs": [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "signature": "72e7ad573ac23255d4651d5b0326a200496588acb7a4894b22092236d5eda6a0a9a4d8429b022c2219081fefce5b33795cae488d10f5ea9438849ed8353624f2" + } + ] + } + } + ] + }, + { + "comment": "Recipient ignores unrelated outputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ] + }, + "expected": { + "outputs": [ + [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8" + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "", + "txinwitness": "0140c459b671370d12cfb5acee76da7e3ba7cc29b0b4653e3af8388591082660137d087fdc8e89a612cd5d15be0febe61fc7cdcf3161a26e599a4514aa5c3e86f47b", + "prevout": { + "scriptPubKey": { + "hex": "51205a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b972103782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9147cdd63cc408564188e8e472640e921c7c90e651d88ac" + } + } + } + ], + "outputs": [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [] + } + } + ] + }, + { + "comment": "No valid inputs, sender generates no outputs", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d641045a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5c61836c9b1688ba431f7ea3039742251f62f0dca3da1bee58a47fa9b456c2d52", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914460e8b41545d2dbe7e0671f0f573e2232814260a88ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + }, + "private_key": "0378e95685b74565fa56751b84a32dfd18545d10d691641b8372e32164fad66a" + } + ], + "recipients": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ] + }, + "expected": { + "outputs": [ + [] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d641045a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5c61836c9b1688ba431f7ea3039742251f62f0dca3da1bee58a47fa9b456c2d52", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a914460e8b41545d2dbe7e0671f0f573e2232814260a88ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "vout": 0, + "scriptSig": "473045022100a8c61b2d470e393279d1ba54f254b7c237de299580b7fa01ffcc940442ecec4502201afba952f4e4661c40acde7acc0341589031ba103a307b886eb867b23b850b974104782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c3799373233387c5343bf58e23269e903335b958a12182f9849297321e8d710e49a8727129cab", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a9144b92ac4ac6fe6212393894addda332f2e47a315688ac" + } + } + } + ], + "outputs": [ + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "e0ec4f64b3fa2e463ccfcf4e856e37d5e1e20275bc89ec1def9eb098eff1f85d" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [] + } + } + ] + }, + { + "comment": "Input keys sum up to zero / point at infinity: sending fails, receiver skips tx", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "024730440220085003179ce1a3a88ce0069aa6ea045e140761ab88c22a26ae2a8cfe983a6e4602204a8a39940f0735c8a4424270ac8da65240c261ab3fda9272f6d6efbf9cfea366012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + }, + "private_key": "a6df6a0bb448992a301df4258e06a89fe7cf7146f59ac3bd5ff26083acb22ceb" + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402204586a68e1d97dd3c6928e3622799859f8c3b20c3c670cf654cc905c9be29fdb7022043fbcde1689f3f4045e8816caf6163624bd19e62e4565bc99f95c533e599782c012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + }, + "private_key": "592095f44bb766d5cfe20bda71f9575ed2df6b9fb9addc7e5fdffe0923841456" + } + ], + "recipients": [ + "sp1qqtrqglu5g8kh6mfsg4qxa9wq0nv9cauwfwxw70984wkqnw2uwz0w2qnehen8a7wuhwk9tgrzjh8gwzc8q2dlekedec5djk0js9d3d7qhnq6lqj3s" + ] + }, + "expected": { + "outputs": [ + [] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 0, + "scriptSig": "", + "txinwitness": "024730440220085003179ce1a3a88ce0069aa6ea045e140761ab88c22a26ae2a8cfe983a6e4602204a8a39940f0735c8a4424270ac8da65240c261ab3fda9272f6d6efbf9cfea366012102557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149d9e24f9fab4e35bf1a6df4b46cb533296ac0792" + } + } + }, + { + "txid": "3a286147b25e16ae80aff406f2673c6e565418c40f45c071245cdebc8a94174e", + "vout": 1, + "scriptSig": "", + "txinwitness": "0247304402204586a68e1d97dd3c6928e3622799859f8c3b20c3c670cf654cc905c9be29fdb7022043fbcde1689f3f4045e8816caf6163624bd19e62e4565bc99f95c533e599782c012103557ef3e55b0a52489b4454c1169e06bdea43687a69c1f190eb50781644ab6975", + "prevout": { + "scriptPubKey": { + "hex": "00149860538b5575962776ed0814ae222c7d60c72d7b" + } + } + } + ], + "outputs": [ + "0000000000000000000000000000000000000000000000000000000000000000" + ], + "key_material": { + "spend_priv_key": "0000000000000000000000000000000000000000000000000000000000000001", + "scan_priv_key": "0000000000000000000000000000000000000000000000000000000000000002" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqtrqglu5g8kh6mfsg4qxa9wq0nv9cauwfwxw70984wkqnw2uwz0w2qnehen8a7wuhwk9tgrzjh8gwzc8q2dlekedec5djk0js9d3d7qhnq6lqj3s" + ], + "outputs": [] + } + } + ] + } +] \ No newline at end of file From b8220c4535d30e5433b5d44cc8cbfe90a031864c Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sun, 1 Sep 2024 22:26:00 +0200 Subject: [PATCH 2/6] silentpayments: add send output support --- btcutil/silentpayments/input.go | 195 ++++++++++++++++++++++ btcutil/silentpayments/input_test.go | 237 +++++++++++++++++++++++++++ btcutil/silentpayments/output.go | 94 +++++++++++ btcutil/silentpayments/secp.go | 33 ++++ 4 files changed, 559 insertions(+) create mode 100644 btcutil/silentpayments/input.go create mode 100644 btcutil/silentpayments/input_test.go create mode 100644 btcutil/silentpayments/output.go create mode 100644 btcutil/silentpayments/secp.go diff --git a/btcutil/silentpayments/input.go b/btcutil/silentpayments/input.go new file mode 100644 index 0000000000..7c8c870d34 --- /dev/null +++ b/btcutil/silentpayments/input.go @@ -0,0 +1,195 @@ +package silentpayments + +import ( + "bytes" + "errors" + "fmt" + "sort" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + // TagBIP0352Inputs is the BIP-0352 tag for a inputs. + TagBIP0352Inputs = []byte("BIP0352/Inputs") + + // TagBIP0352SharedSecret is the BIP-0352 tag for a shared secret. + TagBIP0352SharedSecret = []byte("BIP0352/SharedSecret") + + // ErrInputKeyZero is returned if the sum of input keys is zero. + ErrInputKeyZero = errors.New("sum of input keys is zero") +) + +// Input describes a UTXO that should be spent in order to pay to one or +// multiple silent addresses. +type Input struct { + // OutPoint is the outpoint of the UTXO. + OutPoint wire.OutPoint + + // Utxo is script and amount of the UTXO. + Utxo wire.TxOut + + // PrivKey is the private key of the input. + // TODO(guggero): Find a way to do this in a remote signer setup where + // we don't have access to the raw private key. We could restrict the + // number of inputs to a single one, then we can do ECDH directly? Or + // is there a PSBT protocol for this? + PrivKey btcec.PrivateKey + + // SkipInput must be set to true if the input should be skipped because + // it meets one of the following conditions: + // - It is a P2TR input with a NUMS internal key. + // - It is a P2WPKH input with an uncompressed public key. + // - It is a P2SH (nested P2WPKH) input with an uncompressed public key. + SkipInput bool +} + +// CreateOutputs creates the outputs for a silent payment transaction. It +// returns the public keys of the outputs that should be used to create the +// transaction. Recipients must be ordered by output index. +func CreateOutputs(inputs []Input, + recipients []Address) ([]OutputWithAddress, error) { + + if len(inputs) == 0 { + return nil, fmt.Errorf("no inputs provided") + } + + // We first sum up all the private keys of the inputs. + // + // Spec: Let a = a1 + a2 + ... + a_n, where each a_i has been negated if + // necessary. + var sumKey = new(btcec.ModNScalar) + for idx, input := range inputs { + a := &input.PrivKey + + // If we should skip the input because it is a P2TR input with a + // NUMS internal key, we can just continue here. + if input.SkipInput { + continue + } + + ok, script, err := InputCompatible(input.Utxo.PkScript) + if err != nil { + return nil, fmt.Errorf("unable check input %d for "+ + "silent payment transaction compatibility: %w", + idx, err) + } + + if !ok { + return nil, fmt.Errorf("input %d (%v) is not "+ + "compatible with silent payment transactions", + idx, script.Class().String()) + } + + // For P2TR we need to take the even key. + if script.Class() == txscript.WitnessV1TaprootTy { + pubKeyBytes := a.PubKey().SerializeCompressed() + if pubKeyBytes[0] == secp.PubKeyFormatCompressedOdd { + a.Key.Negate() + } + } + + sumKey = sumKey.Add(&a.Key) + } + + // Spec: If a = 0, fail. + if sumKey.IsZero() { + return nil, ErrInputKeyZero + } + sumPrivKey := btcec.PrivKeyFromScalar(sumKey) + + inputOutpoints := make([]wire.OutPoint, 0, len(inputs)) + for _, input := range inputs { + inputOutpoints = append(inputOutpoints, input.OutPoint) + } + + // Create the tweak that will be used to tweak the share sum key to + // arrive at the final shared secret. + // + // Spec: Let ecdh_shared_secret = input_hash·a·B_scan. + inputHash, err := CalculateInputHashTweak( + inputOutpoints, sumPrivKey.PubKey(), + ) + if err != nil { + return nil, err + } + + return AddressOutputKeys(recipients, *sumKey, *inputHash) +} + +// InputCompatible checks if a given pkScript is compatible with the silent +// payment protocol. +func InputCompatible(pkScript []byte) (bool, txscript.PkScript, error) { + script, err := txscript.ParsePkScript(pkScript) + if err != nil { + return false, txscript.PkScript{}, fmt.Errorf("error parsing "+ + "pkScript: %w", err) + } + + switch script.Class() { + case txscript.PubKeyHashTy, txscript.WitnessV0PubKeyHashTy, + txscript.WitnessV1TaprootTy: + + // These types are supported in any case. + return true, script, nil + + case txscript.ScriptHashTy: + // Only P2SH-P2WPKH is supported. Do we need further checks? + // Or do we just assume Nested P2WPKH is the only active use + // case of P2SH these days? + return true, script, nil + + default: + return false, script, nil + } +} + +// CalculateInputHashTweak calculates the input hash from the given input +// outpoint list and the sum public key A. The tweak can be used to tweak the +// share sum key to arrive at the final shared secret. +func CalculateInputHashTweak(inputs []wire.OutPoint, + A *btcec.PublicKey) (*btcec.ModNScalar, error) { + + // Now we need to choose the smallest outpoint lexicographically. We can + // do that by sorting the input outpoints. + // + // Spec: Let input_hash = hashBIP0352/Inputs(outpointL || A), where + // outpointL is the smallest outpoint lexicographically used in the + // transaction and A = a·G. + sort.Slice(inputs, func(i, j int) bool { + iBytes := serializeOutpoint(inputs[i]) + jBytes := serializeOutpoint(inputs[j]) + + return bytes.Compare(iBytes[:], jBytes[:]) == -1 + }) + firstInput := inputs[0] + + var inputPayload bytes.Buffer + err := wire.WriteOutPoint(&inputPayload, 0, 0, &firstInput) + if err != nil { + return nil, err + } + _, err = inputPayload.Write(A.SerializeCompressed()) + if err != nil { + return nil, err + } + inputHash := chainhash.TaggedHash( + TagBIP0352Inputs, inputPayload.Bytes(), + ) + + var inputHashScalar btcec.ModNScalar + inputHashScalar.SetBytes((*[32]byte)(inputHash)) + + return &inputHashScalar, nil +} + +// serializeOutpoint serializes an outpoint to a byte slice. +func serializeOutpoint(outpoint wire.OutPoint) []byte { + var buf bytes.Buffer + _ = wire.WriteOutPoint(&buf, 0, 0, &outpoint) + return buf.Bytes() +} diff --git a/btcutil/silentpayments/input_test.go b/btcutil/silentpayments/input_test.go new file mode 100644 index 0000000000..42963fb127 --- /dev/null +++ b/btcutil/silentpayments/input_test.go @@ -0,0 +1,237 @@ +package silentpayments + +import ( + "bytes" + "encoding/hex" + "errors" + "github.com/btcsuite/btcd/txscript" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/stretchr/testify/require" +) + +var ( + // BIP0341NUMSPoint is an example NUMS point as defined in BIP-0341. + BIP0341NUMSPoint = []byte{ + 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, + 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e, + 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, + 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0, + } +) + +// TestCreateOutputs tests the generation of silent payment outputs. +func TestCreateOutputs(t *testing.T) { + vectors, err := ReadTestVectors() + require.NoError(t, err) + + for _, vector := range vectors { + vector := vector + success := t.Run(vector.Comment, func(tt *testing.T) { + runCreateOutputTest(tt, vector) + }) + + if !success { + break + } + } +} + +// runCreateOutputTest tests the generation of silent payment outputs. +func runCreateOutputTest(t *testing.T, vector *TestVector) { + for _, sending := range vector.Sending { + inputs := make([]Input, 0, len(sending.Given.Vin)) + for _, vin := range sending.Given.Vin { + txid, err := chainhash.NewHashFromStr(vin.Txid) + require.NoError(t, err) + + outpoint := wire.NewOutPoint(txid, vin.Vout) + + pkScript, err := hex.DecodeString( + vin.PrevOut.ScriptPubKey.Hex, + ) + require.NoError(t, err) + utxo := wire.NewTxOut(0, pkScript) + + sigScript, err := hex.DecodeString(vin.ScriptSig) + require.NoError(t, err) + + witnessBytes, err := hex.DecodeString(vin.TxInWitness) + require.NoError(t, err) + + skip := shouldSkip(t, pkScript, sigScript, witnessBytes) + + privKeyBytes, err := hex.DecodeString( + vin.PrivateKey, + ) + require.NoError(t, err) + privKey, _ := btcec.PrivKeyFromBytes( + privKeyBytes, + ) + + inputs = append(inputs, Input{ + OutPoint: *outpoint, + Utxo: *utxo, + PrivKey: *privKey, + SkipInput: skip, + }) + } + + recipients := make( + []Address, 0, len(sending.Given.Recipients), + ) + for _, recipient := range sending.Given.Recipients { + addr, err := DecodeAddress(recipient) + require.NoError(t, err) + + recipients = append(recipients, *addr) + } + + result, err := CreateOutputs(inputs, recipients) + + // Special case for when the input keys add up to zero. + if errors.Is(err, ErrInputKeyZero) { + require.Empty(t, sending.Expected.Outputs[0]) + + continue + } + + require.NoError(t, err) + + if len(result) == 0 { + require.Empty(t, sending.Expected.Outputs[0]) + + continue + } + + require.Len(t, result, len(sending.Expected.Outputs[0])) + + resultStrings := make([]string, len(result)) + for idx, output := range result { + resultStrings[idx] = hex.EncodeToString( + schnorr.SerializePubKey(output.OutputKey), + ) + } + + resultsContained(t, sending.Expected.Outputs, resultStrings) + } +} + +func shouldSkip(t *testing.T, pkScript, sigScript, witnessBytes []byte) bool { + script, err := txscript.ParsePkScript(pkScript) + require.NoError(t, err) + + // Special case for P2PKH: + if script.Class() == txscript.PubKeyHashTy { + return checkPubKeyScriptSig(t, sigScript) + } + + // Special case for P2SH with script sig only: + if script.Class() == txscript.ScriptHashTy && len(witnessBytes) == 0 && + len(sigScript) != 0 { + + return checkPubKeyScriptSig(t, sigScript) + + } + + if len(witnessBytes) == 0 { + return false + } + + witness, err := parseWitness(witnessBytes) + require.NoError(t, err) + + if len(witness) == 0 { + return true + } + + switch script.Class() { + case txscript.WitnessV0PubKeyHashTy: + lastWitness := witness[len(witness)-1] + + return len(lastWitness) != btcec.PubKeyBytesLenCompressed + + case txscript.ScriptHashTy: + lastWitness := witness[len(witness)-1] + + return len(lastWitness) != btcec.PubKeyBytesLenCompressed + + case txscript.WitnessV1TaprootTy: + return isNUMSWitness(witnessBytes) + + default: + return true + } +} + +func checkPubKeyScriptSig(t *testing.T, sigScript []byte) bool { + // If the sigScript isn't set, we just assume a valid key. + if len(sigScript) == 0 { + return false + } + + tokenizer := txscript.MakeScriptTokenizer(0, sigScript) + for tokenizer.Next() { + if tokenizer.Opcode() == txscript.OP_DATA_33 && + len(tokenizer.Data()) == 33 { + + return false + } + } + if err := tokenizer.Err(); err != nil { + t.Fatalf("error tokenizing sigScript: %v", err) + } + + // If there was a sigScript set but there was no 33-byte + // compressed key push, we skip the input. + return true +} + +func isNUMSWitness(witnessBytes []byte) bool { + return bytes.Contains(witnessBytes, BIP0341NUMSPoint) +} + +func parseWitness(witnessBytes []byte) (wire.TxWitness, error) { + witnessReader := bytes.NewReader(witnessBytes) + witCount, err := wire.ReadVarInt(witnessReader, 0) + if err != nil { + return nil, err + } + + result := make(wire.TxWitness, witCount) + for j := uint64(0); j < witCount; j++ { + wit, err := wire.ReadVarBytes( + witnessReader, 0, txscript.MaxScriptSize, "witness", + ) + if err != nil { + return nil, err + } + result[j] = wit + } + + return result, nil +} + +func resultsContained(t *testing.T, expected [][]string, results []string) { + for _, expectedSet := range expected { + contained := false + for _, e := range expectedSet { + for _, r := range results { + if e == r { + contained = true + break + } + } + } + + if contained { + return + } + } + + require.Fail(t, "no expected output found in results") +} diff --git a/btcutil/silentpayments/output.go b/btcutil/silentpayments/output.go new file mode 100644 index 0000000000..0e50dd0242 --- /dev/null +++ b/btcutil/silentpayments/output.go @@ -0,0 +1,94 @@ +package silentpayments + +import ( + "encoding/binary" + "fmt" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +// OutputWithAddress is a struct that holds the generated shared public key and +// silent payment address of an output. +type OutputWithAddress struct { + // Address is the address of the output. + Address Address + + // OutputKey is the generated shared public key for the given address. + OutputKey *btcec.PublicKey +} + +// AddressOutputKeys generates the actual on-chain output keys for the given +// addresses. The addresses need to be ordered by output index as they appear +// in the transaction. +func AddressOutputKeys(recipients []Address, sumKey btcec.ModNScalar, + inputHash btcec.ModNScalar) ([]OutputWithAddress, error) { + + // Spec: For each B_m in the group: + results := make([]OutputWithAddress, 0, len(recipients)) + for _, recipients := range GroupByScanKey(recipients) { + // We grouped by scan key before, so we can just take the first + // one. + scanPubKey := recipients[0].ScanKey + + for idx, recipient := range recipients { + recipientSpendKey := recipient.TweakedSpendKey() + + shareSum := ScalarMult(sumKey, &scanPubKey) + sharedKey, err := CreateOutputKey( + *shareSum, *recipientSpendKey, uint32(idx), + inputHash, + ) + if err != nil { + return nil, err + } + + results = append(results, OutputWithAddress{ + Address: recipient, + OutputKey: sharedKey, + }) + } + } + + return results, nil +} + +// CreateOutputKey creates a shared public key for the given share sum key, +// spend key, index and input hash. +func CreateOutputKey(shareSum, spendKey btcec.PublicKey, idx uint32, + inputHash btcec.ModNScalar) (*btcec.PublicKey, error) { + + // The shareSum key is only a·B_scan, so we need to multiply it by + // input_hash. + // + // Spec: Let ecdh_shared_secret = input_hash·a·B_scan. + sharedSecret := ScalarMult(inputHash, &shareSum) + + // Spec: Let tk = hashBIP0352/SharedSecret( + // serP(ecdh_shared_secret) || ser32(k) + // ) + outputPayload := make([]byte, pubKeyLength+4) + copy(outputPayload[:], sharedSecret.SerializeCompressed()) + + k := idx + binary.BigEndian.PutUint32(outputPayload[pubKeyLength:], k) + + t := chainhash.TaggedHash(TagBIP0352SharedSecret, outputPayload) + + var tScalar btcec.ModNScalar + overflow := tScalar.SetBytes((*[32]byte)(t)) + + // Spec: If tk is not valid tweak, i.e., if tk = 0 or tk is larger or + // equal to the secp256k1 group order, fail. + if overflow == 1 { + return nil, fmt.Errorf("tagged hash overflow") + } + if tScalar.IsZero() { + return nil, fmt.Errorf("tagged hash is zero") + } + + // Spec: Let Pmn = Bm + tk·G + sharedKey := ScalarBaseMultAdd(tScalar, &spendKey) + + return sharedKey, nil +} diff --git a/btcutil/silentpayments/secp.go b/btcutil/silentpayments/secp.go new file mode 100644 index 0000000000..450c6ef032 --- /dev/null +++ b/btcutil/silentpayments/secp.go @@ -0,0 +1,33 @@ +package silentpayments + +import ( + "github.com/btcsuite/btcd/btcec/v2" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +// ScalarBaseMultAdd returns the public key resulting from the scalar +// multiplication of the base point with the given private key, then adding +// the given public key. +func ScalarBaseMultAdd(a secp.ModNScalar, b *btcec.PublicKey) *btcec.PublicKey { + var resultJ, bJ btcec.JacobianPoint + b.AsJacobian(&bJ) + + btcec.ScalarBaseMultNonConst(&a, &resultJ) + btcec.AddNonConst(&bJ, &resultJ, &resultJ) + + resultJ.ToAffine() + + return btcec.NewPublicKey(&resultJ.X, &resultJ.Y) +} + +// ScalarMult returns the public key resulting from the scalar multiplication +// of the given scalar with the given public key. +func ScalarMult(a secp.ModNScalar, pub *btcec.PublicKey) *btcec.PublicKey { + var resultJ btcec.JacobianPoint + pub.AsJacobian(&resultJ) + + btcec.ScalarMultNonConst(&a, &resultJ, &resultJ) + resultJ.ToAffine() + + return btcec.NewPublicKey(&resultJ.X, &resultJ.Y) +} \ No newline at end of file From 8c3d1e40a3159ea644196411234d72fcdfbf8cdc Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 28 Dec 2024 15:53:02 +0100 Subject: [PATCH 3/6] silentpayments: implement DLEQ proofs --- btcutil/silentpayments/dleq.go | 206 ++++++++++++++++++ btcutil/silentpayments/dleq_test.go | 195 +++++++++++++++++ btcutil/silentpayments/secp.go | 44 +++- .../testdata/test-vectors-dleq-generate.json | 82 +++++++ .../testdata/test-vectors-dleq-verify.json | 134 ++++++++++++ 5 files changed, 660 insertions(+), 1 deletion(-) create mode 100644 btcutil/silentpayments/dleq.go create mode 100644 btcutil/silentpayments/dleq_test.go create mode 100644 btcutil/silentpayments/testdata/test-vectors-dleq-generate.json create mode 100644 btcutil/silentpayments/testdata/test-vectors-dleq-verify.json diff --git a/btcutil/silentpayments/dleq.go b/btcutil/silentpayments/dleq.go new file mode 100644 index 0000000000..fcfe56338e --- /dev/null +++ b/btcutil/silentpayments/dleq.go @@ -0,0 +1,206 @@ +package silentpayments + +import ( + crand "crypto/rand" + "errors" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/btcsuite/btcd/chaincfg/chainhash" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +var ( + // TagBIP0374Aux is the BIP-0374 tag for auxiliary randomness. + TagBIP0374Aux = []byte("BIP0374/aux") + + // TagBIP0374Nonce is the BIP-0374 tag for a nonce. + TagBIP0374Nonce = []byte("BIP0374/nonce") + + // TagBIP0374Challenge is the BIP-0374 tag for a challenge. + TagBIP0374Challenge = []byte("BIP0374/challenge") + + // ErrPrivateKeyZero is returned if the private key is zero. + ErrPrivateKeyZero = errors.New("private key is zero") +) + +// DLEQProof generates a DLEQ proof according to BIP-0374 for the given private +// key a, public key B, generator point G, auxiliary randomness r, and optional +// message m. +func DLEQProof(a *btcec.PrivateKey, B, G *btcec.PublicKey, r [32]byte, + m *[32]byte) ([64]byte, error) { + + // Fail if a = 0 or a ≥ n. + if a.Key.IsZero() { + return [64]byte{}, ErrPrivateKeyZero + } + + // Fail if is_infinite(B). + if !B.IsOnCurve() { + return [64]byte{}, secp.ErrPubKeyNotOnCurve + } + + // Let A = a⋅G. + A := ScalarMult(a.Key, G) + + // Let C = a⋅B. + C := ScalarMult(a.Key, B) + + // Let t be the byte-wise xor of bytes(32, a) and hash_BIP0374/aux(r). + aBytes := a.Key.Bytes() + t := chainhash.TaggedHash(TagBIP0374Aux, r[:]) + for i := 0; i < len(t); i++ { + t[i] ^= aBytes[i] + } + + // Let rand = hash_BIP0374/nonce(t || cbytes(A) || cbytes(C)). + rand := chainhash.TaggedHash( + TagBIP0374Nonce, t[:], A.SerializeCompressed(), + C.SerializeCompressed(), + ) + + // Let k = int(rand) mod n. + var k secp.ModNScalar + _ = k.SetByteSlice(rand[:]) + + // Fail if k = 0. + if k.IsZero() { + return [64]byte{}, ErrPrivateKeyZero + } + + // Let R1 = k⋅G. + R1 := ScalarMult(k, G) + + // Let R2 = k⋅B. + R2 := ScalarMult(k, B) + + // Let m' = m if m is provided, otherwise an empty byte array. + // Let e = int(hash_BIP0374/challenge( + // cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R1) || + // cbytes(R2) || m') + // ). + e := DLEQChallenge(A, B, C, G, R1, R2, m) + + // Let s = (k + e⋅a) mod n. + s := new(btcec.ModNScalar) + s.Mul2(e, &a.Key).Add(&k) + sBytes := s.Bytes() + k.Zero() + + // Let proof = bytes(32, e) || bytes(32, s). + eBytes := e.Bytes() + var proof [64]byte + copy(proof[:32], eBytes[:]) + copy(proof[32:], sBytes[:]) + + // If VerifyProof(A, B, C, proof) (see below) returns failure, abort. + if !DLEQVerify(A, B, C, G, proof, m) { + return [64]byte{}, errors.New("proof verification failed") + } + + // Return the proof. + return proof, nil +} + +// DLEQVerify verifies a DLEQ proof according to BIP-0374 for the given public +// keys A, B, C, G, proof, and optional message m. +func DLEQVerify(A, B, C, G *btcec.PublicKey, proof [64]byte, m *[32]byte) bool { + // Fail if any of is_infinite(A), is_infinite(B), is_infinite(C), + // is_infinite(G). + if !A.IsOnCurve() || !B.IsOnCurve() || !C.IsOnCurve() { + return false + } + + // Let e = int(proof[0:32]). + e := new(secp.ModNScalar) + _ = e.SetByteSlice(proof[:32]) + + // Let s = int(proof[32:64]); fail if s ≥ n. + s := new(secp.ModNScalar) + overflow := s.SetByteSlice(proof[32:]) + if overflow { + return false + } + + // Let R1 = s⋅G - e⋅A. + eA := ScalarMult(*e, A) + negEA := Negate(eA) + sG := ScalarMult(*s, G) + R1 := Add(sG, negEA) + + // Fail if is_infinite(R1). + if !R1.IsOnCurve() { + return false + } + + // Let R2 = s⋅B - e⋅C. + eC := ScalarMult(*e, C) + negEC := Negate(eC) + sB := ScalarMult(*s, B) + R2 := Add(sB, negEC) + + // Fail if is_infinite(R2). + if !R2.IsOnCurve() { + return false + } + + // Let m' = m if m is provided, otherwise an empty byte array. + // Fail if e ≠ int(hash_BIP0374/challenge( + // cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R1) || + // cbytes(R2) || m') + // ). + if !e.Equals(DLEQChallenge(A, B, C, G, R1, R2, m)) { + return false + } + + // Return success iff no failure occurred before reaching this point. + return true +} + +// DLEQChallenge generates a DLEQ challenge according to BIP-0374 for the given +// public keys A, B, C, G, R1, R2, and optional message m. +func DLEQChallenge(A, B, C, G, R1, R2 *btcec.PublicKey, + m *[32]byte) *secp.ModNScalar { + + // Let m' = m if m is provided, otherwise an empty byte array. + var mPrime []byte + if m != nil { + mPrime = m[:] + } + + // Let e = int(hash_BIP0374/challenge( + // cbytes(A) || cbytes(B) || cbytes(C) || cbytes(G) || cbytes(R1) || + // cbytes(R2) || m') + // ). + eHash := chainhash.TaggedHash( + TagBIP0374Challenge, A.SerializeCompressed(), + B.SerializeCompressed(), C.SerializeCompressed(), + G.SerializeCompressed(), R1.SerializeCompressed(), + R2.SerializeCompressed(), mPrime[:], + ) + e := new(secp.ModNScalar) + _ = e.SetByteSlice(eHash[:]) + + return e +} + +// CreateShare creates a silent payment ECDH share according to BIP-0374, +// including the corresponding DLEQ proof for it. +func CreateShare(privateKey *btcec.PrivateKey, + scanKey *btcec.PublicKey) (*btcec.PublicKey, [64]byte, error) { + + // Generate a random 32-byte value. + var r [32]byte + if _, err := crand.Read(r[:]); err != nil { + return nil, [64]byte{}, err + } + + B := ScalarMult(privateKey.Key, scanKey) + G := Generator() + + proof, err := DLEQProof(privateKey, B, G, r, nil) + if err != nil { + return nil, [64]byte{}, err + } + + return B, proof, nil +} diff --git a/btcutil/silentpayments/dleq_test.go b/btcutil/silentpayments/dleq_test.go new file mode 100644 index 0000000000..695cacbf5e --- /dev/null +++ b/btcutil/silentpayments/dleq_test.go @@ -0,0 +1,195 @@ +package silentpayments + +import ( + "encoding/hex" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/stretchr/testify/require" +) + +var ( + dleqGenerateTestVectorFileName = "test-vectors-dleq-generate.json" + dleqVerifyTestVectorFileName = "test-vectors-dleq-verify.json" +) + +func TestDLEQProof(t *testing.T) { + vectors, err := ReadDLEQGenerateTestVectors() + require.NoError(t, err) + + for _, vector := range vectors { + t.Run(vector.Comment, func(tt *testing.T) { + G, a, B, r, m, p, valid := vector.Parse(tt) + result, err := DLEQProof(a, B, G, r, m) + + if !valid { + require.Error(tt, err) + return + } + + require.NoError(tt, err) + + require.Equal(tt, p, result) + }) + } +} + +func TestDLEQVerify(t *testing.T) { + vectors, err := ReadDLEQVerifyTestVectors() + require.NoError(t, err) + + for _, vector := range vectors { + t.Run(vector.Comment, func(tt *testing.T) { + G, A, B, C, proof, m, valid := vector.Parse(tt) + result := DLEQVerify(A, B, C, G, proof, m) + + require.Equal(tt, valid, result) + }) + } +} + +type DLEQGenerateVector struct { + PointG string `json:"point_g"` + ScalarA string `json:"scalar_a"` + PointB string `json:"point_b"` + AuxRandR string `json:"aux_rand_r"` + Message string `json:"message"` + ResultProof string `json:"result_proof"` + Comment string `json:"comment"` +} + +func (g *DLEQGenerateVector) Parse(t *testing.T) (*btcec.PublicKey, + *btcec.PrivateKey, *btcec.PublicKey, [32]byte, *[32]byte, [64]byte, + bool) { + + pubKeyG := decodePubKey(t, g.PointG) + privKeyA := decodePrivKey(t, g.ScalarA) + var pubKeyB *btcec.PublicKey + if g.PointB == "INFINITY" { + zero := btcec.FieldVal{} + pubKeyB = btcec.NewPublicKey(&zero, &zero) + } else { + pubKeyB = decodePubKey(t, g.PointB) + } + auxRandR := decode32Array(t, g.AuxRandR) + message := decode32Array(t, g.Message) + + var ( + valid bool + proof [64]byte + ) + if g.ResultProof != "INVALID" { + proof = decode64Array(t, g.ResultProof) + valid = true + } + + return pubKeyG, privKeyA, pubKeyB, auxRandR, &message, proof, valid +} + +type DLEQVerifyVector struct { + PointG string `json:"point_g"` + PointA string `json:"point_a"` + PointB string `json:"point_b"` + PointC string `json:"point_c"` + Proof string `json:"proof"` + Message string `json:"message"` + ResultSuccess string `json:"result_success"` + Comment string `json:"comment"` +} + +func (v *DLEQVerifyVector) Parse(t *testing.T) (*btcec.PublicKey, + *btcec.PublicKey, *btcec.PublicKey, *btcec.PublicKey, [64]byte, + *[32]byte, bool) { + + pubKeyG := decodePubKey(t, v.PointG) + pubKeyA := decodePubKey(t, v.PointA) + pubKeyB := decodePubKey(t, v.PointB) + pubKeyC := decodePubKey(t, v.PointC) + proof := decode64Array(t, v.Proof) + message := decode32Array(t, v.Message) + success := v.ResultSuccess == "TRUE" + + return pubKeyG, pubKeyA, pubKeyB, pubKeyC, proof, &message, success +} + +// ReadDLEQGenerateTestVectors reads the DLEQ generate test vectors from the +// test vector file. +func ReadDLEQGenerateTestVectors() ([]*DLEQGenerateVector, error) { + // Open the test vector file. + file, err := os.Open(filepath.Join( + testdataDir, dleqGenerateTestVectorFileName, + )) + if err != nil { + return nil, err + } + + // Decode the test vectors. + var testVectors []*DLEQGenerateVector + if err := json.NewDecoder(file).Decode(&testVectors); err != nil { + return nil, err + } + + return testVectors, nil +} + +// ReadDLEQVerifyTestVectors reads the DLEQ verify test vectors from the +// test vector file. +func ReadDLEQVerifyTestVectors() ([]*DLEQVerifyVector, error) { + // Open the test vector file. + file, err := os.Open(filepath.Join( + testdataDir, dleqVerifyTestVectorFileName, + )) + if err != nil { + return nil, err + } + + // Decode the test vectors. + var testVectors []*DLEQVerifyVector + if err := json.NewDecoder(file).Decode(&testVectors); err != nil { + return nil, err + } + + return testVectors, nil +} + +func decodePubKey(t *testing.T, str string) *btcec.PublicKey { + pubKeyBytes, err := hex.DecodeString(str) + require.NoError(t, err) + + pubKey, err := btcec.ParsePubKey(pubKeyBytes) + require.NoError(t, err) + + return pubKey +} + +func decode32Array(t *testing.T, str string) [32]byte { + rawBytes, err := hex.DecodeString(str) + require.NoError(t, err) + + var array [32]byte + copy(array[:], rawBytes) + + return array +} + +func decode64Array(t *testing.T, str string) [64]byte { + rawBytes, err := hex.DecodeString(str) + require.NoError(t, err) + + var array [64]byte + copy(array[:], rawBytes) + + return array +} + +func decodePrivKey(t *testing.T, str string) *btcec.PrivateKey { + privKeyBytes, err := hex.DecodeString(str) + require.NoError(t, err) + + privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes) + + return privKey +} diff --git a/btcutil/silentpayments/secp.go b/btcutil/silentpayments/secp.go index 450c6ef032..c661f469a1 100644 --- a/btcutil/silentpayments/secp.go +++ b/btcutil/silentpayments/secp.go @@ -5,6 +5,22 @@ import ( secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) +// Generator returns the generator point of the secp256k1 curve. +func Generator() *btcec.PublicKey { + one := new(secp.ModNScalar).SetInt(1) + return ScalarBaseMult(*one) +} + +// ScalarBaseMult returns the public key resulting from the scalar +// multiplication of the generator point with the given private key. +func ScalarBaseMult(a secp.ModNScalar) *btcec.PublicKey { + var resultJ btcec.JacobianPoint + btcec.ScalarBaseMultNonConst(&a, &resultJ) + resultJ.ToAffine() + + return btcec.NewPublicKey(&resultJ.X, &resultJ.Y) +} + // ScalarBaseMultAdd returns the public key resulting from the scalar // multiplication of the base point with the given private key, then adding // the given public key. @@ -30,4 +46,30 @@ func ScalarMult(a secp.ModNScalar, pub *btcec.PublicKey) *btcec.PublicKey { resultJ.ToAffine() return btcec.NewPublicKey(&resultJ.X, &resultJ.Y) -} \ No newline at end of file +} + +// Negate returns the public key resulting from the negation of the given public +// key. +func Negate(pub *btcec.PublicKey) *btcec.PublicKey { + var resultJ btcec.JacobianPoint + pub.AsJacobian(&resultJ) + + resultJ.Y.Negate(1) + resultJ.Y.Normalize() + resultJ.ToAffine() + + return btcec.NewPublicKey(&resultJ.X, &resultJ.Y) +} + +// Add returns the public key resulting from the addition of the two given +// public keys. +func Add(a, b *btcec.PublicKey) *btcec.PublicKey { + var aJ, bJ btcec.JacobianPoint + a.AsJacobian(&aJ) + b.AsJacobian(&bJ) + + btcec.AddNonConst(&aJ, &bJ, &aJ) + aJ.ToAffine() + + return btcec.NewPublicKey(&aJ.X, &aJ.Y) +} diff --git a/btcutil/silentpayments/testdata/test-vectors-dleq-generate.json b/btcutil/silentpayments/testdata/test-vectors-dleq-generate.json new file mode 100644 index 0000000000..9180afcfa6 --- /dev/null +++ b/btcutil/silentpayments/testdata/test-vectors-dleq-generate.json @@ -0,0 +1,82 @@ +[ + { + "index": "0", + "point_g": "02cef38f55e78b321a1f785cb1c6e33dfcef9784c18bdc4e279801c449ccdfb88e", + "scalar_a": "07ff93d43f1012a5d4a44aba55240212ed39c87b3344e46757d99f24177fc576", + "point_b": "02dad4b35c2379ba8334c9a5dda8f6e6d5cd575a7cc9d3ca4faaac51839daaa30f", + "aux_rand_r": "cb979b0fc8ccc7f237751e719d992fcc324b6500af33999cd54a3e5c05fb1ea4", + "message": "efb07d4b382d3da1079fbf24df623ba6c2e4c764993bbfa6dd7a4fe4aaf33859", + "result_proof": "51ce8becf2726c8fed85957500de5d58e0349b8ed0fe40aee2c122288ee21f8fe4d1ca31f5e3c4833fb83a654b044298aceef34881c950efefdc7b64cb5db93e", + "comment": "Success case 1" + }, + { + "index": "1", + "point_g": "02464e351831efedb755223cabbf664f10564b4742c725c023034bc928ed339e0e", + "scalar_a": "f4e9172285393c6ada994c811b3e50fc47e96421ea7e54f4a4e459528d4cf562", + "point_b": "03fe589b0fa23f060f6d4d1e76b9b19d5bb3db0e56d39a4303913de0e706463008", + "aux_rand_r": "75f12482b9209dae12230ea1f8bf69723a1b447d361db8f510dd9ab33556fd4c", + "message": "76184ce9eea5b339ebf5304b57452c1ada1466610f0a58574d6c496798cee04b", + "result_proof": "be01fd4ec6ee4b08adb36ddb7b0290e09710f842f8623f1afc8c0ff00bdd6cee5256b10341c30d4f393d9b2c462b1534d06e2cff60920993c6dc240806576d80", + "comment": "Success case 2" + }, + { + "index": "2", + "point_g": "0222db2054fef98344352a13bc0304a71da7b5e9a2f7fd1f3c9f3519a3d9377fb7", + "scalar_a": "589476913e763b60d5c2a5bfb39230ec669caac1b44312e9bcd2d3f4473abfef", + "point_b": "03bc7a19970c812118f74ba659b491e00dade6096ff62d1afe032a92b8671498ed", + "aux_rand_r": "4da1c4c4b0f9db4eb6b2e5cb648d7e8a0aa35aa5c4ec4d07f096e0e03deca366", + "message": "66503623468a78cfcef47888c85e0010ecd897f441d263448bfc7a89b882ab20", + "result_proof": "a9e3603f2cb11c74dba678448cc5bc6ae6de372502392d1914e976229cb06c401f12bd03dbab57c2cd1a209adb51c14387f82e938a8a9d363fc8dc1e76456dac", + "comment": "Success case 3" + }, + { + "index": "3", + "point_g": "03dfa65bd3711eba75fa1996a0c1d95a4419bd835304152d9aa6efa590670f2af6", + "scalar_a": "24d0ed3fc189eb1b64e5dc9dd4af0f3c8c143b0c79cb5fcca0dfa08a11cc60a1", + "point_b": "03b51081323d38fb0b75f0c1ec6755fdb79c239c327ca11269fe68ba8a878b704e", + "aux_rand_r": "31a68d6db27f6404bbceff646ff1b26a34704a0105a36c5a845d0257cea19c9b", + "message": "f2996b3766d123a949e65541baf1d89d446360d05af51bd93f0445d8c472c952", + "result_proof": "0dbd32f1ecd950987bda4b163e5ea536e4e43e8e2f26bcf235ff799c12089f21d5a27f90f144aaaddf5a05390c44442aa13d9fbfec8cd53d3659942617ce5cb4", + "comment": "Success case 4" + }, + { + "index": "4", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "scalar_a": "73fffa796edb72d111b5e0bbda1608f098ac98120796f971b438691e1bfb7b96", + "point_b": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "aux_rand_r": "1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "comment": "Success case 5" + }, + { + "index": "5", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "scalar_a": "0000000000000000000000000000000000000000000000000000000000000000", + "point_b": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "aux_rand_r": "1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_proof": "INVALID", + "comment": "Failure case (a=0)" + }, + { + "index": "6", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "scalar_a": "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", + "point_b": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "aux_rand_r": "1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_proof": "INVALID", + "comment": "Failure case (a=N [group order])" + }, + { + "index": "7", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "scalar_a": "73fffa796edb72d111b5e0bbda1608f098ac98120796f971b438691e1bfb7b96", + "point_b": "INFINITY", + "aux_rand_r": "1cdfb4d7cce5e50783299896a471a44e6aa2c5e2100d6c37987c6b40503c6162", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_proof": "INVALID", + "comment": "Failure case (B is point at infinity)" + } +] \ No newline at end of file diff --git a/btcutil/silentpayments/testdata/test-vectors-dleq-verify.json b/btcutil/silentpayments/testdata/test-vectors-dleq-verify.json new file mode 100644 index 0000000000..ca49ad0b17 --- /dev/null +++ b/btcutil/silentpayments/testdata/test-vectors-dleq-verify.json @@ -0,0 +1,134 @@ +[ + { + "index": "0", + "point_g": "02cef38f55e78b321a1f785cb1c6e33dfcef9784c18bdc4e279801c449ccdfb88e", + "point_a": "02b540b22c2c5ef0dc886abdaad27498453d893265560bc08a187319af6f845f58", + "point_b": "02dad4b35c2379ba8334c9a5dda8f6e6d5cd575a7cc9d3ca4faaac51839daaa30f", + "point_c": "03fefe00951dcd0ef10b12523393c2b8113119de4fdeeab320694e96bdccd2775b", + "proof": "51ce8becf2726c8fed85957500de5d58e0349b8ed0fe40aee2c122288ee21f8fe4d1ca31f5e3c4833fb83a654b044298aceef34881c950efefdc7b64cb5db93e", + "message": "efb07d4b382d3da1079fbf24df623ba6c2e4c764993bbfa6dd7a4fe4aaf33859", + "result_success": "TRUE", + "comment": "Success case 1" + }, + { + "index": "1", + "point_g": "02464e351831efedb755223cabbf664f10564b4742c725c023034bc928ed339e0e", + "point_a": "032baaf1b10845a51b551196984a91efe2adf9d41b92bec3927218e6e4ca344002", + "point_b": "03fe589b0fa23f060f6d4d1e76b9b19d5bb3db0e56d39a4303913de0e706463008", + "point_c": "031f59aa1df22190e00380d8c5941adf899f596593765a1251005fd24f2bf7c884", + "proof": "be01fd4ec6ee4b08adb36ddb7b0290e09710f842f8623f1afc8c0ff00bdd6cee5256b10341c30d4f393d9b2c462b1534d06e2cff60920993c6dc240806576d80", + "message": "76184ce9eea5b339ebf5304b57452c1ada1466610f0a58574d6c496798cee04b", + "result_success": "TRUE", + "comment": "Success case 2" + }, + { + "index": "2", + "point_g": "0222db2054fef98344352a13bc0304a71da7b5e9a2f7fd1f3c9f3519a3d9377fb7", + "point_a": "026aa26fcd626f8f55295859e9f8dd1f103149dd64d77c2bbba1bcf33bb37ebaa2", + "point_b": "03bc7a19970c812118f74ba659b491e00dade6096ff62d1afe032a92b8671498ed", + "point_c": "035628d1a69910daef614c7cae68d71ece55c5908af2360629e25c1b7de21eeb4b", + "proof": "a9e3603f2cb11c74dba678448cc5bc6ae6de372502392d1914e976229cb06c401f12bd03dbab57c2cd1a209adb51c14387f82e938a8a9d363fc8dc1e76456dac", + "message": "66503623468a78cfcef47888c85e0010ecd897f441d263448bfc7a89b882ab20", + "result_success": "TRUE", + "comment": "Success case 3" + }, + { + "index": "3", + "point_g": "03dfa65bd3711eba75fa1996a0c1d95a4419bd835304152d9aa6efa590670f2af6", + "point_a": "031bf61ba89009ee1266c9003a72e8e07d77877678ccda7f15325aadcd64ed186b", + "point_b": "03b51081323d38fb0b75f0c1ec6755fdb79c239c327ca11269fe68ba8a878b704e", + "point_c": "02d1b1f37a80217ba73785babfa63251052775f9d3ca65060054033288b7a3f66b", + "proof": "0dbd32f1ecd950987bda4b163e5ea536e4e43e8e2f26bcf235ff799c12089f21d5a27f90f144aaaddf5a05390c44442aa13d9fbfec8cd53d3659942617ce5cb4", + "message": "f2996b3766d123a949e65541baf1d89d446360d05af51bd93f0445d8c472c952", + "result_success": "TRUE", + "comment": "Success case 4" + }, + { + "index": "4", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "point_b": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "point_c": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "TRUE", + "comment": "Success case 5" + }, + { + "index": "5", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "point_b": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "point_c": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "FALSE", + "comment": "Swapped points case 1" + }, + { + "index": "6", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "point_b": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "point_c": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "FALSE", + "comment": "Swapped points case 2" + }, + { + "index": "7", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "point_b": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "point_c": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "FALSE", + "comment": "Swapped points case 3" + }, + { + "index": "8", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "point_b": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "point_c": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "FALSE", + "comment": "Swapped points case 4" + }, + { + "index": "9", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "point_b": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "point_c": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "FALSE", + "comment": "Swapped points case 5" + }, + { + "index": "10", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "point_b": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "point_c": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "proof": "5bd36d18c6e75e50f1fbbaa7596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139ffe2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "FALSE", + "comment": "Tampered proof (random bit-flip)" + }, + { + "index": "11", + "point_g": "02b15de5a3aefcfe2473916c76e619b5800ac7250ef93a9e6e0dd1505104fc58e7", + "point_a": "0296c8e00dda60bb5565b77371ff913091978646b58ccf218bc591f68a75232e6e", + "point_b": "03a4692be176ff89a972de9cc407083096847b950d1cae72b947665a3d5f4c2f01", + "point_c": "03a7a7f9527fd387c2b2ce0c76669d646c78a3b470a4b34d3a2dabafc8505ef472", + "proof": "5bd36d18c6e75e50f1fbba27596591ef3506b841ce65c8f3489fa1b31e074f0f511400ad72a06b023727809c6c16c78c5d53ff14e848184462cd357660894d3d", + "message": "0ceb45f560f2cf6b76a139f7e2c47c5ca6d26d6a3a210e59f197413bbec040b4", + "result_success": "FALSE", + "comment": "Tampered message (random bit-flip)" + } +] \ No newline at end of file From 3b70eb5b47078502dc8f92fe1a0e1bcea3af4ba9 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 28 Dec 2024 17:04:40 +0100 Subject: [PATCH 4/6] psbt: add global silent payment shares --- btcutil/psbt/bip32.go | 5 ++ btcutil/psbt/psbt.go | 71 +++++++++++++----- btcutil/psbt/silentpayments.go | 127 +++++++++++++++++++++++++++++++++ btcutil/psbt/types.go | 4 ++ 4 files changed, 191 insertions(+), 16 deletions(-) create mode 100644 btcutil/psbt/silentpayments.go diff --git a/btcutil/psbt/bip32.go b/btcutil/psbt/bip32.go index 96a3f67274..dc2d7f37fb 100644 --- a/btcutil/psbt/bip32.go +++ b/btcutil/psbt/bip32.go @@ -5,6 +5,11 @@ import ( "encoding/binary" ) +const ( + // uint32Size is the size of a uint32 in bytes. + uint32Size = 4 +) + // Bip32Derivation encapsulates the data for the input and output // Bip32Derivation key-value fields. // diff --git a/btcutil/psbt/psbt.go b/btcutil/psbt/psbt.go index a72b7fc551..5db983b74f 100644 --- a/btcutil/psbt/psbt.go +++ b/btcutil/psbt/psbt.go @@ -135,6 +135,10 @@ type Packet struct { // produced by this PSBT. Outputs []POutput + // SilentPaymentShares is a list of ECDH shares that are used to derive + // the shared secret for a silent payment. + SilentPaymentShares []SilentPaymentShare + // Unknowns are the set of custom types (global only) within this PSBT. Unknowns []*Unknown } @@ -161,13 +165,15 @@ func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) { inSlice := make([]PInput, len(tx.TxIn)) outSlice := make([]POutput, len(tx.TxOut)) + spSlice := make([]SilentPaymentShare, 0) unknownSlice := make([]*Unknown, 0) return &Packet{ - UnsignedTx: tx, - Inputs: inSlice, - Outputs: outSlice, - Unknowns: unknownSlice, + UnsignedTx: tx, + Inputs: inSlice, + Outputs: outSlice, + SilentPaymentShares: spSlice, + Unknowns: unknownSlice, }, nil } @@ -230,7 +236,10 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { // Next we parse any unknowns that may be present, making sure that we // break at the separator. - var unknownSlice []*Unknown + var ( + spSlice []SilentPaymentShare + unknownSlice []*Unknown + ) for { keyint, keydata, err := getKey(r) if err != nil { @@ -247,14 +256,32 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { return nil, err } - keyintanddata := []byte{byte(keyint)} - keyintanddata = append(keyintanddata, keydata...) - - newUnknown := &Unknown{ - Key: keyintanddata, - Value: value, + switch GlobalType(keyint) { + case SilentPaymentShareType: + share, err := ReadSilentPaymentShare(keydata, value) + if err != nil { + return nil, err + } + + // Duplicate keys are not allowed. + for _, x := range spSlice { + if x.EqualKey(share) { + return nil, ErrDuplicateKey + } + } + + spSlice = append(spSlice, *share) + + default: + keyintanddata := []byte{byte(keyint)} + keyintanddata = append(keyintanddata, keydata...) + + newUnknown := &Unknown{ + Key: keyintanddata, + Value: value, + } + unknownSlice = append(unknownSlice, newUnknown) } - unknownSlice = append(unknownSlice, newUnknown) } // Next we parse the INPUT section. @@ -283,10 +310,11 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { // Populate the new Packet object. newPsbt := Packet{ - UnsignedTx: msgTx, - Inputs: inSlice, - Outputs: outSlice, - Unknowns: unknownSlice, + UnsignedTx: msgTx, + Inputs: inSlice, + Outputs: outSlice, + SilentPaymentShares: spSlice, + Unknowns: unknownSlice, } // Extended sanity checking is applied here to make sure the @@ -325,6 +353,17 @@ func (p *Packet) Serialize(w io.Writer) error { return err } + // Serialize the global silent payment shares. + for _, share := range p.SilentPaymentShares { + keyBytes, valueBytes := SerializeSilentPaymentShare(&share) + err := serializeKVPairWithType( + w, uint8(SilentPaymentShareType), keyBytes, valueBytes, + ) + if err != nil { + return err + } + } + // Unknown is a special case; we don't have a key type, only a key and // a value field for _, kv := range p.Unknowns { diff --git a/btcutil/psbt/silentpayments.go b/btcutil/psbt/silentpayments.go new file mode 100644 index 0000000000..b593b41ce0 --- /dev/null +++ b/btcutil/psbt/silentpayments.go @@ -0,0 +1,127 @@ +package psbt + +import ( + "bytes" + "crypto/sha256" + "encoding/binary" + "sort" + + "github.com/btcsuite/btcd/wire" + secp "github.com/decred/dcrd/dcrec/secp256k1/v4" +) + +const ( + // outPointSize is the serialized size of an outpoint in bytes. + outPointSize = sha256.Size + uint32Size +) + +// SilentPaymentShare is a single ECDH share for a silent payment. +type SilentPaymentShare struct { + // ScanKey is the silent payment recipient's scan key. + ScanKey []byte + + // OutPoints is the list of outpoints the share is for. + OutPoints []wire.OutPoint + + // Share is the sum of all ECDH shares for the outpoints. + Share []byte +} + +// EqualKey returns true if this silent payment share's key data is the same as +// the given silent payment share. +func (s *SilentPaymentShare) EqualKey(other *SilentPaymentShare) bool { + if !bytes.Equal(s.ScanKey, other.ScanKey) { + return false + } + + return equalOutPoints(s.OutPoints, other.OutPoints) +} + +// equalOutPoints returns true if the given outpoints are equal. +func equalOutPoints(a, b []wire.OutPoint) bool { + if len(a) != len(b) { + return false + } + + sort.Slice(a, func(i, j int) bool { + opI := a[i] + opJ := a[j] + if opI.Hash == opJ.Hash { + return opI.Index < opJ.Index + } + + return bytes.Compare(opI.Hash[:], opJ.Hash[:]) < 0 + }) + sort.Slice(b, func(i, j int) bool { + opI := b[i] + opJ := b[j] + if opI.Hash == opJ.Hash { + return opI.Index < opJ.Index + } + + return bytes.Compare(opI.Hash[:], opJ.Hash[:]) < 0 + }) + + for i, outPoint := range a { + if outPoint != b[i] { + return false + } + } + + return true +} + +// ReadSilentPaymentShare deserializes a silent payment share from the given key +// data and value. +func ReadSilentPaymentShare(keyData, + value []byte) (*SilentPaymentShare, error) { + + // There must be at least the scan key in the key data. + if len(keyData) < secp.PubKeyBytesLenCompressed { + return nil, ErrInvalidKeyData + } + + // The remaining bytes of the key data are outpoints. + outPointsLen := len(keyData) - secp.PubKeyBytesLenCompressed + if outPointsLen%outPointSize != 0 { + return nil, ErrInvalidKeyData + } + + // The share must be a public key. + if len(value) != secp.PubKeyBytesLenCompressed { + return nil, ErrInvalidPsbtFormat + } + + share := &SilentPaymentShare{ + ScanKey: keyData[:secp.PubKeyBytesLenCompressed], + OutPoints: make([]wire.OutPoint, outPointsLen/outPointSize), + Share: value, + } + + for i := 0; i < outPointsLen; i += outPointSize { + idx := i / outPointSize + copy(share.OutPoints[idx].Hash[:], keyData[i:i+sha256.Size]) + share.OutPoints[idx].Index = binary.LittleEndian.Uint32( + keyData[i+sha256.Size : i+sha256.Size+uint32Size], + ) + } + + return share, nil +} + +// SerializeSilentPaymentShare serializes a silent payment share to key data and +// value. +func SerializeSilentPaymentShare(share *SilentPaymentShare) ([]byte, []byte) { + keyData := make( + []byte, 0, len(share.ScanKey)+len(share.OutPoints)*outPointSize, + ) + keyData = append(keyData, share.ScanKey...) + for _, outPoint := range share.OutPoints { + keyData = append(keyData, outPoint.Hash[:]...) + var idx [4]byte + binary.LittleEndian.PutUint32(idx[:], outPoint.Index) + keyData = append(keyData, idx[:]...) + } + + return keyData, share.Share +} diff --git a/btcutil/psbt/types.go b/btcutil/psbt/types.go index e833e1af35..723b8be317 100644 --- a/btcutil/psbt/types.go +++ b/btcutil/psbt/types.go @@ -30,6 +30,10 @@ const ( // extended public key. XpubType GlobalType = 1 + // SilentPaymentShareType is used to house the ECDH shares for silent + // payments. + SilentPaymentShareType GlobalType = 0x07 + // VersionType houses the global version number of this PSBT. There is // no key (only contains the byte type), then the value if omitted, is // assumed to be zero. From 2885f6721e4b7ecfd1ebf38d83514b7bf96550cd Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 28 Dec 2024 17:14:10 +0100 Subject: [PATCH 5/6] psbt: add global silent payment DLEQ proofs --- btcutil/psbt/psbt.go | 46 ++++++++++++++++++--- btcutil/psbt/silentpayments.go | 75 ++++++++++++++++++++++++++++++++++ btcutil/psbt/types.go | 4 ++ 3 files changed, 119 insertions(+), 6 deletions(-) diff --git a/btcutil/psbt/psbt.go b/btcutil/psbt/psbt.go index 5db983b74f..3f11e4c3ae 100644 --- a/btcutil/psbt/psbt.go +++ b/btcutil/psbt/psbt.go @@ -139,6 +139,10 @@ type Packet struct { // the shared secret for a silent payment. SilentPaymentShares []SilentPaymentShare + // SilentPaymentDLEQs is a list of DLEQ proofs that are used to prove + // the validity of the shares for a silent payment. + SilentPaymentDLEQs []SilentPaymentDLEQ + // Unknowns are the set of custom types (global only) within this PSBT. Unknowns []*Unknown } @@ -165,14 +169,16 @@ func NewFromUnsignedTx(tx *wire.MsgTx) (*Packet, error) { inSlice := make([]PInput, len(tx.TxIn)) outSlice := make([]POutput, len(tx.TxOut)) - spSlice := make([]SilentPaymentShare, 0) + spsSlice := make([]SilentPaymentShare, 0) + spdSlice := make([]SilentPaymentDLEQ, 0) unknownSlice := make([]*Unknown, 0) return &Packet{ UnsignedTx: tx, Inputs: inSlice, Outputs: outSlice, - SilentPaymentShares: spSlice, + SilentPaymentShares: spsSlice, + SilentPaymentDLEQs: spdSlice, Unknowns: unknownSlice, }, nil } @@ -237,7 +243,8 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { // Next we parse any unknowns that may be present, making sure that we // break at the separator. var ( - spSlice []SilentPaymentShare + spsSlice []SilentPaymentShare + spdSlice []SilentPaymentDLEQ unknownSlice []*Unknown ) for { @@ -264,13 +271,28 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { } // Duplicate keys are not allowed. - for _, x := range spSlice { + for _, x := range spsSlice { if x.EqualKey(share) { return nil, ErrDuplicateKey } } - spSlice = append(spSlice, *share) + spsSlice = append(spsSlice, *share) + + case SilentPaymentDLEQType: + proof, err := ReadSilentPaymentDLEQ(keydata, value) + if err != nil { + return nil, err + } + + // Duplicate keys are not allowed. + for _, x := range spdSlice { + if x.EqualKey(proof) { + return nil, ErrDuplicateKey + } + } + + spdSlice = append(spdSlice, *proof) default: keyintanddata := []byte{byte(keyint)} @@ -313,7 +335,8 @@ func NewFromRawBytes(r io.Reader, b64 bool) (*Packet, error) { UnsignedTx: msgTx, Inputs: inSlice, Outputs: outSlice, - SilentPaymentShares: spSlice, + SilentPaymentShares: spsSlice, + SilentPaymentDLEQs: spdSlice, Unknowns: unknownSlice, } @@ -364,6 +387,17 @@ func (p *Packet) Serialize(w io.Writer) error { } } + // Serialize the global silent payment DLEQ proofs. + for _, dleq := range p.SilentPaymentDLEQs { + keyBytes, valueBytes := SerializeSilentPaymentDLEQ(&dleq) + err := serializeKVPairWithType( + w, uint8(SilentPaymentDLEQType), keyBytes, valueBytes, + ) + if err != nil { + return err + } + } + // Unknown is a special case; we don't have a key type, only a key and // a value field for _, kv := range p.Unknowns { diff --git a/btcutil/psbt/silentpayments.go b/btcutil/psbt/silentpayments.go index b593b41ce0..d62d85360e 100644 --- a/btcutil/psbt/silentpayments.go +++ b/btcutil/psbt/silentpayments.go @@ -125,3 +125,78 @@ func SerializeSilentPaymentShare(share *SilentPaymentShare) ([]byte, []byte) { return keyData, share.Share } + +// SilentPaymentDLEQ is a DLEQ proof for a silent payment share. +type SilentPaymentDLEQ struct { + // ScanKey is the silent payment recipient's scan key. + ScanKey []byte + + // OutPoints is the list of outpoints the share proof is for. + OutPoints []wire.OutPoint + + // Proof is the DLEQ proof for the share with the same key. + Proof []byte +} + +// EqualKey returns true if this silent payment DLEQ's key data is the same as +// the given silent payment DLEQ. +func (d *SilentPaymentDLEQ) EqualKey(other *SilentPaymentDLEQ) bool { + if !bytes.Equal(d.ScanKey, other.ScanKey) { + return false + } + + return equalOutPoints(d.OutPoints, other.OutPoints) +} + +// ReadSilentPaymentDLEQ deserializes a silent payment DLEQ proof from the given +// key data and value. +func ReadSilentPaymentDLEQ(keyData, value []byte) (*SilentPaymentDLEQ, error) { + // There must be at least the scan key in the key data. + if len(keyData) < secp.PubKeyBytesLenCompressed { + return nil, ErrInvalidKeyData + } + + // The remaining bytes of the key data are outpoints. + outPointsLen := len(keyData) - secp.PubKeyBytesLenCompressed + if outPointsLen%outPointSize != 0 { + return nil, ErrInvalidKeyData + } + + // The proof must be 64 bytes. + if len(value) != 64 { + return nil, ErrInvalidPsbtFormat + } + + share := &SilentPaymentDLEQ{ + ScanKey: keyData[:secp.PubKeyBytesLenCompressed], + OutPoints: make([]wire.OutPoint, outPointsLen/outPointSize), + Proof: value, + } + + for i := 0; i < outPointsLen; i += outPointSize { + idx := i / outPointSize + copy(share.OutPoints[idx].Hash[:], keyData[i:i+sha256.Size]) + share.OutPoints[idx].Index = binary.LittleEndian.Uint32( + keyData[i+sha256.Size : i+sha256.Size+uint32Size], + ) + } + + return share, nil +} + +// SerializeSilentPaymentDLEQ serializes a silent payment DLEQ proof to key data +// and value. +func SerializeSilentPaymentDLEQ(dleq *SilentPaymentDLEQ) ([]byte, []byte) { + keyData := make( + []byte, 0, len(dleq.ScanKey)+len(dleq.OutPoints)*outPointSize, + ) + keyData = append(keyData, dleq.ScanKey...) + for _, outPoint := range dleq.OutPoints { + keyData = append(keyData, outPoint.Hash[:]...) + var idx [4]byte + binary.LittleEndian.PutUint32(idx[:], outPoint.Index) + keyData = append(keyData, idx[:]...) + } + + return keyData, dleq.Proof +} diff --git a/btcutil/psbt/types.go b/btcutil/psbt/types.go index 723b8be317..817eb67835 100644 --- a/btcutil/psbt/types.go +++ b/btcutil/psbt/types.go @@ -33,6 +33,10 @@ const ( // SilentPaymentShareType is used to house the ECDH shares for silent // payments. SilentPaymentShareType GlobalType = 0x07 + + // SilentPaymentDLEQType is used to house the DLEQ proofs for silent + // payments. + SilentPaymentDLEQType GlobalType = 0x08 // VersionType houses the global version number of this PSBT. There is // no key (only contains the byte type), then the value if omitted, is From 996c4d38d345c3dd57f27d6686490b70c06726a9 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sat, 28 Dec 2024 17:25:19 +0100 Subject: [PATCH 6/6] psbt: add output silent payment info --- btcutil/psbt/partial_output.go | 23 ++++++++++++++++++ btcutil/psbt/silentpayments.go | 43 ++++++++++++++++++++++++++++++++++ btcutil/psbt/types.go | 4 ++++ 3 files changed, 70 insertions(+) diff --git a/btcutil/psbt/partial_output.go b/btcutil/psbt/partial_output.go index 86e476457d..9698fcf79f 100644 --- a/btcutil/psbt/partial_output.go +++ b/btcutil/psbt/partial_output.go @@ -17,6 +17,7 @@ type POutput struct { TaprootInternalKey []byte TaprootTapTree []byte TaprootBip32Derivation []*TaprootBip32Derivation + SilentPaymentInfo *SilentPaymentInfo Unknowns []*Unknown } @@ -144,6 +145,18 @@ func (po *POutput) deserialize(r io.Reader) error { po.TaprootBip32Derivation, taprootDerivation, ) + case SilentPaymentV0InfoOutputType: + if po.SilentPaymentInfo != nil { + return ErrDuplicateKey + } + + info, err := ReadSilentPaymentInfo(value) + if err != nil { + return err + } + + po.SilentPaymentInfo = info + default: // A fall through case for any proprietary types. keyCodeAndData := append( @@ -246,6 +259,16 @@ func (po *POutput) serialize(w io.Writer) error { } } + if po.SilentPaymentInfo != nil { + err := serializeKVPairWithType( + w, uint8(SilentPaymentV0InfoOutputType), nil, + SerializeSilentPaymentInfo(po.SilentPaymentInfo), + ) + if err != nil { + return err + } + } + // Unknown is a special case; we don't have a key type, only a key and // a value field for _, kv := range po.Unknowns { diff --git a/btcutil/psbt/silentpayments.go b/btcutil/psbt/silentpayments.go index d62d85360e..4bd7581ea8 100644 --- a/btcutil/psbt/silentpayments.go +++ b/btcutil/psbt/silentpayments.go @@ -6,6 +6,7 @@ import ( "encoding/binary" "sort" + "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" secp "github.com/decred/dcrd/dcrec/secp256k1/v4" ) @@ -15,6 +16,16 @@ const ( outPointSize = sha256.Size + uint32Size ) +var ( + // SilentPaymentDummyP2TROutput is a dummy P2TR output that can be used + // to mark a transaction output as a silent payment recipient output to + // which the final Taproot output key hasn't been calculated yet. + SilentPaymentDummyP2TROutput = append( + []byte{txscript.OP_1, txscript.OP_DATA_32}, + bytes.Repeat([]byte{0x00}, 32)..., + ) +) + // SilentPaymentShare is a single ECDH share for a silent payment. type SilentPaymentShare struct { // ScanKey is the silent payment recipient's scan key. @@ -200,3 +211,35 @@ func SerializeSilentPaymentDLEQ(dleq *SilentPaymentDLEQ) ([]byte, []byte) { return keyData, dleq.Proof } + +// SilentPaymentInfo is the information needed to create a silent payment +// recipient output. +type SilentPaymentInfo struct { + // ScanKey is the silent payment recipient's scan key. + ScanKey []byte + + // SpendKey is the silent payment recipient's spend key. + SpendKey []byte +} + +// ReadSilentPaymentInfo deserializes a silent payment info from the given +// value. +func ReadSilentPaymentInfo(value []byte) (*SilentPaymentInfo, error) { + if len(value) != secp.PubKeyBytesLenCompressed*2 { + return nil, ErrInvalidPsbtFormat + } + + return &SilentPaymentInfo{ + ScanKey: value[:secp.PubKeyBytesLenCompressed], + SpendKey: value[secp.PubKeyBytesLenCompressed:], + }, nil +} + +// SerializeSilentPaymentInfo serializes a silent payment info to value. +func SerializeSilentPaymentInfo(info *SilentPaymentInfo) []byte { + value := make([]byte, 0, secp.PubKeyBytesLenCompressed*2) + value = append(value, info.ScanKey...) + value = append(value, info.SpendKey...) + + return value +} diff --git a/btcutil/psbt/types.go b/btcutil/psbt/types.go index 817eb67835..a2c809e5b6 100644 --- a/btcutil/psbt/types.go +++ b/btcutil/psbt/types.go @@ -208,4 +208,8 @@ const ( // followed by said number of 32-byte leaf hashes. The rest of the value // is then identical to the Bip32DerivationInputType value. TaprootBip32DerivationOutputType OutputType = 7 + + // SilentPaymentV0InfoOutputType is used to house the silent payment + // recipient information for a version 0 silent payment output. + SilentPaymentV0InfoOutputType OutputType = 0x09 )