From 318986da9cb03e4c18da2e6c0daf3e62aef7eb72 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 5 Feb 2024 11:58:51 -0300 Subject: [PATCH 01/53] feat: Cake update v1 --- README.md | 28 ++-- .../lib/bitcoin_cash/burn_token_example.dart | 6 +- .../create_cash_token_example.dart | 6 +- .../lib/bitcoin_cash/create_nft_example.dart | 6 +- .../lib/bitcoin_cash/make_vout0_example.dart | 6 +- .../lib/bitcoin_cash/minting_nft_example.dart | 4 +- .../old_examples/old_example.dart | 16 +- .../bitcoin_cash/p2sh32_spend_example.dart | 6 +- .../bitcoin_cash/send_ft_token_example.dart | 6 +- .../bitcoin_cash/transfer_bch_example.dart | 6 +- example/lib/global/bch_example.dart | 6 +- .../global/old_examples/bitcoin_example.dart | 26 ++-- .../old_examples/dash_example/dash.dart | 8 +- .../doge_example/doge_example.dart | 8 +- .../litecoin_example/litecoin_example.dart | 16 +- .../spending_builders.dart | 21 ++- .../spending_single_type.dart | 26 ++-- .../multi_sig_transactions.dart | 8 +- .../transaction_builder_example.dart | 8 +- ...er_from_7_account_to_6_accout_example.dart | 22 +-- .../global/transfer_to_8_account_example.dart | 24 +-- example/lib/main.dart | 16 +- lib/bitcoin_base.dart | 2 + lib/src/bitcoin/address/address.dart | 1 + lib/src/bitcoin/address/core.dart | 20 +-- lib/src/bitcoin/address/legacy_address.dart | 138 +++++++++++++----- lib/src/bitcoin/address/segwit_address.dart | 94 ++++++------ lib/src/bitcoin/address/util.dart | 35 +++++ .../bitcoin/address/utils/address_utils.dart | 16 +- lib/src/bitcoin/script/input.dart | 10 +- lib/src/bitcoin/script/op_code/constant.dart | 123 +++++++++++++++- lib/src/bitcoin/script/script.dart | 77 ++++++++-- lib/src/crypto/keypair/ec_public.dart | 81 +++++----- lib/src/provider/models/multisig_script.dart | 8 +- lib/src/provider/models/utxo_details.dart | 2 +- .../forked_transaction_builder.dart | 30 ++-- .../transaction_builder.dart | 44 +++--- lib/src/utils/script.dart | 36 +++++ test/keys_test.dart | 22 +-- test/p2sh_test.dart | 4 +- test/p2wpkh_test.dart | 4 +- test/p2wsh_test.dart | 6 +- 42 files changed, 668 insertions(+), 364 deletions(-) create mode 100644 lib/src/bitcoin/address/util.dart create mode 100644 lib/src/utils/script.dart diff --git a/README.md b/README.md index 16be210..35e4bbf 100644 --- a/README.md +++ b/README.md @@ -134,10 +134,10 @@ We have integrated three APIs—Mempool, BlockCypher, and Electrum—into the pl final publicKey = ECPublic.fromHex('.....'); // Generate a Pay-to-Public-Key-Hash (P2PKH) address from the public key. - final p2pkh = publicKey.toAddress(); + final p2pkh = publicKey.toP2pkhAddress(); // Generate a Pay-to-Witness-Public-Key-Hash (P2WPKH) Segregated Witness (SegWit) address from the public key. - final p2wpkh = publicKey.toSegwitAddress(); + final p2wpkh = publicKey.toP2wpkhAddress(); // Generate a Pay-to-Witness-Script-Hash (P2WSH) Segregated Witness (SegWit) address from the public key. final p2wsh = publicKey.toP2wshAddress(); @@ -274,8 +274,8 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li final privateKey = ECPrivate.fromHex( "76257aafc9b954351c7f6445b2d07277f681a5e83d515a1f32ebf54989c2af4f"); final examplePublicKey = privateKey.getPublic(); - final spender1 = examplePublicKey.toAddress(); - final spender2 = examplePublicKey.toSegwitAddress(); + final spender1 = examplePublicKey.toP2pkhAddress(); + final spender2 = examplePublicKey.toP2wpkhAddress(); final spender3 = examplePublicKey.toTaprootAddress(); final spender4 = examplePublicKey.toP2pkhInP2sh(); final spender5 = examplePublicKey.toP2pkInP2sh(); @@ -324,9 +324,9 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li /// P2pkhAddress.fromAddress(address: ".....", network: network); /// P2trAddress.fromAddress(address: "....", network: network) /// .... - final List outPuts = [ + final List outputs = [ BitcoinOutput( - address: examplePublicKey2.toSegwitAddress(), + address: examplePublicKey2.toP2wpkhAddress(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( address: examplePublicKey2.toTaprootAddress(), @@ -353,11 +353,11 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li int transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: accountsUtxos, outputs: [ - ...outPuts, + ...outputs, /// I add more output for change value to get correct transaction size BitcoinOutput( - address: examplePublicKey2.toAddress(), value: BigInt.zero) + address: examplePublicKey2.toP2pkhAddress(), value: BigInt.zero) ], /// network @@ -384,13 +384,13 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li } //// if we have change value we back amount to account if (changeValue > BigInt.zero) { - outPuts.add(BitcoinOutput( - address: examplePublicKey2.toAddress(), value: changeValue)); + outputs.add(BitcoinOutput( + address: examplePublicKey2.toP2pkhAddress(), value: changeValue)); } /// create transaction builder final builder = BitcoinTransactionBuilder( - outPuts: outPuts, + outputs: outputs, fee: fee, network: network, utxos: accountsUtxos, @@ -452,7 +452,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li /// p2pkh with token address () final receiver1 = P2pkhAddress.fromHash160( - addrHash: publicKey.toAddress().addressProgram, + h160: publicKey.toP2pkhAddress().addressProgram, type: P2pkhAddressType.p2pkhwt); /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. @@ -501,7 +501,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li previousValue + element.utxo.token!.amount); final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ /// change address for bch values (sum of bch amout - (outputs amount + fee)) BitcoinOutput( address: p2pkhAddress.baseAddress, @@ -662,7 +662,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li // index of utxo txInIndex: i, // spender script pub key - script: utxo[i].public().toAddress().toScriptPubKey(), + script: utxo[i].public().toP2pkhAddress().toScriptPubKey(), ); // sign transaction diff --git a/example/lib/bitcoin_cash/burn_token_example.dart b/example/lib/bitcoin_cash/burn_token_example.dart index 0b3f005..63b33df 100644 --- a/example/lib/bitcoin_cash/burn_token_example.dart +++ b/example/lib/bitcoin_cash/burn_token_example.dart @@ -29,10 +29,10 @@ void main() async { /// p2pkh with token address () final receiver1 = P2pkhAddress.fromHash160( - addrHash: publicKey.toAddress().addressProgram, + h160: publicKey.toP2pkhAddress().addressProgram, type: P2pkhAddressType.p2pkhwt); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), @@ -80,7 +80,7 @@ void main() async { previousValue + element.utxo.token!.amount); final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ /// change address for bch values (sum of bch amout - (outputs amount + fee)) BitcoinOutput( address: p2pkhAddress.baseAddress, diff --git a/example/lib/bitcoin_cash/create_cash_token_example.dart b/example/lib/bitcoin_cash/create_cash_token_example.dart index 06aa954..170a4eb 100644 --- a/example/lib/bitcoin_cash/create_cash_token_example.dart +++ b/example/lib/bitcoin_cash/create_cash_token_example.dart @@ -25,9 +25,9 @@ void main() async { /// Derives a P2PKH address from the given public key and converts it to a Bitcoin Cash address /// for enhanced accessibility within the network. - final p2pkhAddress = publicKey.toAddress(); + final p2pkhAddress = publicKey.toP2pkhAddress(); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.pubKeyHash(), @@ -65,7 +65,7 @@ void main() async { // print("vout $vout0Hash"); // return; final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ BitcoinTokenOutput( address: p2pkhAddress, diff --git a/example/lib/bitcoin_cash/create_nft_example.dart b/example/lib/bitcoin_cash/create_nft_example.dart index e999dfc..2ccb20b 100644 --- a/example/lib/bitcoin_cash/create_nft_example.dart +++ b/example/lib/bitcoin_cash/create_nft_example.dart @@ -26,9 +26,9 @@ void main() async { /// Derives a P2PKH address from the given public key and converts it to a Bitcoin Cash address /// for enhanced accessibility within the network. final p2pkhAddress = - BitcoinCashAddress.fromBaseAddress(publicKey.toAddress()); + BitcoinCashAddress.fromBaseAddress(publicKey.toP2pkhAddress()); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), @@ -65,7 +65,7 @@ void main() async { return; } final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ BitcoinOutput( address: p2pkhAddress.baseAddress, value: sumOfUtxo - diff --git a/example/lib/bitcoin_cash/make_vout0_example.dart b/example/lib/bitcoin_cash/make_vout0_example.dart index e7af23f..868f1ea 100644 --- a/example/lib/bitcoin_cash/make_vout0_example.dart +++ b/example/lib/bitcoin_cash/make_vout0_example.dart @@ -26,9 +26,9 @@ void main() async { /// Derives a P2PKH address from the given public key and converts it to a Bitcoin Cash address /// for enhanced accessibility within the network. - final p2pkhAddress = publicKey.toAddress(); + final p2pkhAddress = publicKey.toP2pkhAddress(); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.pubKeyHash(), @@ -47,7 +47,7 @@ void main() async { final sumOfUtxo = utxos.sumOfUtxosValue(); final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ BitcoinOutput( address: p2pkhAddress, value: sumOfUtxo - BtcUtils.toSatoshi("0.00003"), diff --git a/example/lib/bitcoin_cash/minting_nft_example.dart b/example/lib/bitcoin_cash/minting_nft_example.dart index 484ae74..1dca92c 100644 --- a/example/lib/bitcoin_cash/minting_nft_example.dart +++ b/example/lib/bitcoin_cash/minting_nft_example.dart @@ -29,7 +29,7 @@ void main() async { final p2pkhAddress = BitcoinCashAddress.fromBaseAddress( publicKey.toP2pkInP2sh(useBCHP2sh32: true)); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), @@ -55,7 +55,7 @@ void main() async { "3f0d87791e5996aaddbce16c12651dd8b5b881cf7338340504bb7b2c6c08bfc4"; final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ BitcoinOutput( address: p2pkhAddress.baseAddress, value: sumOfUtxo - diff --git a/example/lib/bitcoin_cash/old_examples/old_example.dart b/example/lib/bitcoin_cash/old_examples/old_example.dart index 15403f5..7f534ce 100644 --- a/example/lib/bitcoin_cash/old_examples/old_example.dart +++ b/example/lib/bitcoin_cash/old_examples/old_example.dart @@ -101,7 +101,7 @@ void _spendFrom2P2SHAnd2P2PKHAddress() async { ]); final b = ForkedTransactionBuilder( - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the P2shAddress and a value of 0.01 BCH BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("0.01")), @@ -121,7 +121,7 @@ void _spendFrom2P2SHAnd2P2PKHAddress() async { /// Specify the network for the litcoin transaction network: network, - /// Define a list of Unspent Transaction Outputs (UTXOs) for the Bitcoin transaction + /// Define a list of Unspent Transaction outputs (UTXOs) for the Bitcoin transaction utxos: [ UtxoWithAddress( @@ -138,13 +138,13 @@ void _spendFrom2P2SHAnd2P2PKHAddress() async { vout: 0, /// Script type indicates the type of script associated with the UTXO's address - scriptType: examplePublicKey2.toAddress().type, + scriptType: examplePublicKey2.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey2.toHex(), - address: examplePublicKey2.toAddress())), + address: examplePublicKey2.toP2pkhAddress())), ]); /// Build the transaction by invoking the buildTransaction method on the ForkedTransactionBuilder @@ -247,7 +247,7 @@ void _spendFrom2P2SHAnd1P2PKHAddress() async { final b = ForkedTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the P2pkhAddress and a value of 0.01 BCH BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("0.01")), @@ -267,7 +267,7 @@ void _spendFrom2P2SHAnd1P2PKHAddress() async { /// Add a memo to the transaction, linking to the GitHub repository memo: "https://github.com/mrtnetwork", - /// Define a list of Unspent Transaction Outputs (UTXOs) for the Bitcoin transaction + /// Define a list of Unspent Transaction outputs (UTXOs) for the Bitcoin transaction utxos: [ UtxoWithAddress( @@ -324,13 +324,13 @@ void _spendFrom2P2SHAnd1P2PKHAddress() async { vout: 2, /// Script type indicates the type of script associated with the UTXO's address - scriptType: examplePublicKey.toAddress().type, + scriptType: examplePublicKey.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey.toHex(), - address: examplePublicKey.toAddress())), + address: examplePublicKey.toP2pkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( /// Transaction hash uniquely identifies the referenced transaction diff --git a/example/lib/bitcoin_cash/p2sh32_spend_example.dart b/example/lib/bitcoin_cash/p2sh32_spend_example.dart index afb771b..aadff93 100644 --- a/example/lib/bitcoin_cash/p2sh32_spend_example.dart +++ b/example/lib/bitcoin_cash/p2sh32_spend_example.dart @@ -27,7 +27,7 @@ void main() async { /// Derives a P2PKH address from the given public key and converts it to a Bitcoin Cash address /// for enhanced accessibility within the network. final p2pkhAddress = - BitcoinCashAddress.fromBaseAddress(publicKey.toAddress()); + BitcoinCashAddress.fromBaseAddress(publicKey.toP2pkhAddress()); /// Initialize two P2SH32 addresses for receiving funds. /// bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7 @@ -42,7 +42,7 @@ void main() async { final p2sh32Example2 = BitcoinCashAddress.fromBaseAddress( publicKey.toP2pkInP2sh(useBCHP2sh32: true)); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final example1ElectrumUtxos = await provider.request(ElectrumScriptHashListUnspent( @@ -79,7 +79,7 @@ void main() async { return; } final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ BitcoinOutput( address: p2pkhAddress.baseAddress, value: BtcUtils.toSatoshi("0.00001"), diff --git a/example/lib/bitcoin_cash/send_ft_token_example.dart b/example/lib/bitcoin_cash/send_ft_token_example.dart index 8079149..790c925 100644 --- a/example/lib/bitcoin_cash/send_ft_token_example.dart +++ b/example/lib/bitcoin_cash/send_ft_token_example.dart @@ -30,10 +30,10 @@ void main() async { /// p2pkh with token address () final receiver1 = P2pkhAddress.fromHash160( - addrHash: publicKey.toAddress().addressProgram, + h160: publicKey.toP2pkhAddress().addressProgram, type: P2pkhAddressType.p2pkhwt); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), @@ -80,7 +80,7 @@ void main() async { previousValue + element.utxo.token!.amount); final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ /// change address for bch values (sum of bch amout - (outputs amount + fee)) BitcoinOutput( address: p2pkhAddress.baseAddress, diff --git a/example/lib/bitcoin_cash/transfer_bch_example.dart b/example/lib/bitcoin_cash/transfer_bch_example.dart index c1d1bfc..8ab2425 100644 --- a/example/lib/bitcoin_cash/transfer_bch_example.dart +++ b/example/lib/bitcoin_cash/transfer_bch_example.dart @@ -27,7 +27,7 @@ void main() async { /// Derives a P2PKH address from the given public key and converts it to a Bitcoin Cash address /// for enhanced accessibility within the network. final p2pkhAddress = - BitcoinCashAddress.fromBaseAddress(publicKey.toAddress()); + BitcoinCashAddress.fromBaseAddress(publicKey.toP2pkhAddress()); /// Initialize two P2SH32 addresses for receiving funds. final p2sh32Example1 = BitcoinCashAddress( @@ -37,7 +37,7 @@ void main() async { "bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7", network: network); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), @@ -59,7 +59,7 @@ void main() async { } final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ /// change input (sumofutxos - spend) BitcoinOutput( address: p2pkhAddress.baseAddress, diff --git a/example/lib/global/bch_example.dart b/example/lib/global/bch_example.dart index 3c877d5..1da4853 100644 --- a/example/lib/global/bch_example.dart +++ b/example/lib/global/bch_example.dart @@ -24,7 +24,7 @@ void main() async { /// Derives a P2PKH address from the given public key and converts it to a Bitcoin Cash address /// for enhanced accessibility within the network. final p2pkhAddress = - BitcoinCashAddress.fromBaseAddress(publicKey.toAddress()); + BitcoinCashAddress.fromBaseAddress(publicKey.toP2pkhAddress()); /// Initialize two P2SH32 addresses for receiving funds. final p2sh32Example1 = BitcoinCashAddress( @@ -34,7 +34,7 @@ void main() async { "bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7", network: network); - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account. + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account. /// We does not need tokens utxo and we set to false. final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), @@ -56,7 +56,7 @@ void main() async { } final bchTransaction = ForkedTransactionBuilder( - outPuts: [ + outputs: [ /// change input (sumofutxos - spend) BitcoinOutput( address: p2pkhAddress.baseAddress, diff --git a/example/lib/global/old_examples/bitcoin_example.dart b/example/lib/global/old_examples/bitcoin_example.dart index 0ef7b04..3ce647d 100644 --- a/example/lib/global/old_examples/bitcoin_example.dart +++ b/example/lib/global/old_examples/bitcoin_example.dart @@ -68,7 +68,7 @@ void _spendFromP2pkhTo10DifferentType() async { network: network); final out3 = P2wpkhAddress.fromAddress( address: "tb1q3zqgu9j368wgk8u5f9vtmkdwq8geetdxry690d", network: network); - final out4 = P2pkAddress(publicKey: examplePublicKey.publicKey.toHex()); + final out4 = P2pkAddress.fromPubkey(pubkey: examplePublicKey.publicKey.toHex()); final out5 = P2shAddress.fromAddress( address: "2N5hVdETdJMwLDxxttfqeWgMuny6K4SYGSc", network: network); final out6 = P2shAddress.fromAddress( @@ -97,7 +97,7 @@ void _spendFromP2pkhTo10DifferentType() async { final builder = BitcoinTransactionBuilder( /// outputs and values - outPuts: [ + outputs: [ BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("0.001")), BitcoinOutput(address: out2, value: BtcUtils.toSatoshi("0.001")), BitcoinOutput(address: out3, value: BtcUtils.toSatoshi("0.001")), @@ -122,7 +122,7 @@ void _spendFromP2pkhTo10DifferentType() async { /// memo memo: "https://github.com/mrtnetwork", - /// Define a list of Unspent Transaction Outputs (UTXOs) for the Bitcoin transaction + /// Define a list of Unspent Transaction outputs (UTXOs) for the Bitcoin transaction utxos: [ /// Create a UTXO using a BitcoinUtxo with specific details UtxoWithAddress( @@ -140,24 +140,24 @@ void _spendFromP2pkhTo10DifferentType() async { vout: 3, /// Script type indicates the type of script associated with the UTXO's address - scriptType: examplePublicKey2.toAddress().type, + scriptType: examplePublicKey2.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey2.toHex(), - address: examplePublicKey2.toAddress())), + address: examplePublicKey2.toP2pkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( txHash: "6ff0bdb2966f62f5e202c924e1cab1368b0258833e48986cc0a70fbca624ba93", value: BigInt.from(812830), vout: 0, - scriptType: examplePublicKey2.toAddress().type, + scriptType: examplePublicKey2.toP2pkhAddress().type, ), ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey2.toHex(), - address: examplePublicKey2.toAddress())), + address: examplePublicKey2.toP2pkhAddress())), ]); /// Build the transaction by invoking the buildTransaction method on the BitcoinTransactionBuilder @@ -255,7 +255,7 @@ void _spendFrom10DifferentTypeToP2pkh() async { final builder = BitcoinTransactionBuilder( /// outputs - outPuts: [BitcoinOutput(address: out1, value: change)], + outputs: [BitcoinOutput(address: out1, value: change)], /// Set the transaction fee fee: BtcUtils.toSatoshi("0.00005"), @@ -265,7 +265,7 @@ void _spendFrom10DifferentTypeToP2pkh() async { /// Add a memo to the transaction, linking to the GitHub repository memo: "https://github.com/mrtnetwork", - /// Define a list of Unspent Transaction Outputs (UTXOs) for the Bitcoin transaction. + /// Define a list of Unspent Transaction outputs (UTXOs) for the Bitcoin transaction. /// We are selecting 10 UTXOs for spending, and each UTXO has a different address type. /// These UTXOs are related to the previous example at the top of this page. utxos: [ @@ -282,13 +282,13 @@ void _spendFrom10DifferentTypeToP2pkh() async { vout: 0, /// Script type indicates the type of script associated with the UTXO's address - scriptType: childKey1PublicKey.toAddress().type, + scriptType: childKey1PublicKey.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: childKey1PublicKey.toHex(), - address: childKey1PublicKey.toAddress())), + address: childKey1PublicKey.toP2pkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( txHash: @@ -306,11 +306,11 @@ void _spendFrom10DifferentTypeToP2pkh() async { "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 2, - scriptType: childKey1PublicKey.toSegwitAddress().type, + scriptType: childKey1PublicKey.toP2wpkhAddress().type, ), ownerDetails: UtxoAddressDetails( publicKey: childKey1PublicKey.toHex(), - address: childKey1PublicKey.toSegwitAddress())), + address: childKey1PublicKey.toP2wpkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( txHash: diff --git a/example/lib/global/old_examples/dash_example/dash.dart b/example/lib/global/old_examples/dash_example/dash.dart index 0479f3d..c4aa4d6 100644 --- a/example/lib/global/old_examples/dash_example/dash.dart +++ b/example/lib/global/old_examples/dash_example/dash.dart @@ -78,7 +78,7 @@ void _spendFromTwoP2shAndOneP2PKH() async { final b = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the P2shAddress and a value of 1.0 DASH BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("1")), @@ -112,13 +112,13 @@ void _spendFromTwoP2shAndOneP2PKH() async { vout: 2, /// Script type indicates the type of script associated with the UTXO's address - scriptType: examplePublicKey2.toAddress().type, + scriptType: examplePublicKey2.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey2.toHex(), - address: examplePublicKey2.toAddress())), + address: examplePublicKey2.toP2pkhAddress())), ]); /// Build the transaction by invoking the buildTransaction method on the BitcoinTransactionBuilder @@ -184,7 +184,7 @@ void _spendP2SH() async { final b = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ BitcoinOutput(address: out1, value: change), ], diff --git a/example/lib/global/old_examples/doge_example/doge_example.dart b/example/lib/global/old_examples/doge_example/doge_example.dart index 1ff568c..943fab4 100644 --- a/example/lib/global/old_examples/doge_example/doge_example.dart +++ b/example/lib/global/old_examples/doge_example/doge_example.dart @@ -85,7 +85,7 @@ void _spendFrom3P2shAddress() async { final builder = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the P2shAddress and a value of 1 DOGE BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("1")), @@ -119,13 +119,13 @@ void _spendFrom3P2shAddress() async { vout: 1, /// Script type indicates the type of script associated with the UTXO's address - scriptType: childKey1PublicKey.toAddress().type, + scriptType: childKey1PublicKey.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: childKey1PublicKey.toHex(), - address: childKey1PublicKey.toAddress())), + address: childKey1PublicKey.toP2pkhAddress())), ]); final tr = builder.buildTransaction((trDigest, utxo, publicKey, sighash) { if (publicKey == childKey1PublicKey.toHex()) { @@ -213,7 +213,7 @@ void _spendFromP2pkhAndP2sh() async { final b = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the P2pkhAddress and a value of 1.0 DOGE BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("1")), diff --git a/example/lib/global/old_examples/litecoin_example/litecoin_example.dart b/example/lib/global/old_examples/litecoin_example/litecoin_example.dart index ccf5f5e..8e66f3a 100644 --- a/example/lib/global/old_examples/litecoin_example/litecoin_example.dart +++ b/example/lib/global/old_examples/litecoin_example/litecoin_example.dart @@ -78,7 +78,7 @@ void _spendLTCP2pkhAddress() async { final builder = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the first P2shAddress and a value of 0.0001 LTC BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("0.0001")), @@ -115,12 +115,12 @@ void _spendLTCP2pkhAddress() async { vout: 3, /// Script type indicates the type of script associated with the UTXO's address - scriptType: pub.toAddress().type, + scriptType: pub.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( - publicKey: pub.toHex(), address: pub.toAddress())), + publicKey: pub.toHex(), address: pub.toP2pkhAddress())), ]); /// Build the transaction by invoking the buildTransaction method on the BitcoinTransactionBuilder @@ -200,7 +200,7 @@ void _spendFrom2P2shAddressAndOneMultiSigP2shAddress() async { final builder = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the third P2shAddress and a value equal to the 'change' variable BitcoinOutput(address: out1, value: change), ], @@ -338,7 +338,7 @@ void _spendFromNestedSegwitP2WPKHInP2SH() async { final builder = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the third P2wpkhAddress and a value equal to the 'change' variable BitcoinOutput(address: out1, value: change), ], @@ -456,7 +456,7 @@ void _spendFromSegwitP2WPKHAddress() async { final builder = BitcoinTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the third P2pkhAddress and a value equal to the 'change' variable BitcoinOutput(address: input1, value: change), ], @@ -487,13 +487,13 @@ void _spendFromSegwitP2WPKHAddress() async { vout: 0, /// Script type indicates the type of script associated with the UTXO's address - scriptType: examplePublicKey.toSegwitAddress().type, + scriptType: examplePublicKey.toP2wpkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey.toHex(), - address: examplePublicKey.toSegwitAddress())), + address: examplePublicKey.toP2wpkhAddress())), ]); /// Build the transaction by invoking the buildTransaction method on the BitcoinTransactionBuilder instance (builder) diff --git a/example/lib/global/old_examples/spending_with_scripts/spending_builders.dart b/example/lib/global/old_examples/spending_with_scripts/spending_builders.dart index 060550f..3a17df5 100644 --- a/example/lib/global/old_examples/spending_with_scripts/spending_builders.dart +++ b/example/lib/global/old_examples/spending_with_scripts/spending_builders.dart @@ -42,7 +42,7 @@ BtcTransaction buildP2wpkTransaction({ // index of input txInIndex: i, // script pub key of spender address - script: utxo[i].public().toAddress().toScriptPubKey(), + script: utxo[i].public().toP2pkhAddress().toScriptPubKey(), // amount of utxo amount: utxo[i].utxo.value); // sign transaction @@ -107,12 +107,12 @@ BtcTransaction buildP2WSHTransaction({ // index of utxo txInIndex: i, // P2WSH scripts - script: utxo[i].public().toP2wshScript(), + script: utxo[i].public().toP2wshRedeemScript(), // amount of utxo amount: utxo[i].utxo.value); // sign transaction - final signedTx = sign(txDigit, utxo[i].public().toP2wshScript().toHex(), + final signedTx = sign(txDigit, utxo[i].public().toP2wshRedeemScript().toHex(), BitcoinOpCodeConst.SIGHASH_ALL); // create unlock script @@ -164,7 +164,7 @@ BtcTransaction buildP2pkhTransaction({ // index of utxo txInIndex: i, // spender script pub key - script: utxo[i].public().toAddress().toScriptPubKey(), + script: utxo[i].public().toP2pkhAddress().toScriptPubKey(), ); // sign transaction @@ -205,10 +205,9 @@ BtcTransaction buildP2shNoneSegwitTransaction({ final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); for (int i = 0; i < txin.length; i++) { final ownerPublic = utxo[i].public(); - final scriptPubKey = - utxo[i].ownerDetails.address.type == P2shAddressType.p2pkhInP2sh - ? ownerPublic.toAddress().toScriptPubKey() - : ownerPublic.toRedeemScript(); + final scriptPubKey = utxo[i].ownerDetails.address.type == P2shAddressType.p2pkhInP2sh + ? ownerPublic.toP2pkhAddress().toScriptPubKey() + : ownerPublic.toRedeemScript(); // For None-SegWit transactions, we use the 'getTransactionDigest' method // to obtain the input digest for signing. final txDigit = tx.getTransactionDigest( @@ -275,8 +274,8 @@ BtcTransaction buildP2SHSegwitTransaction({ final ownerPublic = utxo[i].public(); final scriptPubKey = utxo[i].ownerDetails.address.type == P2shAddressType.p2wpkhInP2sh - ? ownerPublic.toAddress().toScriptPubKey() - : ownerPublic.toP2wshScript(); + ? ownerPublic.toP2pkhAddress().toScriptPubKey() + : ownerPublic.toP2wshRedeemScript(); // For SegWit transactions (excluding P2TR), we use the 'getTransactionSegwitDigit' method // to obtain the input digest for signing. @@ -307,7 +306,7 @@ BtcTransaction buildP2SHSegwitTransaction({ switch (utxo[i].ownerDetails.address.type) { case P2shAddressType.p2wpkhInP2sh: witnesses.add(TxWitnessInput(stack: [signedTx, ownerPublic.toHex()])); - final script = ownerPublic.toSegwitAddress().toScriptPubKey(); + final script = ownerPublic.toP2wpkhAddress().toScriptPubKey(); txin[i].scriptSig = Script(script: [script.toHex()]); break; case P2shAddressType.p2wshInP2sh: diff --git a/example/lib/global/old_examples/spending_with_scripts/spending_single_type.dart b/example/lib/global/old_examples/spending_with_scripts/spending_single_type.dart index 1c7acee..4f16538 100644 --- a/example/lib/global/old_examples/spending_with_scripts/spending_single_type.dart +++ b/example/lib/global/old_examples/spending_with_scripts/spending_single_type.dart @@ -26,7 +26,7 @@ Future spendingP2WPKH(ECPrivate sWallet, ECPrivate rWallet) async { // In this section, you can add any number of addresses with type P2PWPH to this transaction. final publicKey = sWallet.getPublic(); // P2WPKH - final sender = publicKey.toSegwitAddress(); + final sender = publicKey.toP2wpkhAddress(); // Read UTXOs of accounts from the BlockCypher API. final utxo = await api.getAccountUtxo( UtxoAddressDetails(address: sender, publicKey: publicKey.toHex())); @@ -44,7 +44,7 @@ Future spendingP2WPKH(ECPrivate sWallet, ECPrivate rWallet) async { final prive = sWallet; final recPub = rWallet.getPublic(); // P2WPKH - final receiver = recPub.toSegwitAddress(); + final receiver = recPub.toP2wpkhAddress(); // P2TR final changeAddress = recPub.toTaprootAddress(); @@ -124,9 +124,9 @@ Future spendingP2WSH(ECPrivate sWallet, ECPrivate rWallet) async { final prive = sWallet; final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); + final receiver = recPub.toP2wpkhAddress(); - final changeAddress = recPub.toSegwitAddress(); + final changeAddress = recPub.toP2wpkhAddress(); final List outputsAdress = [ BitcoinOutput(address: receiver, value: BigInt.zero), BitcoinOutput(address: changeAddress, value: BigInt.zero) @@ -160,7 +160,7 @@ Future spendingP2PKH(ECPrivate sWallet, ECPrivate rWallet) async { // and we use method `buildP2pkhTransaction` to create the transaction. final addr = sWallet.getPublic(); // P2PKH - final sender = addr.toAddress(); + final sender = addr.toP2pkhAddress(); final utxo = await api.getAccountUtxo( UtxoAddressDetails(address: sender, publicKey: addr.toHex())); final sumOfUtxo = utxo.sumOfUtxosValue(); @@ -173,8 +173,8 @@ Future spendingP2PKH(ECPrivate sWallet, ECPrivate rWallet) async { final prive = sWallet; final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); - final changeAddress = recPub.toSegwitAddress(); + final receiver = recPub.toP2wpkhAddress(); + final changeAddress = recPub.toP2wpkhAddress(); final List outputsAdress = [ BitcoinOutput(address: receiver, value: BigInt.zero), BitcoinOutput(address: changeAddress, value: BigInt.zero) @@ -225,8 +225,8 @@ Future spendingP2SHNoneSegwit( final prive = sWallet; final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); - final changeAddress = recPub.toSegwitAddress(); + final receiver = recPub.toP2wpkhAddress(); + final changeAddress = recPub.toP2wpkhAddress(); final List outputsAdress = [ BitcoinOutput(address: receiver, value: BigInt.zero), BitcoinOutput(address: changeAddress, value: BigInt.zero) @@ -275,9 +275,9 @@ Future spendingP2shSegwit(ECPrivate sWallet, ECPrivate rWallet) async { final prive = sWallet; final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); + final receiver = recPub.toP2wpkhAddress(); - final changeAddress = recPub.toSegwitAddress(); + final changeAddress = recPub.toP2wpkhAddress(); final List outputsAdress = [ BitcoinOutput(address: receiver, value: BigInt.zero), BitcoinOutput(address: changeAddress, value: BigInt.zero) @@ -327,8 +327,8 @@ Future spendingP2TR(ECPrivate sWallet, ECPrivate rWallet) async { final prive = sWallet; final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); - final changeAddress = recPub.toSegwitAddress(); + final receiver = recPub.toP2wpkhAddress(); + final changeAddress = recPub.toP2wpkhAddress(); final List outputsAdress = [ BitcoinOutput(address: receiver, value: BigInt.zero), BitcoinOutput(address: changeAddress, value: BigInt.zero) diff --git a/example/lib/global/old_examples/spending_with_transaction_builder/multi_sig_transactions.dart b/example/lib/global/old_examples/spending_with_transaction_builder/multi_sig_transactions.dart index bdb3948..db18bda 100644 --- a/example/lib/global/old_examples/spending_with_transaction_builder/multi_sig_transactions.dart +++ b/example/lib/global/old_examples/spending_with_transaction_builder/multi_sig_transactions.dart @@ -61,7 +61,7 @@ void main() async { // tb1qxt3c7849m0m6cv3z3s35c3zvdna3my3yz0r609qd9g0dcyyk580sgyldhe final p2wshMultiSigAddress = - multiSignatureAddress.toP2wshAddress(network: network).toAddress(network); + multiSignatureAddress.toP2wshAddress(network: network).toP2pkhAddress(network); // p2sh(p2wsh) multisig final signerP2sh1 = @@ -81,12 +81,12 @@ void main() async { // 2N8co8bth9CNKtnWGfHW6HuUNgnNPNdpsMj final p2shMultisigAddress = p2shMultiSignature .toP2wshInP2shAddress(network: network) - .toAddress(network); + .toP2pkhAddress(network); // P2TR final exampleAddr2 = public2.toTaprootAddress(); // P2KH - final exampleAddr4 = public3.toAddress(); + final exampleAddr4 = public3.toP2pkhAddress(); // Spending List // i use some different address type for this // now i want to spending from 8 address in one transaction @@ -204,7 +204,7 @@ void main() async { // Now, we provide the UTXOs we want to spend. utxos: utxos, // We select transaction outputs - outPuts: [output1, output2, output3, output4], + outputs: [output1, output2, output3, output4], /* Transaction fee Ensure that you have accurately calculated the amounts. diff --git a/example/lib/global/old_examples/spending_with_transaction_builder/transaction_builder_example.dart b/example/lib/global/old_examples/spending_with_transaction_builder/transaction_builder_example.dart index 3128c0d..11bf6e1 100644 --- a/example/lib/global/old_examples/spending_with_transaction_builder/transaction_builder_example.dart +++ b/example/lib/global/old_examples/spending_with_transaction_builder/transaction_builder_example.dart @@ -38,7 +38,7 @@ void main() async { final public4 = private4.getPublic(); // P2PKH ADDRESS - final exampleAddr1 = public1.toAddress(); + final exampleAddr1 = public1.toP2pkhAddress(); // P2TR final exampleAddr2 = public2.toTaprootAddress(); @@ -46,7 +46,7 @@ void main() async { // P2PKHINP2SH final exampleAddr3 = public2.toP2pkhInP2sh(); // P2KH - final exampleAddr4 = public3.toAddress(); + final exampleAddr4 = public3.toP2pkhAddress(); // P2PKHINP2SH final exampleAddr5 = public3.toP2pkhInP2sh(); // P2WSHINP2SH 1-1 multisig @@ -56,7 +56,7 @@ void main() async { // P2PKINP2SH final exampleAddr8 = public4.toP2pkInP2sh(); // P2WPKH - final exampleAddr9 = public3.toSegwitAddress(); + final exampleAddr9 = public3.toP2wpkhAddress(); // P2WSH 1-1 multisig final exampleAddr10 = public3.toP2wshAddress(); @@ -144,7 +144,7 @@ void main() async { // Now, we provide the UTXOs we want to spend. utxos: utxos, // We select transaction outputs - outPuts: [ + outputs: [ output1, output2, output3, diff --git a/example/lib/global/transfer_from_7_account_to_6_accout_example.dart b/example/lib/global/transfer_from_7_account_to_6_accout_example.dart index a441543..37c6e1d 100644 --- a/example/lib/global/transfer_from_7_account_to_6_accout_example.dart +++ b/example/lib/global/transfer_from_7_account_to_6_accout_example.dart @@ -25,8 +25,8 @@ void main() async { final privateKey = ECPrivate.fromHex( "76257aafc9b954351c7f6445b2d07277f681a5e83d515a1f32ebf54989c2af4f"); final examplePublicKey = privateKey.getPublic(); - final spender1 = examplePublicKey.toAddress(); - final spender2 = examplePublicKey.toSegwitAddress(); + final spender1 = examplePublicKey.toP2pkhAddress(); + final spender2 = examplePublicKey.toP2wpkhAddress(); final spender3 = examplePublicKey.toTaprootAddress(); final spender4 = examplePublicKey.toP2pkhInP2sh(); final spender5 = examplePublicKey.toP2pkInP2sh(); @@ -47,7 +47,7 @@ void main() async { /// loop each spenders address and get utxos and add to accountsUtxos for (final i in spenders) { - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account final elctrumUtxos = await provider .request(ElectrumScriptHashListUnspent(scriptHash: i.pubKeyHash())); @@ -75,9 +75,9 @@ void main() async { /// P2pkhAddress.fromAddress(address: ".....", network: network); /// P2trAddress.fromAddress(address: "....", network: network) /// .... - final List outPuts = [ + final List outputs = [ BitcoinOutput( - address: examplePublicKey2.toSegwitAddress(), + address: examplePublicKey2.toP2wpkhAddress(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( address: examplePublicKey2.toTaprootAddress(), @@ -97,18 +97,18 @@ void main() async { const String memo = "https://github.com/mrtnetwork"; /// SUM OF OUTOUT AMOUNTS - final sumOfOutputs = outPuts.fold( + final sumOfOutputs = outputs.fold( BigInt.zero, (previousValue, element) => previousValue + element.value); /// Estimate transaction size int transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: accountsUtxos, outputs: [ - ...outPuts, + ...outputs, /// I add more output for change value to get correct transaction size BitcoinOutput( - address: examplePublicKey2.toAddress(), value: BigInt.zero) + address: examplePublicKey2.toP2pkhAddress(), value: BigInt.zero) ], /// network @@ -135,13 +135,13 @@ void main() async { } //// if we have change value we back amount to account if (changeValue > BigInt.zero) { - outPuts.add(BitcoinOutput( - address: examplePublicKey2.toAddress(), value: changeValue)); + outputs.add(BitcoinOutput( + address: examplePublicKey2.toP2pkhAddress(), value: changeValue)); } /// create transaction builder final builder = BitcoinTransactionBuilder( - outPuts: outPuts, + outputs: outputs, fee: fee, network: network, utxos: accountsUtxos, diff --git a/example/lib/global/transfer_to_8_account_example.dart b/example/lib/global/transfer_to_8_account_example.dart index 90f21f7..a8f6f6b 100644 --- a/example/lib/global/transfer_to_8_account_example.dart +++ b/example/lib/global/transfer_to_8_account_example.dart @@ -17,7 +17,7 @@ void main() async { 'cTALNpTpRbbxTCJ2A5Vq88UxT44w1PE2cYqiB3n4hRvzyCev1Wwo', netVersion: BitcoinNetwork.testnet.wifNetVer); final examplePublicKey2 = examplePrivateKey2.getPublic(); - final p2pkhAddress = examplePublicKey2.toAddress(); + final p2pkhAddress = examplePublicKey2.toP2pkhAddress(); /// receiver addresses i use public key for generate address final examplePublicKey = ECPublic.fromHex( @@ -25,9 +25,9 @@ void main() async { const network = BitcoinNetwork.testnet; - /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account + /// Reads all UTXOs (Unspent Transaction outputs) associated with the account final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( - scriptHash: examplePublicKey2.toAddress().pubKeyHash())); + scriptHash: examplePublicKey2.toP2pkhAddress().pubKeyHash())); /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. /// read spender utxos @@ -48,12 +48,12 @@ void main() async { /// P2pkhAddress.fromAddress(address: ".....", network: network); /// P2trAddress.fromAddress(address: "....", network: network) /// .... - final List outPuts = [ + final List outputs = [ BitcoinOutput( - address: examplePublicKey.toAddress(), + address: examplePublicKey.toP2pkhAddress(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( - address: examplePublicKey.toSegwitAddress(), + address: examplePublicKey.toP2wpkhAddress(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( address: examplePublicKey.toTaprootAddress(), @@ -76,18 +76,18 @@ void main() async { const String memo = "https://github.com/mrtnetwork"; /// SUM OF OUTOUT AMOUNTS - final sumOfOutputs = outPuts.fold( + final sumOfOutputs = outputs.fold( BigInt.zero, (previousValue, element) => previousValue + element.value); /// ESTIMATE TRANSACTION SIZE int estimateSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: utxos, outputs: [ - ...outPuts, + ...outputs, /// I add more output for change value to get correct transaction size BitcoinOutput( - address: examplePublicKey2.toAddress(), value: BigInt.zero) + address: examplePublicKey2.toP2pkhAddress(), value: BigInt.zero) ], /// network @@ -113,13 +113,13 @@ void main() async { //// if we have change value we back amount to account if (changeValue > BigInt.zero) { final changeOutput = BitcoinOutput( - address: examplePublicKey2.toAddress(), value: changeValue); - outPuts.add(changeOutput); + address: examplePublicKey2.toP2pkhAddress(), value: changeValue); + outputs.add(changeOutput); } /// create transaction builder final builder = BitcoinTransactionBuilder( - outPuts: outPuts, + outputs: outputs, fee: fee, network: network, utxos: utxos, diff --git a/example/lib/main.dart b/example/lib/main.dart index 5e85d9f..ed100b9 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -101,7 +101,7 @@ void _spendFrom2P2SHAnd2P2PKHAddress() async { ]); final b = ForkedTransactionBuilder( - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the P2shAddress and a value of 0.01 BCH BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("0.01")), @@ -121,7 +121,7 @@ void _spendFrom2P2SHAnd2P2PKHAddress() async { /// Specify the network for the litcoin transaction network: network, - /// Define a list of Unspent Transaction Outputs (UTXOs) for the Bitcoin transaction + /// Define a list of Unspent Transaction outputs (UTXOs) for the Bitcoin transaction utxos: [ UtxoWithAddress( @@ -138,13 +138,13 @@ void _spendFrom2P2SHAnd2P2PKHAddress() async { vout: 0, /// Script type indicates the type of script associated with the UTXO's address - scriptType: examplePublicKey2.toAddress().type, + scriptType: examplePublicKey2.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey2.toHex(), - address: examplePublicKey2.toAddress())), + address: examplePublicKey2.toP2pkhAddress())), ]); /// Build the transaction by invoking the buildTransaction method on the ForkedTransactionBuilder @@ -247,7 +247,7 @@ void _spendFrom2P2SHAnd1P2PKHAddress() async { final b = ForkedTransactionBuilder( /// outputs - outPuts: [ + outputs: [ /// Define a BitcoinOutput with the P2pkhAddress and a value of 0.01 BCH BitcoinOutput(address: out1, value: BtcUtils.toSatoshi("0.01")), @@ -267,7 +267,7 @@ void _spendFrom2P2SHAnd1P2PKHAddress() async { /// Add a memo to the transaction, linking to the GitHub repository memo: "https://github.com/mrtnetwork", - /// Define a list of Unspent Transaction Outputs (UTXOs) for the Bitcoin transaction + /// Define a list of Unspent Transaction outputs (UTXOs) for the Bitcoin transaction utxos: [ UtxoWithAddress( @@ -324,13 +324,13 @@ void _spendFrom2P2SHAnd1P2PKHAddress() async { vout: 2, /// Script type indicates the type of script associated with the UTXO's address - scriptType: examplePublicKey.toAddress().type, + scriptType: examplePublicKey.toP2pkhAddress().type, ), /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( publicKey: examplePublicKey.toHex(), - address: examplePublicKey.toAddress())), + address: examplePublicKey.toP2pkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( /// Transaction hash uniquely identifies the referenced transaction diff --git a/lib/bitcoin_base.dart b/lib/bitcoin_base.dart index ef45ef4..7fd6954 100644 --- a/lib/bitcoin_base.dart +++ b/lib/bitcoin_base.dart @@ -8,6 +8,8 @@ library bitcoin_base; export 'package:bitcoin_base/src/bitcoin/address/address.dart'; +export 'package:bitcoin_base/src/bitcoin/address/util.dart'; + export 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; export 'package:bitcoin_base/src/crypto/crypto.dart'; diff --git a/lib/src/bitcoin/address/address.dart b/lib/src/bitcoin/address/address.dart index 3a19b71..0ad415e 100644 --- a/lib/src/bitcoin/address/address.dart +++ b/lib/src/bitcoin/address/address.dart @@ -11,6 +11,7 @@ library bitcoin_base.address; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/src/utils/enumerate.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:bitcoin_base/src/utils/script.dart'; part 'core.dart'; part 'legacy_address.dart'; part 'utils/address_utils.dart'; diff --git a/lib/src/bitcoin/address/core.dart b/lib/src/bitcoin/address/core.dart index 15a119d..7ae6bfb 100644 --- a/lib/src/bitcoin/address/core.dart +++ b/lib/src/bitcoin/address/core.dart @@ -37,9 +37,7 @@ abstract class BitcoinAddressType implements Enumerate { P2pkhAddressType.p2pkhwt ]; @override - String toString() { - return "BitcoinAddressType.$value"; - } + String toString() => value; } abstract class BitcoinBaseAddress { @@ -62,9 +60,7 @@ class PubKeyAddressType implements BitcoinAddressType { @override int get hashLength => 20; @override - String toString() { - return "PubKeyAddressType.$value"; - } + String toString() => value; } class P2pkhAddressType implements BitcoinAddressType { @@ -83,9 +79,7 @@ class P2pkhAddressType implements BitcoinAddressType { @override int get hashLength => 20; @override - String toString() { - return "P2pkhAddressType.$value"; - } + String toString() => value; } class P2shAddressType implements BitcoinAddressType { @@ -135,9 +129,7 @@ class P2shAddressType implements BitcoinAddressType { final String value; @override - String toString() { - return "P2shAddressType.$value"; - } + String toString() => value; } class SegwitAddresType implements BitcoinAddressType { @@ -164,7 +156,5 @@ class SegwitAddresType implements BitcoinAddressType { } @override - String toString() { - return "SegwitAddresType.$value"; - } + String toString() => value; } diff --git a/lib/src/bitcoin/address/legacy_address.dart b/lib/src/bitcoin/address/legacy_address.dart index efd621d..8e60a6a 100644 --- a/lib/src/bitcoin/address/legacy_address.dart +++ b/lib/src/bitcoin/address/legacy_address.dart @@ -4,26 +4,62 @@ abstract class LegacyAddress implements BitcoinBaseAddress { /// Represents a Bitcoin address /// /// [addressProgram] the addressProgram string representation of the address; hash160 represents - /// two consequtive hashes of the public key or the redeam script or SHA256 for BCH(P2SH), first + /// two consequtive hashes of the public key or the redeem script or SHA256 for BCH(P2SH), first /// a SHA-256 and then an RIPEMD-160 - LegacyAddress.fromHash160(String addrHash, BitcoinAddressType addressType) - : _addressProgram = - _BitcoinAddressUtils.validateAddressProgram(addrHash, addressType); - LegacyAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) { - final decode = _BitcoinAddressUtils.decodeLagacyAddressWithNetworkAndType( + LegacyAddress.fromHash160(String h160, BitcoinAddressType addressType) + : _addressProgram = _BitcoinAddressUtils.validateAddressProgram(h160, addressType); + LegacyAddress.fromAddress({required String address, required BasedUtxoNetwork network}) { + final decode = _BitcoinAddressUtils.decodeLegacyAddressWithNetworkAndType( address: address, type: type, network: network); if (decode == null) { throw MessageException("Invalid ${network.conf.coinName} address"); } _addressProgram = decode; } - LegacyAddress.fromScript({required Script script}) + LegacyAddress.fromPubkey({required ECPublic pubkey}) + : _pubkey = pubkey, + _addressProgram = _BitcoinAddressUtils.pubkeyToHash160(pubkey.toHex()); + LegacyAddress.fromRedeemScript({required Script script}) : _addressProgram = _BitcoinAddressUtils.scriptToHash160(script); - LegacyAddress._(); + LegacyAddress.fromScriptSig({required Script script}) { + switch (type) { + case PubKeyAddressType.p2pk: + _signature = script.findScriptParam(0); + break; + case P2pkhAddressType.p2pkh: + if (script.script.length != 2) throw ArgumentError('Input is invalid'); + _signature = script.findScriptParam(0); + if (!isCanonicalScriptSignature(BytesUtils.fromHexString(_signature!))) { + throw ArgumentError('Input has invalid signature'); + } + _pubkey = ECPublic.fromHex(script.findScriptParam(1)); + _addressProgram = _BitcoinAddressUtils.pubkeyToHash160(_pubkey!.toHex()); + break; + case P2shAddressType.p2wpkhInP2sh: + case P2shAddressType.p2wshInP2sh: + case P2shAddressType.p2pkhInP2sh: + case P2shAddressType.p2pkInP2sh: + _signature = script.findScriptParam(1); + _addressProgram = _BitcoinAddressUtils.scriptToHash160( + Script.fromRaw(hexData: script.findScriptParam(2))); + break; + default: + throw UnimplementedError(); + } + } + ECPublic? _pubkey; + String? _signature; late final String _addressProgram; + ECPublic? get pubkey { + return _pubkey; + } + + String? get signature { + return _signature; + } + @override String get addressProgram { if (type == PubKeyAddressType.p2pk) throw UnimplementedError(); @@ -43,22 +79,30 @@ abstract class LegacyAddress implements BitcoinBaseAddress { } class P2shAddress extends LegacyAddress { - P2shAddress.fromScript( - {required Script script, this.type = P2shAddressType.p2pkInP2sh}) - : super.fromScript(script: script); + static RegExp get regex => RegExp(r'(^|\s)[23][a-km-zA-HJ-NP-Z1-9]{25,34}($|\s)'); + + P2shAddress.fromRedeemScript({required Script script, this.type = P2shAddressType.p2pkInP2sh}) + : super.fromRedeemScript(script: script); P2shAddress.fromAddress( {required String address, required BasedUtxoNetwork network, this.type = P2shAddressType.p2pkInP2sh}) : super.fromAddress(address: address, network: network); - P2shAddress.fromHash160( - {required String addrHash, this.type = P2shAddressType.p2pkInP2sh}) - : super.fromHash160(addrHash, type); + P2shAddress.fromHash160({required String h160, this.type = P2shAddressType.p2pkInP2sh}) + : super.fromHash160(h160, type); @override final P2shAddressType type; + factory P2shAddress.fromScriptPubkey( + {required Script script, type = P2shAddressType.p2pkInP2sh}) { + if (script.getAddressType() is! P2shAddressType) { + throw ArgumentError("Invalid scriptPubKey"); + } + return P2shAddress.fromHash160(h160: script.findScriptParam(1), type: type); + } + @override String toAddress(BasedUtxoNetwork network) { if (!network.supportedAddress.contains(type)) { @@ -71,61 +115,81 @@ class P2shAddress extends LegacyAddress { @override Script toScriptPubKey() { if (addressProgram.length == 64) { - return Script(script: ['OP_HASH256', addressProgram, 'OP_EQUAL']); + return Script( + script: [BitcoinOpCodeConst.OP_HASH256, addressProgram, BitcoinOpCodeConst.OP_EQUAL]); } - return Script(script: ['OP_HASH160', addressProgram, 'OP_EQUAL']); + return Script( + script: [BitcoinOpCodeConst.OP_HASH160, addressProgram, BitcoinOpCodeConst.OP_EQUAL]); } } class P2pkhAddress extends LegacyAddress { - P2pkhAddress.fromScript( - {required Script script, this.type = P2pkhAddressType.p2pkh}) - : super.fromScript(script: script); + static RegExp get regex => RegExp(r'(^|\s)[1mn][a-km-zA-HJ-NP-Z1-9]{25,34}($|\s)'); + factory P2pkhAddress.fromScriptPubkey( + {required Script script, P2pkhAddressType type = P2pkhAddressType.p2pkh}) { + if (script.getAddressType() != P2pkhAddressType.p2pkh) { + throw ArgumentError("Invalid scriptPubKey"); + } + return P2pkhAddress.fromHash160(h160: script.findScriptParam(2), type: type); + } P2pkhAddress.fromAddress( {required String address, required BasedUtxoNetwork network, this.type = P2pkhAddressType.p2pkh}) : super.fromAddress(address: address, network: network); - P2pkhAddress.fromHash160( - {required String addrHash, this.type = P2pkhAddressType.p2pkh}) - : super.fromHash160(addrHash, type); + P2pkhAddress.fromHash160({required String h160, this.type = P2pkhAddressType.p2pkh}) + : super.fromHash160(h160, type); + + P2pkhAddress.fromScriptSig({required Script scriptSig, this.type = P2pkhAddressType.p2pkh}) + : super.fromScriptSig(script: scriptSig); @override Script toScriptPubKey() { return Script(script: [ - 'OP_DUP', - 'OP_HASH160', - addressProgram, - 'OP_EQUALVERIFY', - 'OP_CHECKSIG' + BitcoinOpCodeConst.OP_DUP, + BitcoinOpCodeConst.OP_HASH160, + _addressProgram, + BitcoinOpCodeConst.OP_EQUALVERIFY, + BitcoinOpCodeConst.OP_CHECKSIG ]); } @override final P2pkhAddressType type; + + Script toScriptSig() { + return Script(script: [_signature, _pubkey]); + } } class P2pkAddress extends LegacyAddress { - P2pkAddress({required String publicKey}) : super._() { - final toBytes = BytesUtils.fromHexString(publicKey); - if (!Secp256k1PublicKeyEcdsa.isValidBytes(toBytes)) { - throw const MessageException("Invalid secp256k1 public key"); + static RegExp get regex => RegExp(r'(^|\s)1([A-Za-z0-9]{34})($|\s)'); + + P2pkAddress({required ECPublic publicKey}) + : _pubkeyHex = publicKey.toHex(), + super.fromPubkey(pubkey: publicKey); + factory P2pkAddress.fromPubkey({required ECPublic pubkey}) => pubkey.toP2pkAddress(); + P2pkAddress.fromAddress({required String address, required BasedUtxoNetwork network}) + : super.fromAddress(address: address, network: network); + + factory P2pkAddress.fromScriptPubkey({required Script script}) { + if (script.getAddressType() is! PubKeyAddressType) { + throw ArgumentError("Invalid scriptPubKey"); } - publicHex = publicKey; + return P2pkAddress.fromPubkey(pubkey: ECPublic.fromHex(script.script[0])); } - late final String publicHex; + late final String _pubkeyHex; - /// Returns the scriptPubKey (P2SH) that corresponds to this address @override Script toScriptPubKey() { - return Script(script: [publicHex, 'OP_CHECKSIG']); + return Script(script: [_pubkeyHex, BitcoinOpCodeConst.OP_CHECKSIG]); } @override String toAddress(BasedUtxoNetwork network) { return _BitcoinAddressUtils.legacyToAddress( network: network, - addressProgram: _BitcoinAddressUtils.pubkeyToHash160(publicHex), + addressProgram: _BitcoinAddressUtils.pubkeyToHash160(_pubkeyHex), type: type); } diff --git a/lib/src/bitcoin/address/segwit_address.dart b/lib/src/bitcoin/address/segwit_address.dart index ae92a51..8b3ebb0 100644 --- a/lib/src/bitcoin/address/segwit_address.dart +++ b/lib/src/bitcoin/address/segwit_address.dart @@ -2,9 +2,7 @@ part of 'package:bitcoin_base/src/bitcoin/address/address.dart'; abstract class SegwitAddress implements BitcoinBaseAddress { SegwitAddress.fromAddress( - {required String address, - required BasedUtxoNetwork network, - required this.segwitVersion}) { + {required String address, required BasedUtxoNetwork network, required this.segwitVersion}) { if (!network.supportedAddress.contains(type)) { throw MessageException("network does not support ${type.value} address"); } @@ -12,13 +10,9 @@ abstract class SegwitAddress implements BitcoinBaseAddress { address: address, version: segwitVersion, network: network); } SegwitAddress.fromProgram( - {required String program, - required this.segwitVersion, - required SegwitAddresType addresType}) - : addressProgram = - _BitcoinAddressUtils.validateAddressProgram(program, addresType); - SegwitAddress.fromScript( - {required Script script, required this.segwitVersion}) + {required String program, required this.segwitVersion, required SegwitAddresType addressType}) + : addressProgram = _BitcoinAddressUtils.validateAddressProgram(program, addressType); + SegwitAddress.fromRedeemScript({required Script script, required this.segwitVersion}) : addressProgram = _BitcoinAddressUtils.segwitScriptToSHA256(script); @override @@ -32,9 +26,7 @@ abstract class SegwitAddress implements BitcoinBaseAddress { throw MessageException("network does not support ${type.value} address"); } return _BitcoinAddressUtils.segwitToAddress( - addressProgram: addressProgram, - network: network, - segwitVersion: segwitVersion); + addressProgram: addressProgram, network: network, segwitVersion: segwitVersion); } @override @@ -44,26 +36,31 @@ abstract class SegwitAddress implements BitcoinBaseAddress { } class P2wpkhAddress extends SegwitAddress { - P2wpkhAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) + static RegExp get regex => RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{25,39}($|\s)'); + + P2wpkhAddress.fromAddress({required String address, required BasedUtxoNetwork network}) : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV0, - address: address, - network: network); + segwitVersion: _BitcoinAddressUtils.segwitV0, address: address, network: network); P2wpkhAddress.fromProgram({required String program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV0, program: program, - addresType: SegwitAddresType.p2wpkh); - P2wpkhAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + addressType: SegwitAddresType.p2wpkh); + P2wpkhAddress.fromRedeemScript({required Script script}) + : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + + factory P2wpkhAddress.fromScriptPubkey({required Script script, type = SegwitAddresType.p2wpkh}) { + if (script.getAddressType() != SegwitAddresType.p2wpkh) { + throw ArgumentError("Invalid scriptPubKey"); + } + return P2wpkhAddress.fromProgram(program: script.findScriptParam(1)); + } /// returns the scriptPubKey of a P2WPKH witness script @override Script toScriptPubKey() { - return Script(script: ['OP_0', addressProgram]); + return Script(script: [BitcoinOpCodeConst.OP_0, addressProgram]); } /// returns the type of address @@ -72,25 +69,31 @@ class P2wpkhAddress extends SegwitAddress { } class P2trAddress extends SegwitAddress { - P2trAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) + static RegExp get regex => + RegExp(r'(bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89})'); + + P2trAddress.fromAddress({required String address, required BasedUtxoNetwork network}) : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV1, - address: address, - network: network); + segwitVersion: _BitcoinAddressUtils.segwitV1, address: address, network: network); P2trAddress.fromProgram({required String program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV1, program: program, - addresType: SegwitAddresType.p2tr); - P2trAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV1, script: script); + addressType: SegwitAddresType.p2tr); + P2trAddress.fromRedeemScript({required Script script}) + : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV1, script: script); + + factory P2trAddress.fromScriptPubkey({required Script script, type = SegwitAddresType.p2wpkh}) { + if (script.getAddressType() != SegwitAddresType.p2tr) { + throw ArgumentError("Invalid scriptPubKey"); + } + return P2trAddress.fromProgram(program: script.findScriptParam(1)); + } /// returns the scriptPubKey of a P2TR witness script @override Script toScriptPubKey() { - return Script(script: ['OP_1', addressProgram]); + return Script(script: [BitcoinOpCodeConst.OP_1, addressProgram]); } /// returns the type of address @@ -99,25 +102,30 @@ class P2trAddress extends SegwitAddress { } class P2wshAddress extends SegwitAddress { - P2wshAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) + static RegExp get regex => RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{40,80}'); + + P2wshAddress.fromAddress({required String address, required BasedUtxoNetwork network}) : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV0, - address: address, - network: network); + segwitVersion: _BitcoinAddressUtils.segwitV0, address: address, network: network); P2wshAddress.fromProgram({required String program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV0, program: program, - addresType: SegwitAddresType.p2wsh); - P2wshAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + addressType: SegwitAddresType.p2wsh); + P2wshAddress.fromRedeemScript({required Script script}) + : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + + factory P2wshAddress.fromScriptPubkey({required Script script, type = SegwitAddresType.p2wsh}) { + if (script.getAddressType() != SegwitAddresType.p2wsh) { + throw ArgumentError("Invalid scriptPubKey"); + } + return P2wshAddress.fromProgram(program: script.findScriptParam(1)); + } /// Returns the scriptPubKey of a P2WPKH witness script @override Script toScriptPubKey() { - return Script(script: ['OP_0', addressProgram]); + return Script(script: [BitcoinOpCodeConst.OP_0, addressProgram]); } /// Returns the type of address diff --git a/lib/src/bitcoin/address/util.dart b/lib/src/bitcoin/address/util.dart new file mode 100644 index 0000000..a11b90b --- /dev/null +++ b/lib/src/bitcoin/address/util.dart @@ -0,0 +1,35 @@ +import 'package:bitcoin_base/src/bitcoin/address/address.dart'; +import 'package:bitcoin_base/src/models/network.dart'; + +bool validateAddress({required String address, required BasedUtxoNetwork network}) { + try { + addressToOutputScript(address: address, network: network); + return true; + } catch (_) { + return false; + } +} + +List addressToOutputScript({required String address, required BasedUtxoNetwork network}) { + if (P2pkhAddress.regex.hasMatch(address)) { + return P2pkhAddress.fromAddress(address: address, network: network).toScriptPubKey().toBytes(); + } + + if (P2shAddress.regex.hasMatch(address)) { + return P2shAddress.fromAddress(address: address, network: network).toScriptPubKey().toBytes(); + } + + if (P2wpkhAddress.regex.hasMatch(address)) { + return P2wpkhAddress.fromAddress(address: address, network: network).toScriptPubKey().toBytes(); + } + + if (P2wshAddress.regex.hasMatch(address)) { + return P2wshAddress.fromAddress(address: address, network: network).toScriptPubKey().toBytes(); + } + + if (P2trAddress.regex.hasMatch(address)) { + return P2trAddress.fromAddress(address: address, network: network).toScriptPubKey().toBytes(); + } + + throw ArgumentError('$address has no matching Script'); +} diff --git a/lib/src/bitcoin/address/utils/address_utils.dart b/lib/src/bitcoin/address/utils/address_utils.dart index 5093e3b..6ed06a3 100644 --- a/lib/src/bitcoin/address/utils/address_utils.dart +++ b/lib/src/bitcoin/address/utils/address_utils.dart @@ -66,9 +66,9 @@ class _BitcoinAddressUtils { } final decodedHex = BytesUtils.toHexString(decode.item1); if (bytesEqual(decode.item2, networks.p2pkhNetVer)) { - return P2pkhAddress.fromHash160(addrHash: decodedHex); + return P2pkhAddress.fromHash160(h160: decodedHex); } else if (bytesEqual(decode.item2, networks.p2shNetVer)) { - return P2shAddress.fromHash160(addrHash: decodedHex); + return P2shAddress.fromHash160(h160: decodedHex); } return null; } @@ -102,7 +102,7 @@ class _BitcoinAddressUtils { /// /// Returns a SegwitAddress instance representing the converted SegWit address, /// or null if the conversion is not successful. - static SegwitAddress? toSegwitAddress( + static SegwitAddress? toP2wpkhAddress( String address, BasedUtxoNetwork network) { try { final convert = SegwitBech32Decoder.decode(network.p2wpkhHrp, address); @@ -154,7 +154,7 @@ class _BitcoinAddressUtils { String address, BasedUtxoNetwork network) { BitcoinBaseAddress? baseAddress; if (network.supportedAddress.contains(SegwitAddresType.p2wpkh)) { - baseAddress = toSegwitAddress(address, network); + baseAddress = toP2wpkhAddress(address, network); } baseAddress ??= toLegacy(address, network); if (baseAddress == null) { @@ -234,7 +234,7 @@ class _BitcoinAddressUtils { if (bytesEqual(network.p2pkhNetVer, version) || bytesEqual(network.p2pkhWtNetVer, version)) { return P2pkhAddress.fromHash160( - addrHash: scriptHex, + h160: scriptHex, type: legacyP2pk ? P2pkhAddressType.p2pkh : P2pkhAddressType.p2pkhwt); } @@ -242,7 +242,7 @@ class _BitcoinAddressUtils { if (bytesEqual(network.p2shNetVer, version) || bytesEqual(network.p2shwt20NetVer, version)) { return P2shAddress.fromHash160( - addrHash: scriptHex, + h160: scriptHex, type: legacyP2sh ? P2shAddressType.p2pkhInP2sh : P2shAddressType.p2pkhInP2shwt); @@ -252,7 +252,7 @@ class _BitcoinAddressUtils { if (bytesEqual(network.p2sh32NetVer, version) || bytesEqual(network.p2shwt32NetVer, version)) { return P2shAddress.fromHash160( - addrHash: scriptHex, + h160: scriptHex, type: legacyP2sh ? P2shAddressType.p2pkhInP2sh32 : P2shAddressType.p2pkhInP2sh32wt); @@ -271,7 +271,7 @@ class _BitcoinAddressUtils { /// Returns the address program in hexadecimal format if successful, or null if decoding or validation fails. /// /// Throws a [MessageException] if the specified network does not support the given address type. - static String? decodeLagacyAddressWithNetworkAndType( + static String? decodeLegacyAddressWithNetworkAndType( {required String address, required BitcoinAddressType type, required BasedUtxoNetwork network}) { diff --git a/lib/src/bitcoin/script/input.dart b/lib/src/bitcoin/script/input.dart index 534147d..35a83a2 100644 --- a/lib/src/bitcoin/script/input.dart +++ b/lib/src/bitcoin/script/input.dart @@ -16,9 +16,9 @@ class TxInput { {required this.txId, required this.txIndex, Script? scriptSig, - List? sequance}) + List? sequence}) : sequence = List.unmodifiable( - sequance ?? BitcoinOpCodeConst.DEFAULT_TX_SEQUENCE), + sequence ?? BitcoinOpCodeConst.DEFAULT_TX_SEQUENCE), scriptSig = scriptSig ?? Script(script: []); TxInput copyWith( {String? txId, int? txIndex, Script? scriptSig, List? sequence}) { @@ -26,7 +26,7 @@ class TxInput { txId: txId ?? this.txId, txIndex: txIndex ?? this.txIndex, scriptSig: scriptSig ?? this.scriptSig, - sequance: sequence ?? this.sequence); + sequence: sequence ?? this.sequence); } final String txId; @@ -37,7 +37,7 @@ class TxInput { /// creates a copy of the object TxInput copy() { return TxInput( - txId: txId, txIndex: txIndex, scriptSig: scriptSig, sequance: sequence); + txId: txId, txIndex: txIndex, scriptSig: scriptSig, sequence: sequence); } /// serializes TxInput to bytes @@ -84,7 +84,7 @@ class TxInput { scriptSig: Script.fromRaw( hexData: BytesUtils.toHexString(unlockingScript), hasSegwit: hasSegwit), - sequance: sequenceNumberData), + sequence: sequenceNumberData), cursor); } diff --git a/lib/src/bitcoin/script/op_code/constant.dart b/lib/src/bitcoin/script/op_code/constant.dart index cb6b952..bd62271 100644 --- a/lib/src/bitcoin/script/op_code/constant.dart +++ b/lib/src/bitcoin/script/op_code/constant.dart @@ -1,7 +1,126 @@ -/// ignore_for_file: constant_identifier_names, equal_keys_in_map, non_constant_identifier_names +/// ignore_for_file: constant_identifier_names, equal_keys_in_map, non_constant_identifier_names, camel_case_types /// Constants and identifiers used in the Bitcoin-related code. -// ignore_for_file: constant_identifier_names, non_constant_identifier_names, equal_keys_in_map +// ignore_for_file: constant_identifier_names, non_constant_identifier_names, equal_keys_in_map, camel_case_types + class BitcoinOpCodeConst { + static const OP_0 = "OP_0"; + static const OP_FALSE = "OP_FALSE"; + static const OP_PUSHDATA1 = "OP_PUSHDATA1"; + static const OP_PUSHDATA2 = "OP_PUSHDATA2"; + static const OP_PUSHDATA4 = "OP_PUSHDATA4"; + static const OP_1NEGATE = "OP_1NEGATE"; + static const OP_1 = "OP_1"; + static const OP_TRUE = "OP_TRUE"; + static const OP_2 = "OP_2"; + static const OP_3 = "OP_3"; + static const OP_4 = "OP_4"; + static const OP_5 = "OP_5"; + static const OP_6 = "OP_6"; + static const OP_7 = "OP_7"; + static const OP_8 = "OP_8"; + static const OP_9 = "OP_9"; + static const OP_10 = "OP_10"; + static const OP_11 = "OP_11"; + static const OP_12 = "OP_12"; + static const OP_13 = "OP_13"; + static const OP_14 = "OP_14"; + static const OP_15 = "OP_15"; + static const OP_16 = "OP_16"; + + /// flow control + static const OP_NOP = "OP_NOP"; + static const OP_IF = "OP_IF"; + static const OP_NOTIF = "OP_NOTIF"; + static const OP_ELSE = "OP_ELSE"; + static const OP_ENDIF = "OP_ENDIF"; + static const OP_VERIFY = "OP_VERIFY"; + static const OP_RETURN = "OP_RETURN"; + + /// stack + static const OP_TOALTSTACK = "OP_TOALTSTACK"; + static const OP_FROMALTSTACK = "OP_FROMALTSTACK"; + static const OP_IFDUP = "OP_IFDUP"; + static const OP_DEPTH = "OP_DEPTH"; + static const OP_DROP = "OP_DROP"; + static const OP_DUP = "OP_DUP"; + static const OP_NIP = "OP_NIP"; + static const OP_OVER = "OP_OVER"; + static const OP_PICK = "OP_PICK"; + static const OP_ROLL = "OP_ROLL"; + static const OP_ROT = "OP_ROT"; + static const OP_SWAP = "OP_SWAP"; + static const OP_TUCK = "OP_TUCK"; + static const OP_2DROP = "OP_2DROP"; + static const OP_2DUP = "OP_2DUP"; + static const OP_3DUP = "OP_3DUP"; + static const OP_2OVER = "OP_2OVER"; + static const OP_2ROT = "OP_2ROT"; + static const OP_2SWAP = "OP_2SWAP"; + + /// splice + /// 'OP_CAT': [0x7e], + /// 'OP_SUBSTR': [0x7f], + /// 'OP_LEFT': [0x80], + /// 'OP_RIGHT': [0x81], + static const OP_SIZE = "OP_SIZE"; + + /// bitwise logic + /// 'OP_INVERT': [0x83], + /// 'OP_AND': [0x84], + /// 'OP_OR': [0x85], + /// 'OP_XOR': [0x86], + static const OP_EQUAL = "OP_EQUAL"; + static const OP_EQUALVERIFY = "OP_EQUALVERIFY"; + + /// arithmetic + static const OP_1ADD = "OP_1ADD"; + static const OP_1SUB = "OP_1SUB"; + + /// 'OP_2MUL': [0x8d], + /// 'OP_2DIV': [0x8e], + static const OP_NEGATE = "OP_NEGATE"; + static const OP_ABS = "OP_ABS"; + static const OP_NOT = "OP_NOT"; + static const OP_0NOTEQUAL = "OP_0NOTEQUAL"; + static const OP_ADD = "OP_ADD"; + static const OP_SUB = "OP_SUB"; + + /// 'OP_MUL': [0x95], + /// 'OP_DIV': [0x96], + /// 'OP_MOD': [0x97], + /// 'OP_LSHIFT': [0x98], + /// 'OP_RSHIFT': [0x99], + static const OP_BOOLAND = "OP_BOOLAND"; + static const OP_BOOLOR = "OP_BOOLOR"; + static const OP_NUMEQUAL = "OP_NUMEQUAL"; + static const OP_NUMEQUALVERIFY = "OP_NUMEQUALVERIFY"; + static const OP_NUMNOTEQUAL = "OP_NUMNOTEQUAL"; + static const OP_LESSTHAN = "OP_LESSTHAN"; + static const OP_GREATERTHAN = "OP_GREATERTHAN"; + static const OP_LESSTHANOREQUAL = "OP_LESSTHANOREQUAL"; + static const OP_GREATERTHANOREQUAL = "OP_GREATERTHANOREQUAL"; + static const OP_MIN = "OP_MIN"; + static const OP_MAX = "OP_MAX"; + static const OP_WITHIN = "OP_WITHIN"; + + /// crypto + static const OP_RIPEMD160 = "OP_RIPEMD160"; + static const OP_SHA1 = "OP_SHA1"; + static const OP_SHA256 = "OP_SHA256"; + static const OP_HASH160 = "OP_HASH160"; + static const OP_HASH256 = "OP_HASH256"; + static const OP_CODESEPARATOR = "OP_CODESEPARATOR"; + static const OP_CHECKSIG = "OP_CHECKSIG"; + static const OP_CHECKSIGVERIFY = "OP_CHECKSIGVERIFY"; + static const OP_CHECKMULTISIG = "OP_CHECKMULTISIG"; + static const OP_CHECKMULTISIGVERIFY = "OP_CHECKMULTISIGVERIFY"; + + /// locktime + static const OP_NOP2 = "OP_NOP2"; + static const OP_CHECKLOCKTIMEVERIFY = "OP_CHECKLOCKTIMEVERIFY"; + static const OP_NOP3 = "OP_NOP3"; + static const OP_CHECKSEQUENCEVERIFY = "OP_CHECKSEQUENCEVERIFY"; + static const Map> OP_CODES = { 'OP_0': [0x00], 'OP_FALSE': [0x00], diff --git a/lib/src/bitcoin/script/script.dart b/lib/src/bitcoin/script/script.dart index 7c46945..b7aba14 100644 --- a/lib/src/bitcoin/script/script.dart +++ b/lib/src/bitcoin/script/script.dart @@ -1,3 +1,4 @@ +import 'package:bitcoin_base/src/bitcoin/address/address.dart'; import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; @@ -16,10 +17,13 @@ class Script { script = List.unmodifiable(script); final List script; - static Script fromRaw({required String hexData, bool hasSegwit = false}) { + static Script fromRaw({List? byteData, String? hexData, bool hasSegwit = false}) { List commands = []; int index = 0; - final scriptBytes = BytesUtils.fromHexString(hexData); + final scriptBytes = byteData ?? (hexData != null ? BytesUtils.fromHexString(hexData) : null); + if (scriptBytes == null) { + throw ArgumentError("Invalid script"); + } while (index < scriptBytes.length) { int byte = scriptBytes[index]; if (BitcoinOpCodeConst.CODE_OPS.containsKey(byte)) { @@ -28,39 +32,88 @@ class Script { } else if (!hasSegwit && byte == 0x4c) { int bytesToRead = scriptBytes[index + 1]; index = index + 1; - commands.add(BytesUtils.toHexString( - scriptBytes.sublist(index, index + bytesToRead))); + commands.add(BytesUtils.toHexString(scriptBytes.sublist(index, index + bytesToRead))); index = index + bytesToRead; } else if (!hasSegwit && byte == 0x4d) { int bytesToRead = readUint16LE(scriptBytes, index + 1); index = index + 3; - commands.add(BytesUtils.toHexString( - scriptBytes.sublist(index, index + bytesToRead))); + commands.add(BytesUtils.toHexString(scriptBytes.sublist(index, index + bytesToRead))); index = index + bytesToRead; } else if (!hasSegwit && byte == 0x4e) { int bytesToRead = readUint32LE(scriptBytes, index + 1); index = index + 5; - commands.add(BytesUtils.toHexString( - scriptBytes.sublist(index, index + bytesToRead))); + commands.add(BytesUtils.toHexString(scriptBytes.sublist(index, index + bytesToRead))); index = index + bytesToRead; } else { - final viAndSize = - IntUtils.decodeVarint(scriptBytes.sublist(index, index + 9)); + final viAndSize = IntUtils.decodeVarint(scriptBytes.sublist(index, index + 9)); int dataSize = viAndSize.item1; int size = viAndSize.item2; final lastIndex = (index + size + dataSize) > scriptBytes.length ? scriptBytes.length : (index + size + dataSize); - commands.add(BytesUtils.toHexString( - scriptBytes.sublist(index + size, lastIndex))); + commands.add(BytesUtils.toHexString(scriptBytes.sublist(index + size, lastIndex))); index = index + dataSize + size; } } return Script(script: commands); } + dynamic findScriptParam(int index) { + if (index < script.length) { + return script[index]; + } + return null; + } + + BitcoinAddressType? getAddressType() { + if (script.isEmpty) return null; + + final first = findScriptParam(0); + final sec = findScriptParam(1); + if (sec == null || sec is! String) { + return null; + } + + if (first == "OP_0") { + final lockingScriptBytes = opPushData(sec); + + if (lockingScriptBytes.length == 21) { + return SegwitAddresType.p2wpkh; + } else if (lockingScriptBytes.length == 33) { + return SegwitAddresType.p2wsh; + } + } else if (first == "OP_1") { + final lockingScriptBytes = opPushData(sec); + + if (lockingScriptBytes.length == 33) { + return SegwitAddresType.p2tr; + } + } + + final third = findScriptParam(2); + final fourth = findScriptParam(3); + final fifth = findScriptParam(4); + if (first == "OP_DUP") { + if (sec == "OP_HASH160" && + opPushData(third).length == 21 && + fourth == "OP_EQUALVERIFY" && + fifth == "OP_CHECKSIG") { + return P2pkhAddressType.p2pkh; + } + } else if (first == "OP_HASH160" && opPushData(sec).length == 21 && third == "OP_EQUAL") { + return P2shAddressType.p2pkhInP2sh; + } else if (sec == "OP_CHECKSIG") { + if (first.length == 66) { + return PubKeyAddressType.p2pk; + } + } + + return null; + } + + /// returns a serialized byte version of the script List toBytes() { if (script.isEmpty) return []; DynamicByteTracker scriptBytes = DynamicByteTracker(); diff --git a/lib/src/crypto/keypair/ec_public.dart b/lib/src/crypto/keypair/ec_public.dart index def3907..a4051e8 100644 --- a/lib/src/crypto/keypair/ec_public.dart +++ b/lib/src/crypto/keypair/ec_public.dart @@ -12,8 +12,8 @@ class ECPublic { /// Constructs an ECPublic key from a byte representation. factory ECPublic.fromBytes(List public) { - final publicKey = Bip32PublicKey.fromBytes(public, Bip32KeyData(), - Bip32Const.mainNetKeyNetVersions, EllipticCurveTypes.secp256k1); + final publicKey = Bip32PublicKey.fromBytes( + public, Bip32KeyData(), Bip32Const.mainNetKeyNetVersions, EllipticCurveTypes.secp256k1); return ECPublic(publicKey); } @@ -45,17 +45,17 @@ class ECPublic { return BytesUtils.toHexString(QuickCrypto.hash160(bytes)); } - /// toAddress generates a P2PKH (Pay-to-Public-Key-Hash) address from the ECPublic key. + /// toP2pkhAddress generates a P2PKH (Pay-to-Public-Key-Hash) address from the ECPublic key. /// If 'compressed' is true, the key is in compressed format. - P2pkhAddress toAddress({bool compressed = true}) { + P2pkhAddress toP2pkhAddress({bool compressed = true}) { final h16 = _toHash160(compressed: compressed); final toHex = BytesUtils.toHexString(h16); - return P2pkhAddress.fromHash160(addrHash: toHex); + return P2pkhAddress.fromHash160(h160: toHex); } - /// toSegwitAddress generates a P2WPKH (Pay-to-Witness-Public-Key-Hash) SegWit address + /// toP2wpkhAddress generates a P2WPKH (Pay-to-Witness-Public-Key-Hash) SegWit address /// from the ECPublic key. If 'compressed' is true, the key is in compressed format. - P2wpkhAddress toSegwitAddress({bool compressed = true}) { + P2wpkhAddress toP2wpkhAddress({bool compressed = true}) { final h16 = _toHash160(compressed: compressed); final toHex = BytesUtils.toHexString(h16); @@ -65,8 +65,7 @@ class ECPublic { /// toP2pkAddress generates a P2PK (Pay-to-Public-Key) address from the ECPublic key. /// If 'compressed' is true, the key is in compressed format. P2pkAddress toP2pkAddress({bool compressed = true}) { - final h = toHex(compressed: compressed); - return P2pkAddress(publicKey: h); + return P2pkAddress(publicKey: this); } /// toRedeemScript generates a redeem script from the ECPublic key. @@ -80,39 +79,34 @@ class ECPublic { /// wrapping a P2PK (Pay-to-Public-Key) script derived from the ECPublic key. /// If 'compressed' is true, the key is in compressed format. P2shAddress toP2pkhInP2sh({bool compressed = true, useBCHP2sh32 = false}) { - final addr = toAddress(compressed: compressed); + final addr = toP2pkhAddress(compressed: compressed); final script = addr.toScriptPubKey(); if (useBCHP2sh32) { return P2shAddress.fromHash160( - addrHash: BytesUtils.toHexString( - QuickCrypto.sha256DoubleHash(script.toBytes())), + h160: BytesUtils.toHexString(QuickCrypto.sha256DoubleHash(script.toBytes())), type: P2shAddressType.p2pkhInP2sh32); } - return P2shAddress.fromScript( - script: script, type: P2shAddressType.p2pkhInP2sh); + return P2shAddress.fromRedeemScript(script: script, type: P2shAddressType.p2pkhInP2sh); } /// toP2pkInP2sh generates a P2SH (Pay-to-Script-Hash) address /// wrapping a P2PK (Pay-to-Public-Key) script derived from the ECPublic key. /// If 'compressed' is true, the key is in compressed format. - P2shAddress toP2pkInP2sh( - {bool compressed = true, bool useBCHP2sh32 = false}) { + P2shAddress toP2pkInP2sh({bool compressed = true, bool useBCHP2sh32 = false}) { final script = toRedeemScript(compressed: compressed); if (useBCHP2sh32) { return P2shAddress.fromHash160( - addrHash: BytesUtils.toHexString( - QuickCrypto.sha256DoubleHash(script.toBytes())), + h160: BytesUtils.toHexString(QuickCrypto.sha256DoubleHash(script.toBytes())), type: P2shAddressType.p2pkInP2sh32); } - return P2shAddress.fromScript( - script: script, type: P2shAddressType.p2pkInP2sh); + return P2shAddress.fromRedeemScript(script: script, type: P2shAddressType.p2pkInP2sh); } /// ToTaprootAddress generates a P2TR(Taproot) address from the ECPublic key /// and an optional script. The 'script' parameter can be used to specify /// custom spending conditions. - P2trAddress toTaprootAddress({List>? scripts}) { - final pubKey = toTapRotHex(script: scripts); + P2trAddress toTaprootAddress({List>? scripts, bool tweak = true}) { + final pubKey = toTapRotHex(script: scripts, tweak: tweak); return P2trAddress.fromProgram(program: pubKey); } @@ -120,27 +114,29 @@ class ECPublic { /// wrapping a P2WPKH (Pay-to-Witness-Public-Key-Hash) script derived from the ECPublic key. /// If 'compressed' is true, the key is in compressed format. P2shAddress toP2wpkhInP2sh({bool compressed = true}) { - final addr = toSegwitAddress(compressed: compressed); - return P2shAddress.fromScript( + final addr = toP2wpkhAddress(compressed: compressed); + return P2shAddress.fromRedeemScript( script: addr.toScriptPubKey(), type: P2shAddressType.p2wpkhInP2sh); } + /// toP2wpkhInP2sh generates a P2WSH (Pay-to-Witness-Script-Hash) address + /// wrapping a P2WPKH (Pay-to-Witness-Public-Key-Hash) script derived from the ECPublic key. + /// If 'compressed' is true, the key is in compressed format. + P2wshAddress toP2wpkhInP2wsh({bool compressed = true}) { + return P2wshAddress.fromRedeemScript( + script: toP2wpkhAddress(compressed: compressed).toScriptPubKey()); + } + /// toP2wshScript generates a P2WSH (Pay-to-Witness-Script-Hash) script /// derived from the ECPublic key. If 'compressed' is true, the key is in compressed format. - Script toP2wshScript({bool compressed = true}) { - return Script(script: [ - 'OP_1', - toHex(compressed: compressed), - "OP_1", - "OP_CHECKMULTISIG" - ]); + Script toP2wshRedeemScript({bool compressed = true}) { + return Script(script: ['OP_1', toHex(compressed: compressed), "OP_1", "OP_CHECKMULTISIG"]); } /// toP2wshAddress generates a P2WSH (Pay-to-Witness-Script-Hash) address /// from the ECPublic key. If 'compressed' is true, the key is in compressed format. P2wshAddress toP2wshAddress({bool compressed = true}) { - return P2wshAddress.fromScript( - script: toP2wshScript(compressed: compressed)); + return P2wshAddress.fromRedeemScript(script: toP2wshRedeemScript(compressed: compressed)); } /// toP2wshInP2sh generates a P2SH (Pay-to-Script-Hash) address @@ -148,7 +144,7 @@ class ECPublic { /// If 'compressed' is true, the key is in compressed format. P2shAddress toP2wshInP2sh({bool compressed = true}) { final p2sh = toP2wshAddress(compressed: compressed); - return P2shAddress.fromScript( + return P2shAddress.fromRedeemScript( script: p2sh.toScriptPubKey(), type: P2shAddressType.p2wshInP2sh); } @@ -166,14 +162,15 @@ class ECPublic { } /// returns the x coordinate only as hex string after tweaking (needed for taproot) - String toTapRotHex({List>? script}) { - final scriptBytes = - script?.map((e) => e.map((e) => e.toBytes()).toList()).toList(); - final pubKey = P2TRUtils.tweakPublicKey( - publicKey.point as ProjectiveECCPoint, - script: scriptBytes); - return BytesUtils.toHexString( - BigintUtils.toBytes(pubKey.x, length: publicKey.point.curve.baselen)); + String toTapRotHex({List>? script, bool tweak = true}) { + var x = publicKey.point.x; + if (tweak) { + final scriptBytes = script?.map((e) => e.map((e) => e.toBytes()).toList()).toList(); + final pubKey = + P2TRUtils.tweakPublicKey(publicKey.point as ProjectiveECCPoint, script: scriptBytes); + x = pubKey.x; + } + return BytesUtils.toHexString(BigintUtils.toBytes(x, length: publicKey.point.curve.baselen)); } /// toXOnlyHex extracts and returns the x-coordinate (first 32 bytes) of the ECPublic key diff --git a/lib/src/provider/models/multisig_script.dart b/lib/src/provider/models/multisig_script.dart index 63f0fe0..c9770a3 100644 --- a/lib/src/provider/models/multisig_script.dart +++ b/lib/src/provider/models/multisig_script.dart @@ -53,12 +53,12 @@ class MultiSignatureAddress { throw ArgumentError( "${network.conf.coinName.name} Bitcoin forks that do not support Segwit. use toP2shAddress"); } - return P2wshAddress.fromScript(script: multiSigScript); + return P2wshAddress.fromScriptPubkey(script: multiSigScript); } BitcoinBaseAddress toP2wshInP2shAddress({required BasedUtxoNetwork network}) { final p2wsh = toP2wshAddress(network: network); - return P2shAddress.fromScript( + return P2shAddress.fromScriptPubkey( script: p2wsh.toScriptPubKey(), type: P2shAddressType.p2wshInP2sh); } @@ -71,11 +71,11 @@ class MultiSignatureAddress { if (addressType.hashLength == 32) { return P2shAddress.fromHash160( - addrHash: BytesUtils.toHexString( + h160: BytesUtils.toHexString( QuickCrypto.sha256DoubleHash(multiSigScript.toBytes())), type: addressType); } - return P2shAddress.fromScript(script: multiSigScript, type: addressType); + return P2shAddress.fromScriptPubkey(script: multiSigScript, type: addressType); } BitcoinBaseAddress fromType({ diff --git a/lib/src/provider/models/utxo_details.dart b/lib/src/provider/models/utxo_details.dart index a5c50e6..8223d64 100644 --- a/lib/src/provider/models/utxo_details.dart +++ b/lib/src/provider/models/utxo_details.dart @@ -223,7 +223,7 @@ class BitcoinUtxo { /// convert utxos to transaction input with specify sequence like ReplaceByeFee (4Bytes) TxInput toInput([List? sequence]) { - return TxInput(txId: txHash, txIndex: vout, sequance: sequence); + return TxInput(txId: txHash, txIndex: vout, sequence: sequence); } @override diff --git a/lib/src/provider/transaction_builder/forked_transaction_builder.dart b/lib/src/provider/transaction_builder/forked_transaction_builder.dart index 8ef24b6..d9725e3 100644 --- a/lib/src/provider/transaction_builder/forked_transaction_builder.dart +++ b/lib/src/provider/transaction_builder/forked_transaction_builder.dart @@ -8,7 +8,7 @@ import 'package:blockchain_utils/blockchain_utils.dart'; /// such as UTXOs, memo, enableRBF (Replace-By-Fee), and more. /// /// Parameters: -/// - [outPuts]: List of Bitcoin outputs to be included in the transaction. +/// - [outputs]: List of Bitcoin outputs to be included in the transaction. /// - [fee]: Transaction fee (BigInt) for processing the transaction. /// - [network]: The target Bitcoin network (Bitcoin Cash or Bitcoin SV). /// - [utxosInfo]: List of UtxoWithAddress objects providing information about Unspent Transaction Outputs (UTXOs). @@ -20,7 +20,7 @@ import 'package:blockchain_utils/blockchain_utils.dart'; /// /// Note: The constructor automatically validates the builder by calling the [_validateBuilder] method. class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { - final List outPuts; + final List outputs; final BigInt fee; final BasedUtxoNetwork network; final List utxosInfo; @@ -30,7 +30,7 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { final BitcoinOrdering inputOrdering; final BitcoinOrdering outputOrdering; ForkedTransactionBuilder( - {required this.outPuts, + {required this.outputs, required this.fee, required this.network, required List utxos, @@ -51,7 +51,7 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { for (final i in utxosInfo) { i.ownerDetails.address.toAddress(network); } - for (final i in outPuts) { + for (final i in outputs) { if (i is BitcoinOutput) { i.address.toAddress(network); } @@ -73,7 +73,7 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { utxos: utxos, /// We select transaction outputs - outPuts: outputs, + outputs: outputs, /// Transaction fee /// Ensure that you have accurately calculated the amounts. @@ -143,7 +143,7 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { case P2pkhAddressType.p2pkhwt: case P2shAddressType.p2pkhInP2shwt: case P2shAddressType.p2pkhInP2sh32wt: - return senderPub.toAddress().toScriptPubKey(); + return senderPub.toP2pkhAddress().toScriptPubKey(); default: throw MessageException( "${utxo.utxo.scriptType} does not sudpport on ${network.conf.coinName.name}"); @@ -215,7 +215,7 @@ that demonstrate the right to spend the bitcoins associated with the correspondi case P2shAddressType.p2pkhInP2shwt: case P2shAddressType.p2pkhInP2sh32: case P2shAddressType.p2pkhInP2sh32wt: - final script = senderPub.toAddress().toScriptPubKey(); + final script = senderPub.toP2pkhAddress().toScriptPubKey(); return [signedDigest, senderPub.toHex(), script.toHex()]; case P2shAddressType.p2pkInP2sh: case P2shAddressType.p2pkInP2shwt: @@ -256,20 +256,20 @@ that demonstrate the right to spend the bitcoins associated with the correspondi } List _buildOutputs() { - List outputs = outPuts + List builtOutputs = outputs .where((element) => element is! BitcoinBurnableOutput) .map((e) => e.toOutput) .toList(); if (memo != null) { - outputs + builtOutputs .add(TxOutput(amount: BigInt.zero, scriptPubKey: _opReturn(memo!))); } if (outputOrdering == BitcoinOrdering.shuffle) { - outputs = outputs..shuffle(); + builtOutputs = builtOutputs..shuffle(); } else if (outputOrdering == BitcoinOrdering.bip69) { - outputs = outputs + builtOutputs = builtOutputs ..sort( (a, b) { final valueComparison = a.amount.compareTo(b.amount); @@ -281,7 +281,7 @@ that demonstrate the right to spend the bitcoins associated with the correspondi }, ); } - return List.unmodifiable(outputs); + return List.unmodifiable(builtOutputs); } /* @@ -359,7 +359,7 @@ be retrieved by anyone who examines the blockchain's history. for (final i in sumOfTokenUtxos.entries) { if (sumTokenOutputAmouts[i.key] != i.value) { BigInt amount = sumTokenOutputAmouts[i.key] ?? BigInt.zero; - amount += outPuts + amount += outputs .whereType() .where((element) => element.categoryID == i.key) .fold( @@ -383,12 +383,12 @@ be retrieved by anyone who examines the blockchain's history. final token = i.utxo.token!; if (token.hasAmount) continue; if (!token.hasNFT) continue; - final hasOneoutput = outPuts.whereType().any( + final hasOneoutput = outputs.whereType().any( (element) => element.utxoHash == i.utxo.txHash && element.token.category == token.category); if (hasOneoutput) continue; - final hasBurnableOutput = outPuts + final hasBurnableOutput = outputs .whereType() .any((element) => element.utxoHash == i.utxo.txHash && diff --git a/lib/src/provider/transaction_builder/transaction_builder.dart b/lib/src/provider/transaction_builder/transaction_builder.dart index b8029d1..46d5c96 100644 --- a/lib/src/provider/transaction_builder/transaction_builder.dart +++ b/lib/src/provider/transaction_builder/transaction_builder.dart @@ -21,7 +21,7 @@ import 'package:blockchain_utils/blockchain_utils.dart'; /// /// Note: The constructor automatically validates the builder by calling the [_validateBuilder] method. class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { - final List outPuts; + final List outputs; final BigInt fee; final BasedUtxoNetwork network; final List utxosInfo; @@ -31,7 +31,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { final BitcoinOrdering inputOrdering; final BitcoinOrdering outputOrdering; BitcoinTransactionBuilder({ - required this.outPuts, + required this.outputs, required this.fee, required this.network, required List utxos, @@ -51,8 +51,8 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { "invalid network for BitcoinCashNetwork and BSVNetwork use ForkedTransactionBuilder"); } final token = utxosInfo.any((element) => element.utxo.token != null); - final tokenInput = outPuts.whereType(); - final burn = outPuts.whereType(); + final tokenInput = outputs.whereType(); + final burn = outputs.whereType(); if (token || tokenInput.isNotEmpty || burn.isNotEmpty) { throw const MessageException( "Cash Token only work on Bitcoin cash network"); @@ -61,7 +61,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { /// Verify each input for its association with this network's address. Raise an exception if the address is incorrect. i.ownerDetails.address.toAddress(network); } - for (final i in outPuts) { + for (final i in outputs) { if (i is BitcoinOutput) { /// Verify each output for its association with this network's address. Raise an exception if the address is incorrect. i.address.toAddress(network); @@ -83,7 +83,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { utxos: utxos, /// We select transaction outputs - outPuts: outputs, + outputs: outputs, /* Transaction fee Ensure that you have accurately calculated the amounts. @@ -205,31 +205,31 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { if (isTaproot) { return senderPub.toP2wshAddress().toScriptPubKey(); } - return senderPub.toP2wshScript(); + return senderPub.toP2wshRedeemScript(); case P2pkhAddressType.p2pkh: - return senderPub.toAddress().toScriptPubKey(); + return senderPub.toP2pkhAddress().toScriptPubKey(); case SegwitAddresType.p2wpkh: if (isTaproot) { - return senderPub.toSegwitAddress().toScriptPubKey(); + return senderPub.toP2wpkhAddress().toScriptPubKey(); } - return senderPub.toAddress().toScriptPubKey(); + return senderPub.toP2pkhAddress().toScriptPubKey(); case SegwitAddresType.p2tr: return senderPub.toTaprootAddress().toScriptPubKey(); case P2shAddressType.p2pkhInP2sh: if (isTaproot) { return senderPub.toP2pkhInP2sh().toScriptPubKey(); } - return senderPub.toAddress().toScriptPubKey(); + return senderPub.toP2pkhAddress().toScriptPubKey(); case P2shAddressType.p2wpkhInP2sh: if (isTaproot) { return senderPub.toP2wpkhInP2sh().toScriptPubKey(); } - return senderPub.toAddress().toScriptPubKey(); + return senderPub.toP2pkhAddress().toScriptPubKey(); case P2shAddressType.p2wshInP2sh: if (isTaproot) { return senderPub.toP2wshInP2sh().toScriptPubKey(); } - return senderPub.toP2wshScript(); + return senderPub.toP2wshRedeemScript(); case P2shAddressType.p2pkInP2sh: if (isTaproot) { return senderPub.toP2pkInP2sh().toScriptPubKey(); @@ -310,7 +310,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { final script = Script.fromRaw( hexData: utxo.multiSigAddress.multiSigScript.toHex(), hasSegwit: true); - final p2wsh = P2wshAddress.fromScript(script: script); + final p2wsh = P2wshAddress.fromRedeemScript(script: script); return [p2wsh.toScriptPubKey().toHex()]; default: throw Exception( @@ -323,7 +323,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { final script = senderPub.toP2wshAddress().toScriptPubKey(); return [script.toHex()]; case P2shAddressType.p2wpkhInP2sh: - final script = senderPub.toSegwitAddress().toScriptPubKey(); + final script = senderPub.toP2wpkhAddress().toScriptPubKey(); return [script.toHex()]; default: throw Exception( @@ -346,7 +346,7 @@ that demonstrate the right to spend the bitcoins associated with the correspondi switch (utx.utxo.scriptType) { case P2shAddressType.p2wshInP2sh: case SegwitAddresType.p2wsh: - final script = senderPub.toP2wshScript(); + final script = senderPub.toP2wshRedeemScript(); return ['', signedDigest, script.toHex()]; case SegwitAddresType.p2wpkh: case P2shAddressType.p2wpkhInP2sh: @@ -362,7 +362,7 @@ that demonstrate the right to spend the bitcoins associated with the correspondi case P2pkhAddressType.p2pkh: return [signedDigest, senderPub.toHex()]; case P2shAddressType.p2pkhInP2sh: - final script = senderPub.toAddress().toScriptPubKey(); + final script = senderPub.toP2pkhAddress().toScriptPubKey(); return [signedDigest, senderPub.toHex(), script.toHex()]; case P2shAddressType.p2pkInP2sh: final script = senderPub.toRedeemScript(); @@ -422,15 +422,15 @@ that demonstrate the right to spend the bitcoins associated with the correspondi // } List _buildOutputs() { - List outputs = outPuts.map((e) => e.toOutput).toList(); + List builtOutputs = outputs.map((e) => e.toOutput).toList(); if (memo != null) { - outputs + builtOutputs .add(TxOutput(amount: BigInt.zero, scriptPubKey: _opReturn(memo!))); } if (outputOrdering == BitcoinOrdering.shuffle) { - outputs = outputs..shuffle(); + builtOutputs = builtOutputs..shuffle(); } else if (outputOrdering == BitcoinOrdering.bip69) { - outputs = outputs + builtOutputs = builtOutputs ..sort( (a, b) { final valueComparison = a.amount.compareTo(b.amount); @@ -442,7 +442,7 @@ that demonstrate the right to spend the bitcoins associated with the correspondi }, ); } - return List.unmodifiable(outputs); + return List.unmodifiable(builtOutputs); } /// The primary use case for OP_RETURN is data storage. You can embed various types of diff --git a/lib/src/utils/script.dart b/lib/src/utils/script.dart new file mode 100644 index 0000000..f5a3838 --- /dev/null +++ b/lib/src/utils/script.dart @@ -0,0 +1,36 @@ +import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; + +bool isDefinedHashType(sighash) { + final hashTypeMod = sighash & ~BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; + return hashTypeMod > BitcoinOpCodeConst.SIGHASH_ALL && + hashTypeMod < BitcoinOpCodeConst.SIGHASH_SINGLE; +} + +bool bip66check(buffer) { + if (buffer.length < 8) return false; + if (buffer.length > 72) return false; + if (buffer[0] != 0x30) return false; + if (buffer[1] != buffer.length - 2) return false; + if (buffer[2] != 0x02) return false; + + var lenR = buffer[3]; + if (lenR == 0) return false; + if (5 + lenR >= buffer.length) return false; + if (buffer[4 + lenR] != 0x02) return false; + + var lenS = buffer[5 + lenR]; + if (lenS == 0) return false; + if ((6 + lenR + lenS) != buffer.length) return false; + + if (buffer[4] & 0x80 != 0) return false; + if (lenR > 1 && (buffer[4] == 0x00) && buffer[5] & 0x80 == 0) return false; + + if (buffer[lenR + 6] & 0x80 != 0) return false; + if (lenS > 1 && (buffer[lenR + 6] == 0x00) && buffer[lenR + 7] & 0x80 == 0) return false; + return true; +} + +bool isCanonicalScriptSignature(List buffer) { + if (!isDefinedHashType(buffer[buffer.length - 1])) return false; + return bip66check(buffer.sublist(0, buffer.length - 1)); +} diff --git a/test/keys_test.dart b/test/keys_test.dart index ac2d818..6f788a8 100644 --- a/test/keys_test.dart +++ b/test/keys_test.dart @@ -124,10 +124,10 @@ void main() { test("testPubkeyCreation", () { final pub1 = ECPublic.fromHex(publicKeyHex); expect( - pub1.toAddress(compressed: false).toAddress(BitcoinNetwork.mainnet), + pub1.toP2pkhAddress(compressed: false).toAddress(BitcoinNetwork.mainnet), unCompressedAddress); expect(pub1.toBytes(whitPrefix: false), publicKeyBytes); - expect(pub1.toHash160(), pub1.toAddress(compressed: true).addressProgram); + expect(pub1.toHash160(), pub1.toP2pkhAddress(compressed: true).addressProgram); }); }); @@ -137,8 +137,8 @@ void main() { const String address = '1EHNa6Q4Jz2uvNExL497mE43ikXhwF6kZm'; const String addressc = '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH'; test("test1", () { - final p1 = P2pkhAddress.fromHash160(addrHash: hash160); - final p2 = P2pkhAddress.fromHash160(addrHash: hash160c); + final p1 = P2pkhAddress.fromHash160(h160: hash160); + final p2 = P2pkhAddress.fromHash160(h160: hash160c); expect(p1.toAddress(BitcoinNetwork.mainnet), address); expect(p2.toAddress(BitcoinNetwork.mainnet), addressc); }); @@ -181,7 +181,7 @@ void main() { const String p2shaddress = '2NDkr9uD2MSY5em3rsjkff8fLZcJzCfY3W1'; test("test create", () { final script = Script(script: [pub.toHex(), 'OP_CHECKSIG']); - final addr = P2shAddress.fromScript( + final addr = P2shAddress.fromRedeemScript( script: script, type: P2shAddressType.p2pkInP2sh); expect(addr.toAddress(BitcoinNetwork.testnet), p2shaddress); }); @@ -202,7 +202,7 @@ void main() { '2NC2DBZd3WfEF9cZcpBRDYxCTGCVCfPUf7Q'; test("test1", () { final address = P2wpkhAddress.fromProgram( - program: pub.toSegwitAddress().addressProgram); + program: pub.toP2wpkhAddress().addressProgram); expect(correctP2wpkhAddress, address.toAddress(BitcoinNetwork.testnet)); }); test("test2", () { @@ -210,8 +210,8 @@ void main() { "cTmyBsxMQ3vyh4J3jCKYn2Au7AhTKvqeYuxxkinsg6Rz3BBPrYKK", netVersion: BitcoinNetwork.testnet.wifNetVer) .getPublic() - .toSegwitAddress(); - final p2sh = P2shAddress.fromScript( + .toP2wpkhAddress(); + final p2sh = P2shAddress.fromRedeemScript( script: addr.toScriptPubKey(), type: P2shAddressType.p2pkInP2sh); expect(correctP2shP2wpkhAddress, p2sh.toAddress(BitcoinNetwork.testnet)); }); @@ -225,7 +225,7 @@ void main() { 'OP_1', 'OP_CHECKMULTISIG' ]); - final pw = P2wshAddress.fromScript(script: script); + final pw = P2wshAddress.fromRedeemScript(script: script); expect(pw.toAddress(BitcoinNetwork.testnet), correctP2wshAddress); }); test("test4", () { @@ -238,8 +238,8 @@ void main() { 'OP_1', 'OP_CHECKMULTISIG' ]); - final pw = P2wshAddress.fromScript(script: script); - final p2sh = P2shAddress.fromScript( + final pw = P2wshAddress.fromRedeemScript(script: script); + final p2sh = P2shAddress.fromRedeemScript( script: pw.toScriptPubKey(), type: P2shAddressType.p2pkInP2sh); expect(p2sh.toAddress(BitcoinNetwork.testnet), correctP2shP2wshAddress); }); diff --git a/test/p2sh_test.dart b/test/p2sh_test.dart index 5b515b4..76cb159 100644 --- a/test/p2sh_test.dart +++ b/test/p2sh_test.dart @@ -16,7 +16,7 @@ void main() { Script(script: [p2pkSk.getPublic().toHex(), 'OP_CHECKSIG']); final txout = TxOutput( amount: BigInt.from(9000000), - scriptPubKey: P2shAddress.fromScript( + scriptPubKey: P2shAddress.fromRedeemScript( script: p2pkRedeemScript, type: P2shAddressType.p2pkInP2sh) .toScriptPubKey()); const createP2shAndSendResult = @@ -42,7 +42,7 @@ void main() { txId: 'f557c623e55f0affc696b742630770df2342c4aac395e0ed470923247bc51b95', txIndex: 0, - sequance: seq.forInputSequence()); + sequence: seq.forInputSequence()); final anotherAddr = P2pkhAddress.fromAddress( address: 'n4bkvTyU1dVdzsrhWBqBw8fEMbHjJvtmJR', network: BitcoinNetwork.testnet); diff --git a/test/p2wpkh_test.dart b/test/p2wpkh_test.dart index badf9b8..0263810 100644 --- a/test/p2wpkh_test.dart +++ b/test/p2wpkh_test.dart @@ -7,9 +7,9 @@ void main() { "cTALNpTpRbbxTCJ2A5Vq88UxT44w1PE2cYqiB3n4hRvzyCev1Wwo", netVersion: BitcoinNetwork.testnet.wifNetVer); - final p2pkhAddr = sk.getPublic().toAddress(); + final p2pkhAddr = sk.getPublic().toP2pkhAddress(); - final p2wpkhAddr = sk.getPublic().toSegwitAddress(); + final p2wpkhAddr = sk.getPublic().toP2wpkhAddress(); final txin1 = TxInput( txId: diff --git a/test/p2wsh_test.dart b/test/p2wsh_test.dart index 1b78659..88f17e4 100644 --- a/test/p2wsh_test.dart +++ b/test/p2wsh_test.dart @@ -18,9 +18,9 @@ void main() { 'OP_CHECKMULTISIG' ]); - final p2wshAddr = P2wshAddress.fromScript(script: p2wshScript); + final p2wshAddr = P2wshAddress.fromRedeemScript(script: p2wshScript); - final p2pkhAddr = sk1.getPublic().toAddress(); + final p2pkhAddr = sk1.getPublic().toP2pkhAddress(); final txin1 = TxInput( txId: @@ -59,7 +59,7 @@ void main() { amount: BigInt.from(100000), scriptPubKey: p2wshAddr.toScriptPubKey()); final output2Multiple = TxOutput( amount: BigInt.from(100000), - scriptPubKey: sk1.getPublic().toSegwitAddress().toScriptPubKey()); + scriptPubKey: sk1.getPublic().toP2wpkhAddress().toScriptPubKey()); final output3Multiple = TxOutput( amount: BigInt.from(1770000), scriptPubKey: p2pkhAddr.toScriptPubKey()); From 9611e9db77e92a8434e918cdfb620068f6fcb1aa Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Thu, 8 Feb 2024 17:34:51 -0300 Subject: [PATCH 02/53] fix: delete invalid script --- lib/src/crypto/keypair/ec_public.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/src/crypto/keypair/ec_public.dart b/lib/src/crypto/keypair/ec_public.dart index a4051e8..9f8e45e 100644 --- a/lib/src/crypto/keypair/ec_public.dart +++ b/lib/src/crypto/keypair/ec_public.dart @@ -119,14 +119,6 @@ class ECPublic { script: addr.toScriptPubKey(), type: P2shAddressType.p2wpkhInP2sh); } - /// toP2wpkhInP2sh generates a P2WSH (Pay-to-Witness-Script-Hash) address - /// wrapping a P2WPKH (Pay-to-Witness-Public-Key-Hash) script derived from the ECPublic key. - /// If 'compressed' is true, the key is in compressed format. - P2wshAddress toP2wpkhInP2wsh({bool compressed = true}) { - return P2wshAddress.fromRedeemScript( - script: toP2wpkhAddress(compressed: compressed).toScriptPubKey()); - } - /// toP2wshScript generates a P2WSH (Pay-to-Witness-Script-Hash) script /// derived from the ECPublic key. If 'compressed' is true, the key is in compressed format. Script toP2wshRedeemScript({bool compressed = true}) { From 235ec3654d0d0367642e474df1a99af85186afd7 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 13 Feb 2024 18:23:58 -0300 Subject: [PATCH 03/53] fix: use p2wpkh as default for validate --- lib/src/bitcoin/address/util.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bitcoin/address/util.dart b/lib/src/bitcoin/address/util.dart index a11b90b..e59978a 100644 --- a/lib/src/bitcoin/address/util.dart +++ b/lib/src/bitcoin/address/util.dart @@ -31,5 +31,5 @@ List addressToOutputScript({required String address, required BasedUtxoNetw return P2trAddress.fromAddress(address: address, network: network).toScriptPubKey().toBytes(); } - throw ArgumentError('$address has no matching Script'); + return P2wpkhAddress.fromAddress(address: address, network: network).toScriptPubKey().toBytes(); } From b2e994704a812f0a5bbd7eb6aee21fac88880625 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Sun, 25 Feb 2024 16:04:53 -0300 Subject: [PATCH 04/53] feat: pull in new test vectors, adjust to latest bip0352 spec --- lib/bitcoin_base.dart | 2 + lib/src/bitcoin/address/core.dart | 66 +- lib/src/bitcoin/address/segwit_address.dart | 14 +- lib/src/bitcoin/script/outpoint.dart | 18 + lib/src/bitcoin/script/witness.dart | 22 +- lib/src/bitcoin/silent_payments/address.dart | 167 + lib/src/bitcoin/silent_payments/payment.dart | 213 ++ .../silent_payments/silent_payments.dart | 23 + lib/src/bitcoin/silent_payments/utils.dart | 116 + lib/src/crypto/keypair/ec_private.dart | 60 +- lib/src/crypto/keypair/ec_public.dart | 36 +- pubspec.yaml | 5 +- test/fixtures/silent_payments.json | 2766 +++++++++++++++++ test/silent_payments.dart | 290 ++ 14 files changed, 3746 insertions(+), 52 deletions(-) create mode 100644 lib/src/bitcoin/script/outpoint.dart create mode 100644 lib/src/bitcoin/silent_payments/address.dart create mode 100644 lib/src/bitcoin/silent_payments/payment.dart create mode 100644 lib/src/bitcoin/silent_payments/silent_payments.dart create mode 100644 lib/src/bitcoin/silent_payments/utils.dart create mode 100644 test/fixtures/silent_payments.json create mode 100644 test/silent_payments.dart diff --git a/lib/bitcoin_base.dart b/lib/bitcoin_base.dart index 7fd6954..c8558ec 100644 --- a/lib/bitcoin_base.dart +++ b/lib/bitcoin_base.dart @@ -23,3 +23,5 @@ export 'package:bitcoin_base/src/utils/btc_utils.dart'; export 'package:bitcoin_base/src/cash_token/cash_token.dart'; export 'package:bitcoin_base/src/bitcoin_cash/bitcoin_cash.dart'; + +export 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; diff --git a/lib/src/bitcoin/address/core.dart b/lib/src/bitcoin/address/core.dart index 7ae6bfb..42b5b66 100644 --- a/lib/src/bitcoin/address/core.dart +++ b/lib/src/bitcoin/address/core.dart @@ -9,8 +9,7 @@ abstract class BitcoinAddressType implements Enumerate { /// Factory method to create a BitcoinAddressType enum value from a name or value. static BitcoinAddressType fromValue(String value) { return values.firstWhere((element) => element.value == value, - orElse: () => - throw MessageException('Invalid BitcoinAddressType: $value')); + orElse: () => throw MessageException('Invalid BitcoinAddressType: $value')); } /// Check if the address type is Pay-to-Script-Hash (P2SH). @@ -34,7 +33,8 @@ abstract class BitcoinAddressType implements Enumerate { P2shAddressType.p2pkInP2sh32wt, P2shAddressType.p2pkhInP2shwt, P2shAddressType.p2pkInP2shwt, - P2pkhAddressType.p2pkhwt + P2pkhAddressType.p2pkhwt, + SilentPaymentsAddresType.p2sp ]; @override String toString() => value; @@ -84,14 +84,14 @@ class P2pkhAddressType implements BitcoinAddressType { class P2shAddressType implements BitcoinAddressType { const P2shAddressType._(this.value, this.hashLength, this.withToken); - static const P2shAddressType p2wshInP2sh = P2shAddressType._( - "P2SH/P2WSH", _BitcoinAddressUtils.hash160DigestLength, false); - static const P2shAddressType p2wpkhInP2sh = P2shAddressType._( - "P2SH/P2WPKH", _BitcoinAddressUtils.hash160DigestLength, false); - static const P2shAddressType p2pkhInP2sh = P2shAddressType._( - "P2SH/P2PKH", _BitcoinAddressUtils.hash160DigestLength, false); - static const P2shAddressType p2pkInP2sh = P2shAddressType._( - "P2SH/P2PK", _BitcoinAddressUtils.hash160DigestLength, false); + static const P2shAddressType p2wshInP2sh = + P2shAddressType._("P2SH/P2WSH", _BitcoinAddressUtils.hash160DigestLength, false); + static const P2shAddressType p2wpkhInP2sh = + P2shAddressType._("P2SH/P2WPKH", _BitcoinAddressUtils.hash160DigestLength, false); + static const P2shAddressType p2pkhInP2sh = + P2shAddressType._("P2SH/P2PKH", _BitcoinAddressUtils.hash160DigestLength, false); + static const P2shAddressType p2pkInP2sh = + P2shAddressType._("P2SH/P2PK", _BitcoinAddressUtils.hash160DigestLength, false); @override bool get isP2sh => true; @override @@ -103,27 +103,27 @@ class P2shAddressType implements BitcoinAddressType { /// specify BCH NETWORK for now! /// Pay-to-Script-Hash-32 - static const P2shAddressType p2pkhInP2sh32 = P2shAddressType._( - "P2SH32/P2PKH", _BitcoinAddressUtils.scriptHashLenght, false); + static const P2shAddressType p2pkhInP2sh32 = + P2shAddressType._("P2SH32/P2PKH", _BitcoinAddressUtils.scriptHashLenght, false); //// Pay-to-Script-Hash-32 - static const P2shAddressType p2pkInP2sh32 = P2shAddressType._( - "P2SH32/P2PK", _BitcoinAddressUtils.scriptHashLenght, false); + static const P2shAddressType p2pkInP2sh32 = + P2shAddressType._("P2SH32/P2PK", _BitcoinAddressUtils.scriptHashLenght, false); /// Pay-to-Script-Hash-32-with-token - static const P2shAddressType p2pkhInP2sh32wt = P2shAddressType._( - "P2SH32WT/P2PKH", _BitcoinAddressUtils.scriptHashLenght, true); + static const P2shAddressType p2pkhInP2sh32wt = + P2shAddressType._("P2SH32WT/P2PKH", _BitcoinAddressUtils.scriptHashLenght, true); /// Pay-to-Script-Hash-32-with-token - static const P2shAddressType p2pkInP2sh32wt = P2shAddressType._( - "P2SH32WT/P2PK", _BitcoinAddressUtils.scriptHashLenght, true); + static const P2shAddressType p2pkInP2sh32wt = + P2shAddressType._("P2SH32WT/P2PK", _BitcoinAddressUtils.scriptHashLenght, true); /// Pay-to-Script-Hash-with-token - static const P2shAddressType p2pkhInP2shwt = P2shAddressType._( - "P2SHWT/P2PKH", _BitcoinAddressUtils.hash160DigestLength, true); + static const P2shAddressType p2pkhInP2shwt = + P2shAddressType._("P2SHWT/P2PKH", _BitcoinAddressUtils.hash160DigestLength, true); /// Pay-to-Script-Hash-with-token - static const P2shAddressType p2pkInP2shwt = P2shAddressType._( - "P2SHWT/P2PK", _BitcoinAddressUtils.hash160DigestLength, true); + static const P2shAddressType p2pkInP2shwt = + P2shAddressType._("P2SHWT/P2PK", _BitcoinAddressUtils.hash160DigestLength, true); @override final String value; @@ -158,3 +158,23 @@ class SegwitAddresType implements BitcoinAddressType { @override String toString() => value; } + +class SilentPaymentsAddresType implements BitcoinAddressType { + const SilentPaymentsAddresType._(this.value); + static const SilentPaymentsAddresType p2sp = SilentPaymentsAddresType._("P2SP"); + @override + bool get isP2sh => false; + @override + bool get isSegwit => true; + + @override + final String value; + + @override + int get hashLength { + return 32; + } + + @override + String toString() => value; +} diff --git a/lib/src/bitcoin/address/segwit_address.dart b/lib/src/bitcoin/address/segwit_address.dart index 8b3ebb0..63961df 100644 --- a/lib/src/bitcoin/address/segwit_address.dart +++ b/lib/src/bitcoin/address/segwit_address.dart @@ -10,8 +10,12 @@ abstract class SegwitAddress implements BitcoinBaseAddress { address: address, version: segwitVersion, network: network); } SegwitAddress.fromProgram( - {required String program, required this.segwitVersion, required SegwitAddresType addressType}) - : addressProgram = _BitcoinAddressUtils.validateAddressProgram(program, addressType); + {required String program, + required this.segwitVersion, + required SegwitAddresType addressType, + ECPublic? ecpublic}) + : addressProgram = _BitcoinAddressUtils.validateAddressProgram(program, addressType), + pubkey = ecpublic; SegwitAddress.fromRedeemScript({required Script script, required this.segwitVersion}) : addressProgram = _BitcoinAddressUtils.segwitScriptToSHA256(script); @@ -19,6 +23,7 @@ abstract class SegwitAddress implements BitcoinBaseAddress { late final String addressProgram; final int segwitVersion; + ECPublic? pubkey; @override String toAddress(BasedUtxoNetwork network) { @@ -75,11 +80,12 @@ class P2trAddress extends SegwitAddress { P2trAddress.fromAddress({required String address, required BasedUtxoNetwork network}) : super.fromAddress( segwitVersion: _BitcoinAddressUtils.segwitV1, address: address, network: network); - P2trAddress.fromProgram({required String program}) + P2trAddress.fromProgram({required String program, ECPublic? pubkey}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV1, program: program, - addressType: SegwitAddresType.p2tr); + addressType: SegwitAddresType.p2tr, + ecpublic: pubkey); P2trAddress.fromRedeemScript({required Script script}) : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV1, script: script); diff --git a/lib/src/bitcoin/script/outpoint.dart b/lib/src/bitcoin/script/outpoint.dart new file mode 100644 index 0000000..8af5748 --- /dev/null +++ b/lib/src/bitcoin/script/outpoint.dart @@ -0,0 +1,18 @@ +import 'package:blockchain_utils/blockchain_utils.dart'; + +class Outpoint { + Outpoint({required this.txid, required this.index, this.value}); + + String txid; + int index; + int? value; + + factory Outpoint.fromBytes(List txid, int index, {int? value}) { + return Outpoint(txid: BytesUtils.toHexString(txid), index: index, value: value); + } + + @override + String toString() { + return 'Outpoint{txid: $txid, index: $index, value: $value}'; + } +} diff --git a/lib/src/bitcoin/script/witness.dart b/lib/src/bitcoin/script/witness.dart index ec0e1f0..3014add 100644 --- a/lib/src/bitcoin/script/witness.dart +++ b/lib/src/bitcoin/script/witness.dart @@ -1,13 +1,28 @@ +import 'dart:typed_data'; + import 'package:blockchain_utils/binary/utils.dart'; import 'package:blockchain_utils/numbers/int_utils.dart'; +class ScriptWitness { + List stack; + + ScriptWitness({List? stack}) : stack = stack ?? []; + + bool isNull() { + return stack.isEmpty; + } +} + /// A list of the witness items required to satisfy the locking conditions of a segwit input (aka witness stack). /// /// [stack] the witness items (hex str) list class TxWitnessInput { - TxWitnessInput({required List stack}) - : stack = List.unmodifiable(stack); + TxWitnessInput({required List stack, ScriptWitness? scriptWitness}) + : stack = List.unmodifiable(stack), + scriptWitness = scriptWitness ?? ScriptWitness(); + final List stack; + ScriptWitness scriptWitness; /// creates a copy of the object (classmethod) TxWitnessInput copy() { @@ -19,8 +34,7 @@ class TxWitnessInput { List stackBytes = []; for (String item in stack) { - List itemBytes = - IntUtils.prependVarint(BytesUtils.fromHexString(item)); + List itemBytes = IntUtils.prependVarint(BytesUtils.fromHexString(item)); stackBytes = [...stackBytes, ...itemBytes]; } diff --git a/lib/src/bitcoin/silent_payments/address.dart b/lib/src/bitcoin/silent_payments/address.dart new file mode 100644 index 0000000..84428dc --- /dev/null +++ b/lib/src/bitcoin/silent_payments/address.dart @@ -0,0 +1,167 @@ +// ignore_for_file: constant_identifier_names +// ignore_for_file: non_constant_identifier_names +part of 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; + +const SCAN_PATH = "m/352'/1'/0'/1'/0"; + +const SPEND_PATH = "m/352'/1'/0'/0'/0"; + +class SilentPaymentOwner extends SilentPaymentAddress { + late ECPrivate b_scan; + late ECPrivate b_spend; + + SilentPaymentOwner({ + required int version, + required ECPublic B_scan, + required ECPublic B_spend, + required String hrp, + required this.b_scan, + required this.b_spend, + }) : super(version: version, B_scan: B_scan, B_spend: B_spend, hrp: hrp); + + factory SilentPaymentOwner.fromPrivateKeys( + {required ECPrivate b_scan, required ECPrivate b_spend, String? hrp, int? version}) { + return SilentPaymentOwner( + b_scan: b_scan, + b_spend: b_spend, + B_scan: b_scan.getPublic(), + B_spend: b_spend.getPublic(), + hrp: hrp ?? 'sp', + version: version ?? 0, + ); + } + + factory SilentPaymentOwner.fromHd(Bip32Slip10Secp256k1 bip32, {String? hrp, int? version}) { + final scanDerivation = bip32.derivePath(SCAN_PATH); + final spendDerivation = bip32.derivePath(SPEND_PATH); + + return SilentPaymentOwner( + b_scan: ECPrivate(scanDerivation.privateKey), + b_spend: ECPrivate(spendDerivation.privateKey), + B_scan: ECPublic(scanDerivation.publicKey), + B_spend: ECPublic(spendDerivation.publicKey), + hrp: hrp ?? 'sp', + version: version ?? 0, + ); + } + + factory SilentPaymentOwner.fromMnemonic(String mnemonic, {String? hrp, int? version}) { + return SilentPaymentOwner.fromHd( + Bip32Slip10Secp256k1.fromSeed( + Bip39MnemonicDecoder().decode(mnemonic), + hrp == "tsp" ? Bip32Const.testNetKeyNetVersions : Bip32Const.mainNetKeyNetVersions, + ), + hrp: hrp, + version: version); + } + + SilentPaymentAddress toLabeledSilentPaymentAddress(int m) { + final B_m = B_spend.clone().tweakAdd(BigintUtils.fromBytes(generateLabel(b_scan, m))); + return SilentPaymentAddress(B_scan: B_scan, B_spend: B_m, hrp: hrp, version: version); + } +} + +class SilentPaymentDestination extends SilentPaymentAddress { + SilentPaymentDestination({ + required int version, + required ECPublic scanPubkey, + required ECPublic spendPubkey, + required String hrp, + required this.amount, + }) : super(version: version, B_scan: scanPubkey, B_spend: spendPubkey, hrp: hrp); + + int amount; + + factory SilentPaymentDestination.fromAddress(String address, int amount) { + final receiver = SilentPaymentAddress.fromAddress(address); + + return SilentPaymentDestination( + scanPubkey: receiver.B_scan, + spendPubkey: receiver.B_spend, + hrp: receiver.hrp, + version: receiver.version, + amount: amount, + ); + } +} + +class SilentPaymentAddress { + static RegExp get regex => RegExp(r'(^|\s)t?sp(rt)?1[0-9a-zA-Z]{113}($|\s)'); + + int version; + ECPublic B_scan; + ECPublic B_spend; + // human readable part (sprt, sp, tsp) + String hrp; + + SilentPaymentAddress( + {required this.B_scan, + required this.B_spend, + BasedUtxoNetwork network = BitcoinNetwork.mainnet, + String? hrp, + this.version = 0}) + : hrp = hrp ?? (network == BitcoinNetwork.testnet ? "tsp" : "sp") { + if (version != 0) { + throw Exception("Can't have other version than 0 for now"); + } + } + + factory SilentPaymentAddress.fromAddress(String address) { + // final decoded = bech32m.decode(address, 1023); + final decoded = Bech32DecoderBase.decodeBech32( + address, + SegwitBech32Const.separator, + SegwitBech32Const.checksumStrLen, + (hrp, data) => Bech32Utils.verifyChecksum(hrp, data, Bech32Encodings.bech32m), + ); + final prefix = decoded.item1; + final words = decoded.item2; + + if (prefix != 'sp' && prefix != 'sprt' && prefix != 'tsp') { + throw Exception('Invalid prefix: $prefix'); + } + + final version = words[0]; + if (version != 0) throw ArgumentError('Invalid version'); + + final key = Bech32BaseUtils.convertFromBase32(words.sublist(1)); + + return SilentPaymentAddress( + B_scan: ECPublic.fromBytes(key.sublist(0, 33)), + B_spend: ECPublic.fromBytes(key.sublist(33)), + hrp: prefix, + version: version, + ); + } + + @override + String toString() { + return Bech32EncoderBase.encodeBech32( + hrp, + [ + version, + ...Bech32BaseUtils.convertToBase32( + [...B_scan.toCompressedBytes(), ...B_spend.toCompressedBytes()]) + ], + SegwitBech32Const.separator, + (hrp, data) => Bech32Utils.computeChecksum(hrp, data, Bech32Encodings.bech32m), + ); + } +} + +class Bech32U5 { + final int value; + + Bech32U5(this.value) { + if (value < 0 || value > 31) { + throw Exception('Value is outside the valid range.'); + } + } + + static Bech32U5 tryFromInt(int value) { + if (value < 0 || value > 31) { + throw Exception('Value is outside the valid range.'); + } + return Bech32U5(value); + } +} diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart new file mode 100644 index 0000000..9eb0107 --- /dev/null +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -0,0 +1,213 @@ +// ignore_for_file: non_constant_identifier_names +part of 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; + +class SilentPaymentOutput { + final P2trAddress address; + final int amount; + + SilentPaymentOutput(this.address, this.amount); +} + +class ECPrivateInfo { + final ECPrivate privkey; + final bool isTaproot; + + ECPrivateInfo(this.privkey, this.isTaproot); +} + +class SilentPaymentBuilder { + final List outpoints; + final List pubkeys; + late ECPublic A_sum; + late List inputHash; + + SilentPaymentBuilder({ + required this.outpoints, + required this.pubkeys, + }) { + _getAsum(); + _getInputHash(); + } + + void _getAsum() { + final head = pubkeys.first; + final tail = pubkeys.sublist(1); + + A_sum = tail.fold(head, (acc, item) => ECPublic(acc.publicKey).pubkeyAdd(item)); + } + + void _getInputHash() { + final sortedOutpoints = >[]; + + for (final outpoint in outpoints) { + final vout = outpoint.index; + + sortedOutpoints.add(BytesUtils.concatBytes([ + BytesUtils.fromHexString(outpoint.txid).reversed.toList(), + BigintUtils.toBytes(BigInt.from(vout), length: 4, order: Endian.little) + ])); + } + + sortedOutpoints.sort(BytesUtils.compareBytes); + final lowestOutpoint = sortedOutpoints.first; + + inputHash = taggedHash( + BytesUtils.concatBytes([lowestOutpoint, A_sum.toCompressedBytes()]), "BIP0352/Inputs"); + } + + Map> createOutputs( + List inputPrivKeyInfos, + List silentPaymentDestinations, + ) { + ECPrivate? a_sum; + + for (final info in inputPrivKeyInfos) { + final key = info.privkey; + final isTaproot = info.isTaproot; + + var k = ECPrivate(key.prive); + + if (isTaproot && key.getPublic().publicKey.point.y % BigInt.two != BigInt.zero) { + k = k.negate(); + } + + if (a_sum == null) { + a_sum = k; + } else { + a_sum = a_sum.tweakAdd(BigintUtils.fromBytes(k.toBytes())); + } + } + + Map>> silentPaymentGroups = {}; + + for (final silentPaymentDestination in silentPaymentDestinations) { + final B_scan = silentPaymentDestination.B_scan; + final B_scan_hex = B_scan.toHex(); + + if (silentPaymentGroups.containsKey(B_scan_hex)) { + // Current key already in silentPaymentGroups, simply add up the new destination + // with the already calculated ecdhSharedSecret + final group = silentPaymentGroups[B_scan_hex]!; + final ecdhSharedSecret = group.keys.first; + final recipients = group.values.first; + + silentPaymentGroups[B_scan_hex] = { + ecdhSharedSecret: [...recipients, silentPaymentDestination] + }; + } else { + final senderPartialSecret = + a_sum!.clone().tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); + + final ecdhSharedSecret = + ECPublic(B_scan.publicKey).tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); + + silentPaymentGroups[B_scan_hex] = { + ecdhSharedSecret: [silentPaymentDestination] + }; + } + } + + // Result destinations with amounts + // { : [(, ), (, )...] } + Map> result = {}; + for (final group in silentPaymentGroups.entries) { + final ecdhSharedSecret = group.value.keys.first; + final destinations = group.value.values.first; + + int k = 0; + for (final destination in destinations) { + final t_k = taggedHash( + BytesUtils.concatBytes([ + ECPublic.fromHex(ecdhSharedSecret).toCompressedBytes(), + BigintUtils.toBytes(BigInt.from(k), length: 4) + ]), + "BIP0352/SharedSecret"); + + final P_mn = ECPublic(destination.B_spend.publicKey).tweakAdd(BigintUtils.fromBytes(t_k)); + + if (result.containsKey(destination.toString())) { + result[destination.toString()]! + .add(SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount)); + } else { + result[destination.toString()] = [ + SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount) + ]; + } + + k++; + } + } + + return result; + } + + Map> scanOutputs( + ECPrivate b_scan, ECPublic B_spend, List outputsToCheck, + {Map? precomputedLabels}) { + final tweakDataForRecipient = A_sum.clone().tweakMul(BigintUtils.fromBytes(inputHash)); + final ecdhSharedSecret = tweakDataForRecipient.tweakMul(b_scan.toBigInt()); + + final matches = >{}; + var k = 0; + + do { + final t_k = taggedHash( + BytesUtils.concatBytes([ + ecdhSharedSecret.toCompressedBytes(), + BigintUtils.toBytes(BigInt.from(k), length: 4, order: Endian.big) + ]), + "BIP0352/SharedSecret"); + + final P_k = B_spend.clone().tweakAdd(BigintUtils.fromBytes(t_k)); + final length = outputsToCheck.length; + + for (var i = 0; i < length; i++) { + final output = outputsToCheck[i]; + + if (BytesUtils.compareBytes( + output.toCompressedBytes().sublist(1), P_k.toCompressedBytes().sublist(1)) == + 0) { + matches[output.toHex()] = [BytesUtils.toHexString(t_k)]; + outputsToCheck.removeAt(i); + k++; + break; + } + + if (precomputedLabels != null && precomputedLabels.isNotEmpty) { + var m_G_sub = output.clone().pubkeyAdd(P_k.clone().negate()); + var m_G = precomputedLabels[m_G_sub.toHex()]; + + if (m_G == null) { + m_G_sub = output.clone().negate().pubkeyAdd(P_k.clone().negate()); + m_G = precomputedLabels[m_G_sub.toHex()]; + } + + if (m_G != null) { + final P_km = P_k.clone() + .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))) + .toCompressedBytes(); + + matches[BytesUtils.toHexString(P_km)] = [ + ECPrivate.fromBytes(t_k) + .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))) + .toHex(), + m_G + ]; + + outputsToCheck.removeAt(i); + k++; // Increment counter + break; + } + } + + outputsToCheck.removeAt(i); + + if (i + 1 >= outputsToCheck.length) { + break; + } + } + } while (outputsToCheck.isNotEmpty); + + return matches; + } +} diff --git a/lib/src/bitcoin/silent_payments/silent_payments.dart b/lib/src/bitcoin/silent_payments/silent_payments.dart new file mode 100644 index 0000000..ab8446b --- /dev/null +++ b/lib/src/bitcoin/silent_payments/silent_payments.dart @@ -0,0 +1,23 @@ +// Library for Bitcoin Silent Payments handling in the bitcoin_base package. +// +// The library includes essential components such as: +// - Core address functionality. +// - encode/decode address support. +// - Utility functions for address manipulation. +// - Generate labeled addresses. +// - Scan transactions. +// - Generate payment outputs. +library bitcoin_base.silent_payments; + +import 'dart:typed_data'; + +import 'package:bitcoin_base/src/bitcoin/address/address.dart'; +import 'package:bitcoin_base/src/bitcoin/script/outpoint.dart'; +import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; +import 'package:bitcoin_base/src/crypto/crypto.dart'; +import 'package:bitcoin_base/src/models/network.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; + +part 'address.dart'; +part 'payment.dart'; +part 'utils.dart'; diff --git a/lib/src/bitcoin/silent_payments/utils.dart b/lib/src/bitcoin/silent_payments/utils.dart new file mode 100644 index 0000000..df5b28a --- /dev/null +++ b/lib/src/bitcoin/silent_payments/utils.dart @@ -0,0 +1,116 @@ +// ignore_for_file: non_constant_identifier_names +part of 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; + +final NUMS_H = BigInt.parse("0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"); + +int deserCompactSize(ByteData f) { + final view = f.buffer; + int nbytes = view.lengthInBytes; + if (nbytes == 0) { + return 0; // end of stream + } + + int nit = f.getUint8(0); + if (nit == 253) { + nit = f.getUint16(1, Endian.little); + } else if (nit == 254) { + nit = f.getUint32(3, Endian.little); + } else if (nit == 255) { + nit = f.getUint64(7, Endian.little); + } + return nit; +} + +ByteData deserString(ByteData f) { + final nit = deserCompactSize(f); + int offset = 1; + return ByteData.sublistView(f.buffer.asUint8List().sublist(offset, nit + offset)); +} + +List deserStringVector(ByteData f) { + int offset = 0; + + final nit = deserCompactSize(f); + offset += 1; + + List result = []; + for (int i = 0; i < nit; i++) { + final t = deserString(ByteData.sublistView(f.buffer.asUint8List().sublist(offset))); + + result.add(t); + offset += t.lengthInBytes + 1; + } + return result; +} + +class VinInfo { + final Outpoint outpoint; + final List scriptSig; + final TxWitnessInput txinwitness; + final Script prevOutScript; + final ECPrivate? privkey; + + VinInfo({ + required this.outpoint, + required this.scriptSig, + required this.txinwitness, + required this.prevOutScript, + this.privkey, + }); +} + +ECPublic? getPubkeyFromInput(VinInfo vin) { + switch (vin.prevOutScript.getAddressType()) { + case P2pkhAddressType.p2pkh: + for (var i = vin.scriptSig.length; i > 0; i--) { + if (i - 33 >= 0) { + final pubkeyBytes = vin.scriptSig.sublist(i - 33, i); + final pubkeyHash = BytesUtils.toHexString(QuickCrypto.hash160(pubkeyBytes)); + if (pubkeyHash == + P2pkhAddress.fromScriptPubkey(script: vin.prevOutScript).addressProgram) { + return ECPublic.fromBytes(pubkeyBytes); + } + } + } + break; + case P2shAddressType.p2pkhInP2sh: + final redeemScript = vin.scriptSig.sublist(1); + if (Script.fromRaw(byteData: redeemScript).getAddressType() == SegwitAddresType.p2wpkh) { + return ECPublic.fromBytes(vin.txinwitness.scriptWitness.stack.last.buffer.asUint8List()); + } + break; + case SegwitAddresType.p2wpkh: + return ECPublic.fromBytes(vin.txinwitness.scriptWitness.stack.last.buffer.asUint8List()); + case SegwitAddresType.p2tr: + final witnessStack = vin.txinwitness.scriptWitness.stack; + if (witnessStack.isNotEmpty) { + if (witnessStack.length > 1 && witnessStack.last.buffer.asUint8List()[0] == 0x50) { + witnessStack.removeLast(); + } + + if (witnessStack.length > 1) { + final controlBlock = witnessStack.last.buffer.asUint8List(); + final internalKey = controlBlock.sublist(1, 33); + if (BytesUtils.compareBytes( + internalKey, BigintUtils.toBytes(NUMS_H, length: 32, order: Endian.big)) == + 0) { + return null; + } + } + return ECPublic.fromBytes(vin.prevOutScript.toBytes().sublist(2)); + } + break; + default: + return null; + } + + return null; +} + +List serUint32(int n) { + return BigintUtils.toBytes(BigInt.from(n), length: 4); +} + +List generateLabel(ECPrivate b_scan, int m) { + return taggedHash(BytesUtils.concatBytes([b_scan.toBytes(), serUint32(m)]), "BIP0352/Label"); +} diff --git a/lib/src/crypto/keypair/ec_private.dart b/lib/src/crypto/keypair/ec_private.dart index ee1b212..a2c1d47 100644 --- a/lib/src/crypto/keypair/ec_private.dart +++ b/lib/src/crypto/keypair/ec_private.dart @@ -13,28 +13,23 @@ class ECPrivate { /// creates an object from raw 32 bytes factory ECPrivate.fromBytes(List prive) { - final key = Bip32PrivateKey.fromBytes(prive, Bip32KeyData(), - Bip32Const.mainNetKeyNetVersions, EllipticCurveTypes.secp256k1); + final key = Bip32PrivateKey.fromBytes( + prive, Bip32KeyData(), Bip32Const.mainNetKeyNetVersions, EllipticCurveTypes.secp256k1); return ECPrivate(key); } /// returns the corresponding ECPublic object - ECPublic getPublic() => - ECPublic.fromHex(BytesUtils.toHexString(prive.publicKey.compressed)); + ECPublic getPublic() => ECPublic.fromHex(BytesUtils.toHexString(prive.publicKey.compressed)); /// creates an object from a WIF of WIFC format (string) factory ECPrivate.fromWif(String wif, {required List? netVersion}) { - final decode = WifDecoder.decode(wif, - netVer: netVersion ?? BitcoinNetwork.mainnet.wifNetVer); + final decode = WifDecoder.decode(wif, netVer: netVersion ?? BitcoinNetwork.mainnet.wifNetVer); return ECPrivate.fromBytes(decode.item1); } /// returns as WIFC (compressed) or WIF format (string) String toWif({bool compressed = true, BitcoinNetwork? network}) { - List bytes = [ - ...(network ?? BitcoinNetwork.mainnet).wifNetVer, - ...toBytes() - ]; + List bytes = [...(network ?? BitcoinNetwork.mainnet).wifNetVer, ...toBytes()]; if (compressed) { bytes = [...bytes, 0x01]; } @@ -46,21 +41,23 @@ class ECPrivate { return prive.raw; } + BigInt toBigInt() { + return BigintUtils.fromBytes(prive.raw); + } + String toHex() { return BytesUtils.toHexString(prive.raw); } /// Returns a Bitcoin compact signature in hex - String signMessage(List message, - {String messagePrefix = '\x18Bitcoin Signed Message:\n'}) { + String signMessage(List message, {String messagePrefix = '\x18Bitcoin Signed Message:\n'}) { final btcSigner = BitcoinSigner.fromKeyBytes(toBytes()); final signature = btcSigner.signMessage(message, messagePrefix); return BytesUtils.toHexString(signature); } /// sign transaction digest and returns the signature. - String signInput(List txDigest, - {int sigHash = BitcoinOpCodeConst.SIGHASH_ALL}) { + String signInput(List txDigest, {int sigHash = BitcoinOpCodeConst.SIGHASH_ALL}) { final btcSigner = BitcoinSigner.fromKeyBytes(toBytes()); List signature = btcSigner.signTransaction(txDigest); signature = [...signature, sigHash]; @@ -79,12 +76,11 @@ class ECPrivate { return true; }(), "When the tweak is false, the `tapScripts` are ignored, to use the tap script path, you need to consider the tweak value to be true."); - final tapScriptBytes = !tweak - ? [] - : tapScripts.map((e) => e.map((e) => e.toBytes()).toList()).toList(); + final tapScriptBytes = + !tweak ? [] : tapScripts.map((e) => e.map((e) => e.toBytes()).toList()).toList(); final btcSigner = BitcoinSigner.fromKeyBytes(toBytes()); - List signatur = btcSigner.signSchnorrTransaction(txDigest, - tapScripts: tapScriptBytes, tweak: tweak); + List signatur = + btcSigner.signSchnorrTransaction(txDigest, tapScripts: tapScriptBytes, tweak: tweak); if (sighash != BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL) { signatur = [...signatur, sighash]; } @@ -95,4 +91,30 @@ class ECPrivate { final secret = QuickCrypto.generateRandom(); return ECPrivate.fromBytes(secret); } + + ECPrivate tweakAdd(BigInt tweak) { + return ECPrivate.fromBytes(BigintUtils.toBytes( + (BigintUtils.fromBytes(prive.raw) + tweak) % Curves.generatorSecp256k1.order!, + length: getPublic().publicKey.point.curve.baselen, + )); + } + + ECPrivate tweakMul(BigInt tweak) { + return ECPrivate.fromBytes(BigintUtils.toBytes( + (BigintUtils.fromBytes(prive.raw) * tweak) % Curves.generatorSecp256k1.order!, + length: getPublic().publicKey.point.curve.baselen, + )); + } + + ECPrivate negate() { + // Negate the private key by subtracting from the order of the curve + return ECPrivate.fromBytes(BigintUtils.toBytes( + Curves.generatorSecp256k1.order! - BigintUtils.fromBytes(prive.raw), + length: getPublic().publicKey.point.curve.baselen, + )); + } + + ECPrivate clone() { + return ECPrivate.fromBytes(prive.raw); + } } diff --git a/lib/src/crypto/keypair/ec_public.dart b/lib/src/crypto/keypair/ec_public.dart index 9f8e45e..4d3c061 100644 --- a/lib/src/crypto/keypair/ec_public.dart +++ b/lib/src/crypto/keypair/ec_public.dart @@ -107,7 +107,7 @@ class ECPublic { /// custom spending conditions. P2trAddress toTaprootAddress({List>? scripts, bool tweak = true}) { final pubKey = toTapRotHex(script: scripts, tweak: tweak); - return P2trAddress.fromProgram(program: pubKey); + return P2trAddress.fromProgram(program: pubKey, pubkey: this); } /// toP2wpkhInP2sh generates a P2SH (Pay-to-Script-Hash) address @@ -191,4 +191,38 @@ class ECPublic { return verifyKey.verifySchnorr(message, signature, tapleafScripts: tapleafScripts, isTweak: isTweak); } + + ECPublic tweakAdd(BigInt tweak) { + final point = publicKey.point as ProjectiveECCPoint; + // Compute the new public key after adding the tweak + final tweakedKey = point + (Curves.generatorSecp256k1 * tweak); + + return ECPublic.fromBytes(tweakedKey.toBytes()); + } + + // Perform the tweak multiplication + ECPublic tweakMul(BigInt tweak) { + final point = publicKey.point as ProjectiveECCPoint; + // Perform the tweak multiplication + final tweakedKey = point * tweak; + + return ECPublic.fromBytes(tweakedKey.toBytes()); + } + + ECPublic pubkeyAdd(ECPublic other) { + final tweakedKey = (publicKey.point as ProjectiveECCPoint) + other.publicKey.point; + return ECPublic.fromBytes(tweakedKey.toBytes()); + } + + ECPublic negate() { + // Negate the Y-coordinate by subtracting it from the field size (p). + final point = (publicKey.point as ProjectiveECCPoint); + final y = point.curve.p - point.y; + return ECPublic.fromBytes(BytesUtils.fromHexString( + "04${BytesUtils.toHexString(BigintUtils.toBytes(point.x, length: point.curve.baselen))}${BytesUtils.toHexString(BigintUtils.toBytes(y, length: point.curve.baselen))}")); + } + + ECPublic clone() { + return ECPublic.fromBytes(publicKey.uncompressed); + } } diff --git a/pubspec.yaml b/pubspec.yaml index f075bc3..7aaa45e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,10 @@ environment: dependencies: - blockchain_utils: ^1.6.0 + blockchain_utils: + git: + url: https://github.com/rafael-xmr/blockchain_utils + ref: cake-update-v1 dev_dependencies: diff --git a/test/fixtures/silent_payments.json b/test/fixtures/silent_payments.json new file mode 100644 index 0000000..a54c584 --- /dev/null +++ b/test/fixtures/silent_payments.json @@ -0,0 +1,2766 @@ +[ + { + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "3e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1", + "priv_key_tweak": "f438b40179a3c4262de12986c0e6cce0634007cdc79c1dcd3e20b9ebc2e7eef6", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "79e71baa2ba3fc66396de3a04f168c7bf24d6870ec88ca877754790c1db357b6", + "priv_key_tweak": "4851455bfbe1ab4f80156570aa45063201aa5c9e1b1dcd29f0f8c33d10bf77ae", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38", + "priv_key_tweak": "ab0c9b87181bf527879f48db9f14a02233619b986f8e8f2d5d408ce68a709f51", + "signature": "398a9790865791a9db41a8015afad3a47d60fec5086c50557806a49a1bc038808632b8fe679a7bb65fc6b455be994502eed849f1da3729cd948fc7be73d67295" + } + ] + } + } + ] + }, + { + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "548ae55c8eec1e736e8d3e520f011f1f42a56d166116ad210b3937599f87f566", + "priv_key_tweak": "f032695e2636619efa523fffaa9ef93c8802299181fd0461913c1b8daf9784cd", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "de88bea8e7ffc9ce1af30d1132f910323c505185aec8eae361670421e749a1fb", + "priv_key_tweak": "3fb9ce5ce1746ced103c8ed254e81f6690764637ddbc876ec1f9b3ddab776b03", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "77cab7dd12b10259ee82c6ea4b509774e33e7078e7138f568092241bf26b99f1", + "priv_key_tweak": "f5382508609771068ed079b24e1f72e4a17ee6d1c979066bf1d4e2a5676f09d4", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "30523cca96b2a9ae3c98beb5e60f7d190ec5bc79b2d11a0b2d4d09a608c448f0", + "priv_key_tweak": "b40017865c79b1fcbed68896791be93186d08f47e416b289b8c063777e14e8df", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "359358f59ee9e9eec3f00bdf4882570fd5c182e451aa2650b788544aff012a3a", + "priv_key_tweak": "a2f9dd05d1d398347c885d9c61a64d18a264de6d49cea4326bafc2791d627fa7", + "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", + 2.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 3.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 2.0 + ], + [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + 3.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "priv_key_tweak": "d97e442d110c0bdd31161a7bb6e7862e038d02a09b1484dfbb463f2e0f7c9230", + "signature": "29bd25d0f808d7fcd2aa6d5ed206053899198397506c301b218a9e47a3d7070af03e903ff718978d50d1b6b9af8cc0e313d84eda5d5b1e8e85e5516d630bbeb9" + } + ] + } + } + ] + }, + { + "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", + 2.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 3.0 + ], + [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + 4.0 + ], + [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn", + 5.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 2.0 + ], + [ + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + 3.0 + ], + [ + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + 4.0 + ], + [ + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + 5.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "priv_key_tweak": "d97e442d110c0bdd31161a7bb6e7862e038d02a09b1484dfbb463f2e0f7c9230", + "signature": "29bd25d0f808d7fcd2aa6d5ed206053899198397506c301b218a9e47a3d7070af03e903ff718978d50d1b6b9af8cc0e313d84eda5d5b1e8e85e5516d630bbeb9" + } + ] + } + }, + { + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "e976a58fbd38aeb4e6093d4df02e9c1de0c4513ae0c588cef68cda5b2f8834ca", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a" + ], + "key_material": { + "spend_priv_key": "9902c3c56e84002a7cd410113a9ab21d142be7f53cf5200720bb01314c5eb920", + "scan_priv_key": "060b751d7892149006ed7b98606955a29fe284a1e900070c0971f5fb93dbf422" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgrz6j0lcqnc04vxccydl0kpsj4frfje0ktmgcl2t346hkw30226xqupawdf48k8882j0strrvcmgg2kdawz53a54dd376ngdhak364hzcmynqtn" + ], + "outputs": [ + { + "pub_key": "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8", + "priv_key_tweak": "2f17ea873a0047fc01ba8010fef0969e76d0e4283f600d48f735098b1fee6eb9", + "signature": "c26f4e3cf371b90b840f48ea0e761b5ec31883ed55719f9ef06a90e282d85f565790ab780a3f491bc2668cc64e944dca849d1022a878cdadb8d168b8da4a6da3" + }, + { + "pub_key": "2e847bb01d1b491da512ddd760b8509617ee38057003d6115d00ba562451323a", + "priv_key_tweak": "72cd082cccb633bf85240a83494b32dc943a4d05647a6686d23ad4ca59c0ebe4", + "signature": "38745f3d9f5eef0b1cfb17ca314efa8c521efab28a23aa20ec5e3abb561d42804d539906dce60c4ee7977966184e6f2cab1faa0e5377ceb7148ec5218b4e7878" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjex54dmqmmv6rw353tsuqhs99ydvadxzrsy9nuvk74epvee55drs734pqq", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "d014d4860f67d607d60b1af70e0ee236b99658b61bb769832acbbe87c374439a", + "priv_key_tweak": "51d4e9d0d482b5700109b4b2e16ff508269b03d800192a043d61dca4a0a72a52", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "67626aebb3c4307cf0f6c39ca23247598fabf675ab783292eb2f81ae75ad1f8c", + "priv_key_tweak": "6024ae214876356b8d917716e7707d267ae16a0fdb07de2a786b74a7bbcddead", + "signature": "a86d554d0d6b7aa0907155f7e0b47f0182752472fffaeddd68da90e99b9402f166fd9b33039c302c7115098d971c1399e67c19e9e4de180b10ea0b9d6f0db832" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq7c2zfthc6x3a5yecwc52nxa0kfd20xuz08zyrjpfw4l2j257yq6qgnkdh5", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "7efa60ce78ac343df8a013a2027c6c5ef29f9502edcbd769d2c21717fecc5951", + "priv_key_tweak": "e336b92330c33030285ce42e4115ad92d5197913c88e06b9072b4a9b47c664a2", + "signature": "c9e80dd3bdd25ca2d352ce77510f1aed37ba3509dc8cc0677f2d7c2dd04090707950ce9dd6c83d2a428063063aff5c04f1744e334f661f2fc01b4ef80b50f739" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + 2.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 1.0 + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + 2.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + } + ] + } + } + ] + }, + { + "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", + 3.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + 4.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + 3.0 + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + 4.0 + ] + ] + } + } + ], + "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": [ + "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "pub_key": "83dc944e61603137294829aed56c74c9b087d80f2c021b98a7fae5799000696c", + "priv_key_tweak": "9d5fd3b91cac9ddfea6fc2e6f9386f680e6cee623cda02f53706306c081de87f", + "signature": "db0dfacc98b6a6fcc67cc4631f080b1ca38c60d8c397f2f19843f8f95ec91594b24e47c5bd39480a861c1209f7e3145c440371f9191fb96e324690101eac8e8e" + }, + { + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + } + ] + } + } + ] + }, + { + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 5.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj", + 6.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + 7.0 + ], + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjyh2ju7hd5gj57jg5r9lev3pckk4n2shtzaq34467erzzdfajfggty6aa5", + 8.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 5.0 + ], + [ + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + 6.0 + ], + [ + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + 7.0 + ], + [ + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b", + 8.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "ae1a780c04237bd577283c3ddb2e499767c3214160d5a6b0767e6b8c278bd701", + "f4569fc5f69c10f0082cfbb8e072e6266ec55f69fba8cffca4cbb4c144b7e59b" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [ + 1 + ] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqaxww2fnhrx05cghth75n0qcj59e3e2anscr0q9wyknjxtxycg07y3pevyj" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + }, + { + "pub_key": "39f42624d5c32a77fda80ff0acee269afec601d3791803e80252ae04e4ffcf4c", + "priv_key_tweak": "43100f89f1a6bf10081c92b473ffc57ceac7dbed600b6aba9bb3976f17dbb914", + "signature": "15c92509b67a6c211ebb4a51b7528d0666e6720de2343b2e92cfb97942ca14693c1f1fdc8451acfdb2644039f8f5c76114807fdc3d3a002d8a46afab6756bd75" + } + ] + } + } + ] + }, + { + "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", + 1.0 + ], + [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr", + 2.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 1.0 + ], + [ + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + 2.0 + ] + ] + } + } + ], + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff" + ], + "key_material": { + "spend_priv_key": "b8f87388cbb41934c50daca018901b00070a5ff6cc25a7e9e716a9d5b9e4d664", + "scan_priv_key": "11b7a82e06ca2648d5fded2366478078ec4fc9dc1d8ff487518226f229d768fd" + }, + "labels": [ + 0 + ] + }, + "expected": { + "addresses": [ + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqauj52ymtc4xdkmx3tgyhrsemg2g3303xk2gtzfy8h8ejet8fz8jcw23zua", + "sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqlv6saelkk5snl4wfutyxrchpzzwm8rjp3z6q7apna59z9huq4x754e5atr" + ], + "outputs": [ + { + "pub_key": "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff", + "priv_key_tweak": "80cd767ed20bd0bb7d8ea5e803f8c381293a62e8a073cf46fb0081da46e64e1f", + "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": [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "be368e28979d950245d742891ae6064020ba548c1e2e65a639a8bb0675d95cff" + ], + "key_material": { + "spend_priv_key": "9d6ad855ce3417ef84e836892e5a56392bfba05fa5d97ccea30e266f540e08b3", + "scan_priv_key": "0f694e068028a717f8af6b9411f9a133dd3565258714cc226594b34db90c1f2c" + }, + "labels": [] + }, + "expected": { + "addresses": [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv" + ], + "outputs": [ + { + "pub_key": "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + "priv_key_tweak": "33ce085c3c11eaad13694aae3c20301a6c83382ec89a7cde96c6799e2f88805a", + "signature": "335667ca6cae7a26438f5cfdd73b3d48fa832fa9768521d7d5445f22c203ab0d74ed85088f27d29959ba627a4509996676f47df8ff284d292567b1beef0e3912" + } + ] + } + } + ] + }, + { + "comment": "Single receipient: 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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "79e79897c52935bfd97fc6e076a6431a0c7543ca8c31e0fc3cf719bb572c842d", + "priv_key_tweak": "3ddec3232609d348d6b8b53123b4f40f6d4f5398ca586f087b0416ec3b851496", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "4612cdbf845c66c7511d70aab4d9aed11e49e48cdb8d799d787101cdd0d53e4f", + "priv_key_tweak": "10bde9781def20d7701e7603ef1b1e5e71c67bae7154818814e3c81ef5b1a3d3", + "signature": "6137969f810e9e8ef6c9755010e808f5dd1aed705882e44d7f0ae64eb0c509ec8b62a0671bee0d5914ac27d2c463443e28e999d82dc3d3a4919f093872d947bb" + } + ] + } + } + ] + }, + { + "comment": "P2PKH and P2WPKH Uncompressed Keys are skipped", + "sending": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + }, + "private_key": "eadc78165ff1f8ea94ad7cfdc54990738a4c53f6e0507b42154201b8e5dff3b1" + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + 1.0 + ] + ] + } + } + ], + "receiving": [ + { + "given": { + "vin": [ + { + "txid": "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16", + "vout": 0, + "scriptSig": "483046022100ad79e6801dd9a8727f342f31c71c4912866f59dc6e7981878e92c5844a0ce929022100fb0d2393e813968648b9753b7e9871d90ab3d815ebf91820d704b19f4ed224d621025a1e61f898173040e20616d43e9f496fba90338a39faa1ed98fcbaeee4dd9be5", + "txinwitness": "", + "prevout": { + "scriptPubKey": { + "hex": "76a91419c2f3ae0ca3b642bd3e49598b8da89f50c1416188ac" + } + } + }, + { + "txid": "a1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d", + "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": [ + { + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + 1.0 + ] + ] + } + }, + { + "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", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + 1.0 + ] + ] + } + } + ], + "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": [ + { + "pub_key": "67fee277da9e8542b5d2e6f32d660a9bbd3f0e107c2d53638ab1d869088882d6", + "priv_key_tweak": "688fa3aeb97d2a46ae87b03591921c2eaf4b505eb0ddca2733c94701e01060cf", + "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": [ + [ + "sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv", + 1.0 + ] + ] + }, + "expected": { + "outputs": [ + [ + "f207162b1a7abc51c42017bef055e9ec1efc3d3567cb720357e2b84325db33ac", + 1.0 + ] + ] + } + } + ], + "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": [ + "782eeb913431ca6e9b8c2fd80a5f72ed2024ef72a3c6fb10263c379937323338", + "841792c33c9dc6193e76744134125d40add8f2f4a96475f28ba150be032d64e8" + ], + "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", + 1.0 + ] + ] + }, + "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": [] + } + } + ] + } +] diff --git a/test/silent_payments.dart b/test/silent_payments.dart new file mode 100644 index 0000000..45fcb06 --- /dev/null +++ b/test/silent_payments.dart @@ -0,0 +1,290 @@ +// ignore_for_file: non_constant_identifier_names +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:bitcoin_base/src/bitcoin/script/outpoint.dart'; +import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; +import 'package:bitcoin_base/src/bitcoin/address/address.dart'; +import 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; +import 'package:bitcoin_base/src/crypto/crypto.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:test/test.dart'; + +// G , needed for generating the labels "database" +final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32)); + +main() { + group('Silent Payment Addresses', () { + // const scanKey = '036a1035a192f8f5fd375556f36ea4abc387361d32c709831ec624a5b73d0b7b9d'; + // const spendKey = '028eaf19db65cece905cf2b3eab811148d6fe874089a4a68e5d8b0a1a0904f6bd0'; + // const silentAddress = + 'sprt1qqd4pqddpjtu0tlfh24t0xm4y40pcwdsaxtrsnqc7ccj2tdeapdae6q5w4uvakewwe6g9eu4na2upz9yddl58gzy6ff5wtk9s5xsfqnmt6q30zssg'; + + // test('can encode scan and spend key to silent payment address', () { + // expect( + // SilentPaymentAddress( + // scanPubkey: ECPublic.fromHex(scanKey), + // spendPubkey: ECPublic.fromHex(spendKey), + // hrp: 'sprt', + // version: 0, + // ).toString(), + // silentAddress); + // }); + // test('can decode scan and spend key from silent payment address', () { + // expect( + // SilentPaymentAddress.fromAddress(silentAddress).toString(), + // SilentPaymentAddress( + // scanPubkey: ECPublic.fromHex(scanKey), + // spendPubkey: ECPublic.fromHex(spendKey), + // hrp: 'sprt', + // version: 0) + // .toString()); + // }); + + // test('can derive scan and spend key from master key', () async { + // const mnemonic = + // 'praise you muffin lion enable neck grocery crumble super myself license ghost'; + // final address = SilentPaymentOwner.fromMnemonic(mnemonic); + + // final seed = Bip39MnemonicDecoder().decode(mnemonic); + // final root = Bip32Slip10Secp256k1.fromSeed(seed, Bip32Const.testNetKeyNetVersions); + + // expect(address.scanPrivkey.toHex(), root.derivePath(SCAN_PATH).privateKey.toHex()); + // expect(address.scanPubkey.toHex(), root.derivePath(SCAN_PATH).publicKey.toHex()); + + // expect(address.spendPrivkey.toHex(), root.derivePath(SPEND_PATH).privateKey.toHex()); + // expect(address.spendPubkey.toHex(), root.derivePath(SPEND_PATH).publicKey.toHex()); + // }); + + // test('can create a labeled silent payment address', () { + // final given = [ + // [ + // '0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4', + // '025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36', + // '0000000000000000000000000000000000000000000000000000000000000001', + // 'sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj', + // ], + // [ + // '0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4', + // '025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36', + // '0000000000000000000000000000000000000000000000000000000000000539', + // 'sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq562yg7htxyg8eq60rl37uul37jy62apnf5ru62uef0eajpdfrnp5cmqndj', + // ], + // [ + // '03b4cc0b090b6f49a684558852db60ee5eb1c5f74352839c3d18a8fc04ef7354e0', + // '03bc95144daf15336db3456825c70ced0a4462f89aca42c4921ee7ccb2b3a44796', + // '91cb04398a508c9d995ff4a18e5eae24d5e9488309f189120a3fdbb977978c46', + // 'sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqll5497pp2gcr4cmq0v5nv07x8u5jswmf8ap2q0kxmx8628mkqanyu63ck8', + // ], + // ]; + + // for (final data in given) { + // final scanKey = data[0]; + // final spendKey = data[1]; + // final label = data[2]; + // final address = data[3]; + // final result = SilentPaymentAddress( + // B_scan: ECPublic.fromHex(scanKey), B_spend: ECPublic.fromHex(spendKey)) + // .createLabeledSilentPaymentAddress(ECPublic.fromHex(scanKey), + // ECPublic.fromHex(spendKey), BigintUtils.fromBytes(BytesUtils.fromHexString(label))); + + // expect(result.toString(), address); + // } + // }); + }); + + final fixtures = + json.decode(File('test/fixtures/silent_payments.json').readAsStringSync(encoding: utf8)); + + for (var testCase in fixtures) { + test(testCase['comment'], () { + Map> sendingOutputs = {}; + + // Test sending + for (var sendingTest in testCase['sending']) { + List vinOutpoints = []; + List inputPrivKeyInfos = []; + List inputPubKeys = []; + + var given = sendingTest["given"]; + + for (var input in given['vin']) { + final prevoutScript = Script.fromRaw(hexData: input['prevout']['scriptPubKey']['hex']); + final privkey = ECPrivate.fromHex(input['private_key']); + + final vin = VinInfo( + outpoint: Outpoint(txid: input['txid'], index: input['vout']), + scriptSig: BytesUtils.fromHexString(input['scriptSig']), + txinwitness: TxWitnessInput( + stack: [], + scriptWitness: ScriptWitness( + stack: deserStringVector( + ByteData.sublistView( + Uint8List.fromList( + BytesUtils.fromHexString(input['txinwitness']), + ), + ), + ))), + prevOutScript: prevoutScript, + privkey: privkey, + ); + + final pubkey = getPubkeyFromInput(vin); + + if (pubkey == null) { + return; + } + + vinOutpoints.add(vin.outpoint); + inputPrivKeyInfos + .add(ECPrivateInfo(privkey, prevoutScript.getAddressType() == SegwitAddresType.p2tr)); + inputPubKeys.add(pubkey); + } + + if (inputPubKeys.isNotEmpty) { + List silentPaymentDestinations = + (given['recipients'] as List) + .map((recipient) => + SilentPaymentDestination.fromAddress(recipient[0], recipient[1].floor())) + .toList(); + + final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); + sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); + + final expectedDestinations = sendingTest['expected']['outputs']; + var i = 0; + + sendingOutputs.forEach((silentAddress, generatedOutputs) { + final expectedSilentAddress = silentPaymentDestinations[i].toString(); + // Check that the silent payment addresses match for the expected and generated outputs + expect(silentAddress, expectedSilentAddress); + + for (final output in generatedOutputs) { + final expectedPubkey = expectedDestinations[i][0]; + final generatedPubkey = output.address.pubkey!; + + // Check that for a given set of inputs, we were able to generate the expected outputs for the receiver + expect(BytesUtils.toHexString(generatedPubkey.toCompressedBytes().sublist(1)), + expectedPubkey); + expect(output.amount, expectedDestinations[i][1].floor()); + + i++; + } + }); + } + } + + final msg = SHA256().update(utf8.encode('message')).digest(); + final aux = SHA256().update(utf8.encode('random auxiliary data')).digest(); + + // Test receiving + for (final receivingTest in testCase['receiving']) { + List vinOutpoints = []; + List inputPubKeys = []; + + final given = receivingTest["given"]; + final expected = receivingTest['expected']; + + List outputsToCheck = (given['outputs'] as List) + .map((output) => ECPublic.fromBytes(BytesUtils.fromHexString(output))) + .toList(); + + final receivingAddresses = []; + + final silentPaymentOwner = SilentPaymentOwner.fromPrivateKeys( + b_scan: ECPrivate.fromHex(given["key_material"]["scan_priv_key"]), + b_spend: ECPrivate.fromHex(given["key_material"]["spend_priv_key"])); + + // Add change address + receivingAddresses.add(silentPaymentOwner); + + Map? preComputedLabels; + for (var label in given['labels']) { + receivingAddresses.add(silentPaymentOwner.toLabeledSilentPaymentAddress(label)); + final generatedLabel = generateLabel(silentPaymentOwner.b_scan, label); + + preComputedLabels ??= {}; + preComputedLabels[G.tweakMul(BigintUtils.fromBytes(generatedLabel)).toHex()] = + BytesUtils.toHexString(generatedLabel); + } + + for (var address in receivingAddresses) { + expect(address.toString(), expected['addresses'][receivingAddresses.indexOf(address)]); + } + + for (var input in given['vin']) { + final prevoutScript = Script.fromRaw(hexData: input['prevout']['scriptPubKey']['hex']); + + final vin = VinInfo( + outpoint: Outpoint(txid: input['txid'], index: input['vout']), + scriptSig: BytesUtils.fromHexString(input['scriptSig']), + txinwitness: TxWitnessInput( + stack: [], + scriptWitness: ScriptWitness( + stack: deserStringVector( + ByteData.sublistView( + Uint8List.fromList( + BytesUtils.fromHexString(input['txinwitness']), + ), + ), + ))), + prevOutScript: prevoutScript, + ); + + final pubkey = getPubkeyFromInput(vin); + + if (pubkey == null) { + return; + } + + vinOutpoints.add(vin.outpoint); + inputPubKeys.add(pubkey); + } + + if (inputPubKeys.isNotEmpty) { + final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); + + final addToWallet = spb.scanOutputs( + silentPaymentOwner.b_scan, silentPaymentOwner.B_spend, outputsToCheck, + precomputedLabels: preComputedLabels); + + final expectedDestinations = expected['outputs']; + + // Check that the private key is correct for the found output public key + for (int i = 0; i < expectedDestinations.length; i++) { + final output = addToWallet.entries.elementAt(i); + final pubkey = output.key; + final expectedPubkey = expectedDestinations[i]["pub_key"]; + expect(BytesUtils.toHexString(BytesUtils.fromHexString(pubkey).sublist(1)), + expectedPubkey); + + final privKeyTweak = output.value[0]; + final expectedPrivKeyTweak = expectedDestinations[i]["priv_key_tweak"]; + expect(privKeyTweak, expectedPrivKeyTweak); + + final fullPrivateKey = + silentPaymentOwner.b_spend.clone().tweakAdd(BigintUtils.parse(privKeyTweak)); + + if (fullPrivateKey.toBytes()[0] == 0x03) { + fullPrivateKey.negate(); + } + + // Sign the message with schnorr + final btcSigner = BitcoinSigner.fromKeyBytes(fullPrivateKey.toBytes()); + List sig = + btcSigner.signSchnorrTransaction(msg, tapScripts: [], tweak: false, aux: aux); + + // Verify the message is correct + expect(btcSigner.verifyKey.verifySchnorr(msg, sig, isTweak: false), true); + + // Verify the signature is correct + expect(BytesUtils.toHexString(sig), expectedDestinations[i]["signature"]); + + i++; + } + } + } + }); + } +} From 076c2a113d03db6be1da567c689a302b01c06a5b Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 26 Feb 2024 21:40:32 -0300 Subject: [PATCH 05/53] feat: move generateLabel to SP, SP type implements base address type --- lib/src/bitcoin/script/scripts.dart | 1 + lib/src/bitcoin/silent_payments/address.dart | 31 ++++++++++++++++++-- lib/src/bitcoin/silent_payments/utils.dart | 4 --- test/silent_payments.dart | 2 +- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/src/bitcoin/script/scripts.dart b/lib/src/bitcoin/script/scripts.dart index edeea0a..e74ba6c 100644 --- a/lib/src/bitcoin/script/scripts.dart +++ b/lib/src/bitcoin/script/scripts.dart @@ -6,3 +6,4 @@ export 'sequence.dart'; export 'transaction.dart'; export 'witness.dart'; export 'op_code/constant_lib.dart'; +export 'outpoint.dart'; diff --git a/lib/src/bitcoin/silent_payments/address.dart b/lib/src/bitcoin/silent_payments/address.dart index 84428dc..eac0f4b 100644 --- a/lib/src/bitcoin/silent_payments/address.dart +++ b/lib/src/bitcoin/silent_payments/address.dart @@ -55,8 +55,12 @@ class SilentPaymentOwner extends SilentPaymentAddress { version: version); } + List generateLabel(int m) { + return taggedHash(BytesUtils.concatBytes([b_scan.toBytes(), serUint32(m)]), "BIP0352/Label"); + } + SilentPaymentAddress toLabeledSilentPaymentAddress(int m) { - final B_m = B_spend.clone().tweakAdd(BigintUtils.fromBytes(generateLabel(b_scan, m))); + final B_m = B_spend.clone().tweakAdd(BigintUtils.fromBytes(generateLabel(m))); return SilentPaymentAddress(B_scan: B_scan, B_spend: B_m, hrp: hrp, version: version); } } @@ -85,7 +89,7 @@ class SilentPaymentDestination extends SilentPaymentAddress { } } -class SilentPaymentAddress { +class SilentPaymentAddress implements BitcoinBaseAddress { static RegExp get regex => RegExp(r'(^|\s)t?sp(rt)?1[0-9a-zA-Z]{113}($|\s)'); int version; @@ -135,7 +139,12 @@ class SilentPaymentAddress { } @override - String toString() { + String toAddress(BasedUtxoNetwork network) { + return toString(network: network); + } + + @override + String toString({BasedUtxoNetwork? network}) { return Bech32EncoderBase.encodeBech32( hrp, [ @@ -147,6 +156,22 @@ class SilentPaymentAddress { (hrp, data) => Bech32Utils.computeChecksum(hrp, data, Bech32Encodings.bech32m), ); } + + @override + BitcoinAddressType get type => SilentPaymentsAddresType.p2sp; + + @override + Script toScriptPubKey() { + throw UnimplementedError(); + } + + @override + String pubKeyHash() { + throw UnimplementedError(); + } + + @override + String get addressProgram => ""; } class Bech32U5 { diff --git a/lib/src/bitcoin/silent_payments/utils.dart b/lib/src/bitcoin/silent_payments/utils.dart index df5b28a..1f68c07 100644 --- a/lib/src/bitcoin/silent_payments/utils.dart +++ b/lib/src/bitcoin/silent_payments/utils.dart @@ -110,7 +110,3 @@ ECPublic? getPubkeyFromInput(VinInfo vin) { List serUint32(int n) { return BigintUtils.toBytes(BigInt.from(n), length: 4); } - -List generateLabel(ECPrivate b_scan, int m) { - return taggedHash(BytesUtils.concatBytes([b_scan.toBytes(), serUint32(m)]), "BIP0352/Label"); -} diff --git a/test/silent_payments.dart b/test/silent_payments.dart index 45fcb06..102b1e3 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -202,7 +202,7 @@ main() { Map? preComputedLabels; for (var label in given['labels']) { receivingAddresses.add(silentPaymentOwner.toLabeledSilentPaymentAddress(label)); - final generatedLabel = generateLabel(silentPaymentOwner.b_scan, label); + final generatedLabel = silentPaymentOwner.generateLabel(label); preComputedLabels ??= {}; preComputedLabels[G.tweakMul(BigintUtils.fromBytes(generatedLabel)).toHex()] = From 43b80bcf0ef6e7224603a6b8874b61efec3c6a4c Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 26 Feb 2024 21:40:39 -0300 Subject: [PATCH 06/53] chore: change repo owner --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 7aaa45e..5265996 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ environment: dependencies: blockchain_utils: git: - url: https://github.com/rafael-xmr/blockchain_utils + url: https://github.com/cake-tech/blockchain_utils ref: cake-update-v1 From be980da9ed063da13db3907ca76d534298bb1d40 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 11:28:10 -0300 Subject: [PATCH 07/53] feat: adjust tests, add receiverTweak for scanning --- lib/src/bitcoin/silent_payments/payment.dart | 28 ++++++++++------- test/silent_payments.dart | 33 +++++++++----------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index 9eb0107..6532be1 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -16,22 +16,26 @@ class ECPrivateInfo { } class SilentPaymentBuilder { - final List outpoints; - final List pubkeys; + final List? outpoints; + final List? pubkeys; late ECPublic A_sum; late List inputHash; + String? receiverTweak; SilentPaymentBuilder({ - required this.outpoints, - required this.pubkeys, + this.outpoints, + this.pubkeys, + this.receiverTweak, }) { - _getAsum(); - _getInputHash(); + if (receiverTweak == null) { + _getAsum(); + _getInputHash(); + } } void _getAsum() { - final head = pubkeys.first; - final tail = pubkeys.sublist(1); + final head = pubkeys!.first; + final tail = pubkeys!.sublist(1); A_sum = tail.fold(head, (acc, item) => ECPublic(acc.publicKey).pubkeyAdd(item)); } @@ -39,7 +43,7 @@ class SilentPaymentBuilder { void _getInputHash() { final sortedOutpoints = >[]; - for (final outpoint in outpoints) { + for (final outpoint in outpoints!) { final vout = outpoint.index; sortedOutpoints.add(BytesUtils.concatBytes([ @@ -99,7 +103,7 @@ class SilentPaymentBuilder { a_sum!.clone().tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); final ecdhSharedSecret = - ECPublic(B_scan.publicKey).tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); + B_scan.clone().tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); silentPaymentGroups[B_scan_hex] = { ecdhSharedSecret: [silentPaymentDestination] @@ -144,7 +148,9 @@ class SilentPaymentBuilder { Map> scanOutputs( ECPrivate b_scan, ECPublic B_spend, List outputsToCheck, {Map? precomputedLabels}) { - final tweakDataForRecipient = A_sum.clone().tweakMul(BigintUtils.fromBytes(inputHash)); + final tweakDataForRecipient = receiverTweak != null + ? ECPublic.fromHex(receiverTweak!) + : A_sum.clone().tweakMul(BigintUtils.fromBytes(inputHash)); final ecdhSharedSecret = tweakDataForRecipient.tweakMul(b_scan.toBigInt()); final matches = >{}; diff --git a/test/silent_payments.dart b/test/silent_payments.dart index 102b1e3..2c28cd4 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -130,13 +130,14 @@ main() { privkey: privkey, ); + vinOutpoints.add(vin.outpoint); + final pubkey = getPubkeyFromInput(vin); if (pubkey == null) { - return; + continue; } - vinOutpoints.add(vin.outpoint); inputPrivKeyInfos .add(ECPrivateInfo(privkey, prevoutScript.getAddressType() == SegwitAddresType.p2tr)); inputPubKeys.add(pubkey); @@ -152,24 +153,19 @@ main() { final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); - final expectedDestinations = sendingTest['expected']['outputs']; - var i = 0; + List expectedDestinations = sendingTest['expected']['outputs']; - sendingOutputs.forEach((silentAddress, generatedOutputs) { - final expectedSilentAddress = silentPaymentDestinations[i].toString(); - // Check that the silent payment addresses match for the expected and generated outputs - expect(silentAddress, expectedSilentAddress); + silentPaymentDestinations.forEach((destination) { + final generatedOutputs = sendingOutputs[destination.toString()]; + expect(generatedOutputs != null, true); - for (final output in generatedOutputs) { - final expectedPubkey = expectedDestinations[i][0]; + for (final output in generatedOutputs!) { final generatedPubkey = output.address.pubkey!; + final expectedPubkey = expectedDestinations.firstWhere((expected) => + BytesUtils.toHexString(generatedPubkey.toCompressedBytes().sublist(1)) == + expected[0]); - // Check that for a given set of inputs, we were able to generate the expected outputs for the receiver - expect(BytesUtils.toHexString(generatedPubkey.toCompressedBytes().sublist(1)), - expectedPubkey); - expect(output.amount, expectedDestinations[i][1].floor()); - - i++; + expect(expectedPubkey != null, true); } }); } @@ -186,9 +182,8 @@ main() { final given = receivingTest["given"]; final expected = receivingTest['expected']; - List outputsToCheck = (given['outputs'] as List) - .map((output) => ECPublic.fromBytes(BytesUtils.fromHexString(output))) - .toList(); + List outputsToCheck = + (given['outputs'] as List).map((output) => ECPublic.fromHex(output)).toList(); final receivingAddresses = []; From 7634511b15e5a48bd18e3c19f81971628090c04f Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 27 Feb 2024 19:39:58 -0300 Subject: [PATCH 08/53] feat: change outputsToCheck --- lib/src/bitcoin/silent_payments/payment.dart | 25 ++++++++++---------- test/silent_payments.dart | 4 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index 6532be1..8312a62 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -131,10 +131,10 @@ class SilentPaymentBuilder { if (result.containsKey(destination.toString())) { result[destination.toString()]! - .add(SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount)); + .add(SilentPaymentOutput(P_mn.toTaprootAddress(), destination.amount)); } else { result[destination.toString()] = [ - SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount) + SilentPaymentOutput(P_mn.toTaprootAddress(), destination.amount) ]; } @@ -146,7 +146,7 @@ class SilentPaymentBuilder { } Map> scanOutputs( - ECPrivate b_scan, ECPublic B_spend, List outputsToCheck, + ECPrivate b_scan, ECPublic B_spend, List outputsToCheck, {Map? precomputedLabels}) { final tweakDataForRecipient = receiverTweak != null ? ECPublic.fromHex(receiverTweak!) @@ -170,30 +170,29 @@ class SilentPaymentBuilder { for (var i = 0; i < length; i++) { final output = outputsToCheck[i]; - if (BytesUtils.compareBytes( - output.toCompressedBytes().sublist(1), P_k.toCompressedBytes().sublist(1)) == - 0) { - matches[output.toHex()] = [BytesUtils.toHexString(t_k)]; + if (output == P_k.toTaprootAddress().toScriptPubKey().toHex() || + (BytesUtils.compareBytes(ECPublic.fromHex(output).toCompressedBytes().sublist(1), + P_k.toCompressedBytes().sublist(1)) == + 0)) { + matches[P_k.toHex()] = [BytesUtils.toHexString(t_k)]; outputsToCheck.removeAt(i); k++; break; } if (precomputedLabels != null && precomputedLabels.isNotEmpty) { - var m_G_sub = output.clone().pubkeyAdd(P_k.clone().negate()); + var m_G_sub = ECPublic.fromHex(output).pubkeyAdd(P_k.clone().negate()); var m_G = precomputedLabels[m_G_sub.toHex()]; if (m_G == null) { - m_G_sub = output.clone().negate().pubkeyAdd(P_k.clone().negate()); + m_G_sub = ECPublic.fromHex(output).negate().pubkeyAdd(P_k.clone().negate()); m_G = precomputedLabels[m_G_sub.toHex()]; } if (m_G != null) { - final P_km = P_k.clone() - .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))) - .toCompressedBytes(); + final P_km = P_k.clone().tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))); - matches[BytesUtils.toHexString(P_km)] = [ + matches[P_km.toHex()] = [ ECPrivate.fromBytes(t_k) .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))) .toHex(), diff --git a/test/silent_payments.dart b/test/silent_payments.dart index 2c28cd4..337c77e 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -182,8 +182,8 @@ main() { final given = receivingTest["given"]; final expected = receivingTest['expected']; - List outputsToCheck = - (given['outputs'] as List).map((output) => ECPublic.fromHex(output)).toList(); + List outputsToCheck = + (given['outputs'] as List).map((output) => output.toString()).toList(); final receivingAddresses = []; From f45e34d18ddff52764101a2ec96dcbc2be730555 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Fri, 1 Mar 2024 20:45:25 -0300 Subject: [PATCH 09/53] fix: refactor, test fixes --- lib/src/bitcoin/silent_payments/address.dart | 5 +- lib/src/bitcoin/silent_payments/payment.dart | 4 +- lib/src/crypto/keypair/ec_public.dart | 5 + test/silent_payments.dart | 117 ++++--------------- 4 files changed, 31 insertions(+), 100 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/address.dart b/lib/src/bitcoin/silent_payments/address.dart index eac0f4b..9995430 100644 --- a/lib/src/bitcoin/silent_payments/address.dart +++ b/lib/src/bitcoin/silent_payments/address.dart @@ -59,9 +59,10 @@ class SilentPaymentOwner extends SilentPaymentAddress { return taggedHash(BytesUtils.concatBytes([b_scan.toBytes(), serUint32(m)]), "BIP0352/Label"); } - SilentPaymentAddress toLabeledSilentPaymentAddress(int m) { + SilentPaymentOwner toLabeledSilentPaymentAddress(int m) { final B_m = B_spend.clone().tweakAdd(BigintUtils.fromBytes(generateLabel(m))); - return SilentPaymentAddress(B_scan: B_scan, B_spend: B_m, hrp: hrp, version: version); + return SilentPaymentOwner( + b_scan: b_scan, b_spend: b_spend, B_scan: B_scan, B_spend: B_m, hrp: hrp, version: version); } } diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index 8312a62..b48cb6a 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -170,7 +170,9 @@ class SilentPaymentBuilder { for (var i = 0; i < length; i++) { final output = outputsToCheck[i]; - if (output == P_k.toTaprootAddress().toScriptPubKey().toHex() || + if (output == + BytesUtils.toHexString( + P_k.toTaprootAddress().toScriptPubKey().toBytes().sublist(2)) || (BytesUtils.compareBytes(ECPublic.fromHex(output).toCompressedBytes().sublist(1), P_k.toCompressedBytes().sublist(1)) == 0)) { diff --git a/lib/src/crypto/keypair/ec_public.dart b/lib/src/crypto/keypair/ec_public.dart index 4d3c061..f4058b3 100644 --- a/lib/src/crypto/keypair/ec_public.dart +++ b/lib/src/crypto/keypair/ec_public.dart @@ -1,6 +1,7 @@ import 'package:bitcoin_base/src/bitcoin/address/address.dart'; import 'package:bitcoin_base/src/bitcoin/script/script.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:blockchain_utils/crypto/crypto/cdsa/point/base.dart'; class ECPublic { final Bip32PublicKey publicKey; @@ -153,6 +154,10 @@ class ECPublic { return publicKey.compressed; } + EncodeType? getEncodeType() { + return publicKey.point.encodeType; + } + /// returns the x coordinate only as hex string after tweaking (needed for taproot) String toTapRotHex({List>? script, bool tweak = true}) { var x = publicKey.point.x; diff --git a/test/silent_payments.dart b/test/silent_payments.dart index 337c77e..ceb7112 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -3,97 +3,18 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:bitcoin_base/src/bitcoin/script/outpoint.dart'; import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; import 'package:bitcoin_base/src/bitcoin/address/address.dart'; import 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; import 'package:bitcoin_base/src/crypto/crypto.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:blockchain_utils/crypto/crypto/cdsa/point/base.dart'; import 'package:test/test.dart'; // G , needed for generating the labels "database" final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32)); main() { - group('Silent Payment Addresses', () { - // const scanKey = '036a1035a192f8f5fd375556f36ea4abc387361d32c709831ec624a5b73d0b7b9d'; - // const spendKey = '028eaf19db65cece905cf2b3eab811148d6fe874089a4a68e5d8b0a1a0904f6bd0'; - // const silentAddress = - 'sprt1qqd4pqddpjtu0tlfh24t0xm4y40pcwdsaxtrsnqc7ccj2tdeapdae6q5w4uvakewwe6g9eu4na2upz9yddl58gzy6ff5wtk9s5xsfqnmt6q30zssg'; - - // test('can encode scan and spend key to silent payment address', () { - // expect( - // SilentPaymentAddress( - // scanPubkey: ECPublic.fromHex(scanKey), - // spendPubkey: ECPublic.fromHex(spendKey), - // hrp: 'sprt', - // version: 0, - // ).toString(), - // silentAddress); - // }); - // test('can decode scan and spend key from silent payment address', () { - // expect( - // SilentPaymentAddress.fromAddress(silentAddress).toString(), - // SilentPaymentAddress( - // scanPubkey: ECPublic.fromHex(scanKey), - // spendPubkey: ECPublic.fromHex(spendKey), - // hrp: 'sprt', - // version: 0) - // .toString()); - // }); - - // test('can derive scan and spend key from master key', () async { - // const mnemonic = - // 'praise you muffin lion enable neck grocery crumble super myself license ghost'; - // final address = SilentPaymentOwner.fromMnemonic(mnemonic); - - // final seed = Bip39MnemonicDecoder().decode(mnemonic); - // final root = Bip32Slip10Secp256k1.fromSeed(seed, Bip32Const.testNetKeyNetVersions); - - // expect(address.scanPrivkey.toHex(), root.derivePath(SCAN_PATH).privateKey.toHex()); - // expect(address.scanPubkey.toHex(), root.derivePath(SCAN_PATH).publicKey.toHex()); - - // expect(address.spendPrivkey.toHex(), root.derivePath(SPEND_PATH).privateKey.toHex()); - // expect(address.spendPubkey.toHex(), root.derivePath(SPEND_PATH).publicKey.toHex()); - // }); - - // test('can create a labeled silent payment address', () { - // final given = [ - // [ - // '0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4', - // '025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36', - // '0000000000000000000000000000000000000000000000000000000000000001', - // 'sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqah4hxfsjdwyaeel4g8x2npkj7qlvf2692l5760z5ut0ggnlrhdzsy3cvsj', - // ], - // [ - // '0220bcfac5b99e04ad1a06ddfb016ee13582609d60b6291e98d01a9bc9a16c96d4', - // '025cc9856d6f8375350e123978daac200c260cb5b5ae83106cab90484dcd8fcf36', - // '0000000000000000000000000000000000000000000000000000000000000539', - // 'sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgq562yg7htxyg8eq60rl37uul37jy62apnf5ru62uef0eajpdfrnp5cmqndj', - // ], - // [ - // '03b4cc0b090b6f49a684558852db60ee5eb1c5f74352839c3d18a8fc04ef7354e0', - // '03bc95144daf15336db3456825c70ced0a4462f89aca42c4921ee7ccb2b3a44796', - // '91cb04398a508c9d995ff4a18e5eae24d5e9488309f189120a3fdbb977978c46', - // 'sp1qqw6vczcfpdh5nf5y2ky99kmqae0tr30hgdfg88parz50cp80wd2wqqll5497pp2gcr4cmq0v5nv07x8u5jswmf8ap2q0kxmx8628mkqanyu63ck8', - // ], - // ]; - - // for (final data in given) { - // final scanKey = data[0]; - // final spendKey = data[1]; - // final label = data[2]; - // final address = data[3]; - // final result = SilentPaymentAddress( - // B_scan: ECPublic.fromHex(scanKey), B_spend: ECPublic.fromHex(spendKey)) - // .createLabeledSilentPaymentAddress(ECPublic.fromHex(scanKey), - // ECPublic.fromHex(spendKey), BigintUtils.fromBytes(BytesUtils.fromHexString(label))); - - // expect(result.toString(), address); - // } - // }); - }); - final fixtures = json.decode(File('test/fixtures/silent_payments.json').readAsStringSync(encoding: utf8)); @@ -134,7 +55,7 @@ main() { final pubkey = getPubkeyFromInput(vin); - if (pubkey == null) { + if (pubkey == null || pubkey.getEncodeType() != EncodeType.compressed) { continue; } @@ -156,18 +77,18 @@ main() { List expectedDestinations = sendingTest['expected']['outputs']; silentPaymentDestinations.forEach((destination) { - final generatedOutputs = sendingOutputs[destination.toString()]; - expect(generatedOutputs != null, true); + expect(sendingOutputs[destination.toString()] != null, true); + }); - for (final output in generatedOutputs!) { - final generatedPubkey = output.address.pubkey!; - final expectedPubkey = expectedDestinations.firstWhere((expected) => - BytesUtils.toHexString(generatedPubkey.toCompressedBytes().sublist(1)) == - expected[0]); + final generatedOutputs = sendingOutputs.values.expand((element) => element).toList(); + for (final expected in expectedDestinations) { + final expectedPubkey = expected[0]; + final generatedPubkey = generatedOutputs.firstWhere((output) => + BytesUtils.toHexString(output.address.pubkey!.toCompressedBytes().sublist(1)) == + expectedPubkey); - expect(expectedPubkey != null, true); - } - }); + expect(generatedPubkey != null, true); + } } } @@ -185,7 +106,7 @@ main() { List outputsToCheck = (given['outputs'] as List).map((output) => output.toString()).toList(); - final receivingAddresses = []; + final List receivingAddresses = []; final silentPaymentOwner = SilentPaymentOwner.fromPrivateKeys( b_scan: ECPrivate.fromHex(given["key_material"]["scan_priv_key"]), @@ -204,8 +125,9 @@ main() { BytesUtils.toHexString(generatedLabel); } - for (var address in receivingAddresses) { - expect(address.toString(), expected['addresses'][receivingAddresses.indexOf(address)]); + for (var address in expected['addresses']) { + expect(receivingAddresses.indexWhere((sp) => sp.toString() == address.toString()), + isNot(-1)); } for (var input in given['vin']) { @@ -227,13 +149,14 @@ main() { prevOutScript: prevoutScript, ); + vinOutpoints.add(vin.outpoint); + final pubkey = getPubkeyFromInput(vin); - if (pubkey == null) { - return; + if (pubkey == null || pubkey.getEncodeType() != EncodeType.compressed) { + continue; } - vinOutpoints.add(vin.outpoint); inputPubKeys.add(pubkey); } From 336694b3d66dc8a2e5b00f1b64f7ac7b2255892d Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Tue, 5 Mar 2024 11:49:18 -0300 Subject: [PATCH 10/53] fix: undetected labels --- lib/src/bitcoin/silent_payments/payment.dart | 33 ++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index b48cb6a..3414742 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -101,7 +101,6 @@ class SilentPaymentBuilder { } else { final senderPartialSecret = a_sum!.clone().tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); - final ecdhSharedSecret = B_scan.clone().tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); @@ -202,9 +201,39 @@ class SilentPaymentBuilder { ]; outputsToCheck.removeAt(i); - k++; // Increment counter + k++; break; } + + if (m_G == null) { + final labels = precomputedLabels.keys; + bool found = false; + for (final label in labels) { + final tweak = precomputedLabels[label]; + final P_km = + P_k.clone().tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(tweak!))); + + if (BytesUtils.toHexString( + P_km.toTaprootAddress().toScriptPubKey().toBytes().sublist(2)) == + output) { + matches[P_km.toHex()] = [ + ECPrivate.fromBytes(t_k) + .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(tweak))) + .toHex(), + label + ]; + + outputsToCheck.removeAt(i); + k++; + found = true; + break; + } + } + + if (found) { + break; + } + } } outputsToCheck.removeAt(i); From 0bacc8d06685208745525ab4ea34465b5935ad79 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Sun, 10 Mar 2024 16:49:16 -0300 Subject: [PATCH 11/53] feat: improvements for readabiliy and performance --- lib/src/bitcoin/silent_payments/address.dart | 2 +- lib/src/bitcoin/silent_payments/payment.dart | 114 ++++++++---------- .../silent_payments/silent_payments.dart | 2 +- test/silent_payments.dart | 18 +-- 4 files changed, 61 insertions(+), 75 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/address.dart b/lib/src/bitcoin/silent_payments/address.dart index 9995430..85560b2 100644 --- a/lib/src/bitcoin/silent_payments/address.dart +++ b/lib/src/bitcoin/silent_payments/address.dart @@ -60,7 +60,7 @@ class SilentPaymentOwner extends SilentPaymentAddress { } SilentPaymentOwner toLabeledSilentPaymentAddress(int m) { - final B_m = B_spend.clone().tweakAdd(BigintUtils.fromBytes(generateLabel(m))); + final B_m = B_spend.tweakAdd(BigintUtils.fromBytes(generateLabel(m))); return SilentPaymentOwner( b_scan: b_scan, b_spend: b_spend, B_scan: B_scan, B_spend: B_m, hrp: hrp, version: version); } diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index 3414742..9358204 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -8,6 +8,14 @@ class SilentPaymentOutput { SilentPaymentOutput(this.address, this.amount); } +class SilentPaymentScanningOutput { + final SilentPaymentOutput output; + final String tweak; + final String? label; + + SilentPaymentScanningOutput({required this.output, required this.tweak, this.label}); +} + class ECPrivateInfo { final ECPrivate privkey; final bool isTaproot; @@ -86,32 +94,29 @@ class SilentPaymentBuilder { for (final silentPaymentDestination in silentPaymentDestinations) { final B_scan = silentPaymentDestination.B_scan; - final B_scan_hex = B_scan.toHex(); + final scanPubkey = B_scan.toHex(); - if (silentPaymentGroups.containsKey(B_scan_hex)) { + if (silentPaymentGroups.containsKey(scanPubkey)) { // Current key already in silentPaymentGroups, simply add up the new destination // with the already calculated ecdhSharedSecret - final group = silentPaymentGroups[B_scan_hex]!; + final group = silentPaymentGroups[scanPubkey]!; final ecdhSharedSecret = group.keys.first; final recipients = group.values.first; - silentPaymentGroups[B_scan_hex] = { + silentPaymentGroups[scanPubkey] = { ecdhSharedSecret: [...recipients, silentPaymentDestination] }; } else { - final senderPartialSecret = - a_sum!.clone().tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); + final senderPartialSecret = a_sum!.tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); final ecdhSharedSecret = - B_scan.clone().tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); + B_scan.tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); - silentPaymentGroups[B_scan_hex] = { + silentPaymentGroups[scanPubkey] = { ecdhSharedSecret: [silentPaymentDestination] }; } } - // Result destinations with amounts - // { : [(, ), (, )...] } Map> result = {}; for (final group in silentPaymentGroups.entries) { final ecdhSharedSecret = group.value.keys.first; @@ -126,14 +131,14 @@ class SilentPaymentBuilder { ]), "BIP0352/SharedSecret"); - final P_mn = ECPublic(destination.B_spend.publicKey).tweakAdd(BigintUtils.fromBytes(t_k)); + final P_mn = destination.B_spend.tweakAdd(BigintUtils.fromBytes(t_k)); if (result.containsKey(destination.toString())) { result[destination.toString()]! - .add(SilentPaymentOutput(P_mn.toTaprootAddress(), destination.amount)); + .add(SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount)); } else { result[destination.toString()] = [ - SilentPaymentOutput(P_mn.toTaprootAddress(), destination.amount) + SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount) ]; } @@ -144,15 +149,18 @@ class SilentPaymentBuilder { return result; } - Map> scanOutputs( - ECPrivate b_scan, ECPublic B_spend, List outputsToCheck, - {Map? precomputedLabels}) { + Map scanOutputs( + ECPrivate b_scan, + ECPublic B_spend, + List outputsToCheck, { + Map? precomputedLabels, + }) { final tweakDataForRecipient = receiverTweak != null ? ECPublic.fromHex(receiverTweak!) - : A_sum.clone().tweakMul(BigintUtils.fromBytes(inputHash)); + : A_sum.tweakMul(BigintUtils.fromBytes(inputHash)); final ecdhSharedSecret = tweakDataForRecipient.tweakMul(b_scan.toBigInt()); - final matches = >{}; + final matches = {}; var k = 0; do { @@ -163,77 +171,48 @@ class SilentPaymentBuilder { ]), "BIP0352/SharedSecret"); - final P_k = B_spend.clone().tweakAdd(BigintUtils.fromBytes(t_k)); + final P_k = B_spend.tweakAdd(BigintUtils.fromBytes(t_k)); final length = outputsToCheck.length; for (var i = 0; i < length; i++) { - final output = outputsToCheck[i]; - - if (output == - BytesUtils.toHexString( - P_k.toTaprootAddress().toScriptPubKey().toBytes().sublist(2)) || - (BytesUtils.compareBytes(ECPublic.fromHex(output).toCompressedBytes().sublist(1), - P_k.toCompressedBytes().sublist(1)) == - 0)) { - matches[P_k.toHex()] = [BytesUtils.toHexString(t_k)]; + final output = outputsToCheck[i].script.toBytes().sublist(2); + final outputPubkey = BytesUtils.toHexString(output); + final outputAmount = outputsToCheck[i].value.toInt(); + + if ((BytesUtils.compareBytes(output, P_k.toCompressedBytes().sublist(1)) == 0)) { + matches[outputPubkey] = SilentPaymentScanningOutput( + output: SilentPaymentOutput(P_k.toTaprootAddress(tweak: false), outputAmount), + tweak: BytesUtils.toHexString(t_k), + ); outputsToCheck.removeAt(i); k++; break; } if (precomputedLabels != null && precomputedLabels.isNotEmpty) { - var m_G_sub = ECPublic.fromHex(output).pubkeyAdd(P_k.clone().negate()); + var m_G_sub = ECPublic.fromBytes(output).pubkeyAdd(P_k.negate()); var m_G = precomputedLabels[m_G_sub.toHex()]; if (m_G == null) { - m_G_sub = ECPublic.fromHex(output).negate().pubkeyAdd(P_k.clone().negate()); + m_G_sub = ECPublic.fromBytes(output).negate().pubkeyAdd(P_k.negate()); m_G = precomputedLabels[m_G_sub.toHex()]; } if (m_G != null) { - final P_km = P_k.clone().tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))); + final P_km = P_k.tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))); - matches[P_km.toHex()] = [ - ECPrivate.fromBytes(t_k) + matches[outputPubkey] = SilentPaymentScanningOutput( + output: SilentPaymentOutput(P_km.toTaprootAddress(tweak: false), outputAmount), + tweak: ECPrivate.fromBytes(t_k) .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))) .toHex(), - m_G - ]; + label: m_G, + ); outputsToCheck.removeAt(i); k++; break; } - - if (m_G == null) { - final labels = precomputedLabels.keys; - bool found = false; - for (final label in labels) { - final tweak = precomputedLabels[label]; - final P_km = - P_k.clone().tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(tweak!))); - - if (BytesUtils.toHexString( - P_km.toTaprootAddress().toScriptPubKey().toBytes().sublist(2)) == - output) { - matches[P_km.toHex()] = [ - ECPrivate.fromBytes(t_k) - .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(tweak))) - .toHex(), - label - ]; - - outputsToCheck.removeAt(i); - k++; - found = true; - break; - } - } - - if (found) { - break; - } - } } outputsToCheck.removeAt(i); @@ -247,3 +226,8 @@ class SilentPaymentBuilder { return matches; } } + +BitcoinScriptOutput getScriptFromOutput(String pubkey, int amount) { + return BitcoinScriptOutput( + script: Script(script: [BitcoinOpCodeConst.OP_1, pubkey]), value: BigInt.from(amount)); +} diff --git a/lib/src/bitcoin/silent_payments/silent_payments.dart b/lib/src/bitcoin/silent_payments/silent_payments.dart index ab8446b..d839c6b 100644 --- a/lib/src/bitcoin/silent_payments/silent_payments.dart +++ b/lib/src/bitcoin/silent_payments/silent_payments.dart @@ -12,7 +12,7 @@ library bitcoin_base.silent_payments; import 'dart:typed_data'; import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/script/outpoint.dart'; +import 'package:bitcoin_base/src/provider/models/models.dart'; import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; import 'package:bitcoin_base/src/crypto/crypto.dart'; import 'package:bitcoin_base/src/models/network.dart'; diff --git a/test/silent_payments.dart b/test/silent_payments.dart index ceb7112..cce5df9 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -76,9 +76,9 @@ main() { List expectedDestinations = sendingTest['expected']['outputs']; - silentPaymentDestinations.forEach((destination) { + for (final destination in silentPaymentDestinations) { expect(sendingOutputs[destination.toString()] != null, true); - }); + } final generatedOutputs = sendingOutputs.values.expand((element) => element).toList(); for (final expected in expectedDestinations) { @@ -87,7 +87,10 @@ main() { BytesUtils.toHexString(output.address.pubkey!.toCompressedBytes().sublist(1)) == expectedPubkey); - expect(generatedPubkey != null, true); + expect( + BytesUtils.toHexString( + generatedPubkey.address.pubkey!.toCompressedBytes().sublist(1)), + expectedPubkey); } } } @@ -163,8 +166,8 @@ main() { if (inputPubKeys.isNotEmpty) { final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); - final addToWallet = spb.scanOutputs( - silentPaymentOwner.b_scan, silentPaymentOwner.B_spend, outputsToCheck, + final addToWallet = spb.scanOutputs(silentPaymentOwner.b_scan, silentPaymentOwner.B_spend, + outputsToCheck.map((o) => getScriptFromOutput(o, 0)).toList(), precomputedLabels: preComputedLabels); final expectedDestinations = expected['outputs']; @@ -174,10 +177,9 @@ main() { final output = addToWallet.entries.elementAt(i); final pubkey = output.key; final expectedPubkey = expectedDestinations[i]["pub_key"]; - expect(BytesUtils.toHexString(BytesUtils.fromHexString(pubkey).sublist(1)), - expectedPubkey); + expect(pubkey, expectedPubkey); - final privKeyTweak = output.value[0]; + final privKeyTweak = output.value.tweak; final expectedPrivKeyTweak = expectedDestinations[i]["priv_key_tweak"]; expect(privKeyTweak, expectedPrivKeyTweak); From fb4c0a0b6cf24628ddad7d3cdc58e4c918eff714 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Mon, 11 Mar 2024 08:46:21 -0300 Subject: [PATCH 12/53] fix: transaction builder with non-tweak silent payment version --- lib/src/provider/models/utxo_details.dart | 3 +++ .../provider/transaction_builder/transaction_builder.dart | 4 +++- test/silent_payments.dart | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/src/provider/models/utxo_details.dart b/lib/src/provider/models/utxo_details.dart index 8223d64..f973d8f 100644 --- a/lib/src/provider/models/utxo_details.dart +++ b/lib/src/provider/models/utxo_details.dart @@ -203,6 +203,7 @@ class BitcoinUtxo { required this.scriptType, this.blockHeight, this.token, + this.isSilentPayment, }); /// check if utxos is p2tr @@ -210,6 +211,8 @@ class BitcoinUtxo { return scriptType == SegwitAddresType.p2tr; } + bool? isSilentPayment; + /// check if utxos is segwit bool isSegwit() { return scriptType.isSegwit || isP2shSegwit(); diff --git a/lib/src/provider/transaction_builder/transaction_builder.dart b/lib/src/provider/transaction_builder/transaction_builder.dart index 46d5c96..bd9355c 100644 --- a/lib/src/provider/transaction_builder/transaction_builder.dart +++ b/lib/src/provider/transaction_builder/transaction_builder.dart @@ -214,7 +214,9 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { } return senderPub.toP2pkhAddress().toScriptPubKey(); case SegwitAddresType.p2tr: - return senderPub.toTaprootAddress().toScriptPubKey(); + return senderPub + .toTaprootAddress(tweak: utxo.utxo.isSilentPayment != true) + .toScriptPubKey(); case P2shAddressType.p2pkhInP2sh: if (isTaproot) { return senderPub.toP2pkhInP2sh().toScriptPubKey(); diff --git a/test/silent_payments.dart b/test/silent_payments.dart index cce5df9..9205da8 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -183,11 +183,11 @@ main() { final expectedPrivKeyTweak = expectedDestinations[i]["priv_key_tweak"]; expect(privKeyTweak, expectedPrivKeyTweak); - final fullPrivateKey = - silentPaymentOwner.b_spend.clone().tweakAdd(BigintUtils.parse(privKeyTweak)); + var fullPrivateKey = + silentPaymentOwner.b_spend.tweakAdd(BigintUtils.parse(privKeyTweak)); if (fullPrivateKey.toBytes()[0] == 0x03) { - fullPrivateKey.negate(); + fullPrivateKey = fullPrivateKey.negate(); } // Sign the message with schnorr From 3ddad3d1a9b78f49c9ef542962758400315d64a7 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 10 Apr 2024 17:29:13 -0300 Subject: [PATCH 13/53] fix: inconsistent p2tr as input silent payments --- lib/src/bitcoin/silent_payments/payment.dart | 38 ++++++++++++-------- lib/src/crypto/keypair/ec_private.dart | 7 ++++ lib/src/crypto/keypair/ec_public.dart | 2 +- test/silent_payments.dart | 3 +- 4 files changed, 33 insertions(+), 17 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index 9358204..aaafeb7 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -52,11 +52,9 @@ class SilentPaymentBuilder { final sortedOutpoints = >[]; for (final outpoint in outpoints!) { - final vout = outpoint.index; - sortedOutpoints.add(BytesUtils.concatBytes([ BytesUtils.fromHexString(outpoint.txid).reversed.toList(), - BigintUtils.toBytes(BigInt.from(vout), length: 4, order: Endian.little) + BigintUtils.toBytes(BigInt.from(outpoint.index), length: 4, order: Endian.little) ])); } @@ -69,18 +67,26 @@ class SilentPaymentBuilder { Map> createOutputs( List inputPrivKeyInfos, - List silentPaymentDestinations, - ) { + List silentPaymentDestinations, { + bool tweak = true, + }) { ECPrivate? a_sum; for (final info in inputPrivKeyInfos) { - final key = info.privkey; + var k = info.privkey; final isTaproot = info.isTaproot; - var k = ECPrivate(key.prive); + if (isTaproot) { + if (tweak) { + k = k.toTweakedTaprootKey(); + } + + final xOnlyPubkey = k.getPublic(); + final isOdd = xOnlyPubkey.publicKey.point.y % BigInt.two != BigInt.zero; - if (isTaproot && key.getPublic().publicKey.point.y % BigInt.two != BigInt.zero) { - k = k.negate(); + if (isOdd) { + k = k.negate(); + } } if (a_sum == null) { @@ -90,6 +96,9 @@ class SilentPaymentBuilder { } } + A_sum = a_sum!.getPublic(); + _getInputHash(); + Map>> silentPaymentGroups = {}; for (final silentPaymentDestination in silentPaymentDestinations) { @@ -107,7 +116,7 @@ class SilentPaymentBuilder { ecdhSharedSecret: [...recipients, silentPaymentDestination] }; } else { - final senderPartialSecret = a_sum!.tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); + final senderPartialSecret = a_sum.tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); final ecdhSharedSecret = B_scan.tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); @@ -132,14 +141,13 @@ class SilentPaymentBuilder { "BIP0352/SharedSecret"); final P_mn = destination.B_spend.tweakAdd(BigintUtils.fromBytes(t_k)); + final resOutput = + SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount); if (result.containsKey(destination.toString())) { - result[destination.toString()]! - .add(SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount)); + result[destination.toString()]!.add(resOutput); } else { - result[destination.toString()] = [ - SilentPaymentOutput(P_mn.toTaprootAddress(tweak: false), destination.amount) - ]; + result[destination.toString()] = [resOutput]; } k++; diff --git a/lib/src/crypto/keypair/ec_private.dart b/lib/src/crypto/keypair/ec_private.dart index a2c1d47..83152fe 100644 --- a/lib/src/crypto/keypair/ec_private.dart +++ b/lib/src/crypto/keypair/ec_private.dart @@ -87,6 +87,13 @@ class ECPrivate { return BytesUtils.toHexString(signatur); } + ECPrivate toTweakedTaprootKey() { + final t = P2TRUtils.calculateTweek(getPublic().publicKey.point as ProjectiveECCPoint); + + return ECPrivate.fromBytes( + BitcoinSignerUtils.calculatePrivateTweek(toBytes(), BigintUtils.fromBytes(t))); + } + static ECPrivate random() { final secret = QuickCrypto.generateRandom(); return ECPrivate.fromBytes(secret); diff --git a/lib/src/crypto/keypair/ec_public.dart b/lib/src/crypto/keypair/ec_public.dart index f4058b3..e09e27a 100644 --- a/lib/src/crypto/keypair/ec_public.dart +++ b/lib/src/crypto/keypair/ec_public.dart @@ -108,7 +108,7 @@ class ECPublic { /// custom spending conditions. P2trAddress toTaprootAddress({List>? scripts, bool tweak = true}) { final pubKey = toTapRotHex(script: scripts, tweak: tweak); - return P2trAddress.fromProgram(program: pubKey, pubkey: this); + return P2trAddress.fromProgram(program: pubKey, pubkey: ECPublic.fromHex(pubKey)); } /// toP2wpkhInP2sh generates a P2SH (Pay-to-Script-Hash) address diff --git a/test/silent_payments.dart b/test/silent_payments.dart index 9205da8..4e65019 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -72,7 +72,8 @@ main() { .toList(); final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); - sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); + sendingOutputs = + spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations, tweak: false); List expectedDestinations = sendingTest['expected']['outputs']; From 22b3f3d6c8fcc0900df94607999bd2cdbb93adb3 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Wed, 17 Apr 2024 16:33:02 -0300 Subject: [PATCH 14/53] feat: move tweak to privkeyInfo --- lib/src/bitcoin/silent_payments/payment.dart | 10 +++++----- test/silent_payments.dart | 10 ++++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index aaafeb7..b280599 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -19,8 +19,9 @@ class SilentPaymentScanningOutput { class ECPrivateInfo { final ECPrivate privkey; final bool isTaproot; + final bool tweak; - ECPrivateInfo(this.privkey, this.isTaproot); + ECPrivateInfo(this.privkey, this.isTaproot, {this.tweak = false}); } class SilentPaymentBuilder { @@ -67,9 +68,8 @@ class SilentPaymentBuilder { Map> createOutputs( List inputPrivKeyInfos, - List silentPaymentDestinations, { - bool tweak = true, - }) { + List silentPaymentDestinations, + ) { ECPrivate? a_sum; for (final info in inputPrivKeyInfos) { @@ -77,7 +77,7 @@ class SilentPaymentBuilder { final isTaproot = info.isTaproot; if (isTaproot) { - if (tweak) { + if (info.tweak) { k = k.toTweakedTaprootKey(); } diff --git a/test/silent_payments.dart b/test/silent_payments.dart index 4e65019..dc22c08 100644 --- a/test/silent_payments.dart +++ b/test/silent_payments.dart @@ -59,8 +59,11 @@ main() { continue; } - inputPrivKeyInfos - .add(ECPrivateInfo(privkey, prevoutScript.getAddressType() == SegwitAddresType.p2tr)); + inputPrivKeyInfos.add(ECPrivateInfo( + privkey, + prevoutScript.getAddressType() == SegwitAddresType.p2tr, + tweak: false, + )); inputPubKeys.add(pubkey); } @@ -72,8 +75,7 @@ main() { .toList(); final spb = SilentPaymentBuilder(pubkeys: inputPubKeys, outpoints: vinOutpoints); - sendingOutputs = - spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations, tweak: false); + sendingOutputs = spb.createOutputs(inputPrivKeyInfos, silentPaymentDestinations); List expectedDestinations = sendingTest['expected']['outputs']; From 2a18ab92a9f7136b76fcd1bf8480eaaa90e0a6b2 Mon Sep 17 00:00:00 2001 From: Rafael Saes Date: Thu, 18 Apr 2024 10:51:58 -0300 Subject: [PATCH 15/53] feat: add silent payment output builder to bitcoin transaction builder --- lib/src/bitcoin/silent_payments/payment.dart | 18 +-- .../transaction_builder.dart | 145 +++++++++++------- test/silent_payments.dart | 4 +- 3 files changed, 100 insertions(+), 67 deletions(-) diff --git a/lib/src/bitcoin/silent_payments/payment.dart b/lib/src/bitcoin/silent_payments/payment.dart index b280599..3beb6cc 100644 --- a/lib/src/bitcoin/silent_payments/payment.dart +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -25,18 +25,18 @@ class ECPrivateInfo { } class SilentPaymentBuilder { - final List? outpoints; + final List vinOutpoints; final List? pubkeys; - late ECPublic A_sum; - late List inputHash; + ECPublic? A_sum; + List? inputHash; String? receiverTweak; SilentPaymentBuilder({ - this.outpoints, + required this.vinOutpoints, this.pubkeys, this.receiverTweak, }) { - if (receiverTweak == null) { + if (receiverTweak == null && pubkeys != null) { _getAsum(); _getInputHash(); } @@ -52,7 +52,7 @@ class SilentPaymentBuilder { void _getInputHash() { final sortedOutpoints = >[]; - for (final outpoint in outpoints!) { + for (final outpoint in vinOutpoints) { sortedOutpoints.add(BytesUtils.concatBytes([ BytesUtils.fromHexString(outpoint.txid).reversed.toList(), BigintUtils.toBytes(BigInt.from(outpoint.index), length: 4, order: Endian.little) @@ -63,7 +63,7 @@ class SilentPaymentBuilder { final lowestOutpoint = sortedOutpoints.first; inputHash = taggedHash( - BytesUtils.concatBytes([lowestOutpoint, A_sum.toCompressedBytes()]), "BIP0352/Inputs"); + BytesUtils.concatBytes([lowestOutpoint, A_sum!.toCompressedBytes()]), "BIP0352/Inputs"); } Map> createOutputs( @@ -116,7 +116,7 @@ class SilentPaymentBuilder { ecdhSharedSecret: [...recipients, silentPaymentDestination] }; } else { - final senderPartialSecret = a_sum.tweakMul(BigintUtils.fromBytes(inputHash)).toBytes(); + final senderPartialSecret = a_sum.tweakMul(BigintUtils.fromBytes(inputHash!)).toBytes(); final ecdhSharedSecret = B_scan.tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); @@ -165,7 +165,7 @@ class SilentPaymentBuilder { }) { final tweakDataForRecipient = receiverTweak != null ? ECPublic.fromHex(receiverTweak!) - : A_sum.tweakMul(BigintUtils.fromBytes(inputHash)); + : A_sum!.tweakMul(BigintUtils.fromBytes(inputHash!)); final ecdhSharedSecret = tweakDataForRecipient.tweakMul(b_scan.toBigInt()); final matches = {}; diff --git a/lib/src/provider/transaction_builder/transaction_builder.dart b/lib/src/provider/transaction_builder/transaction_builder.dart index bd9355c..0896cf8 100644 --- a/lib/src/provider/transaction_builder/transaction_builder.dart +++ b/lib/src/provider/transaction_builder/transaction_builder.dart @@ -30,6 +30,9 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { final bool isFakeTransaction; final BitcoinOrdering inputOrdering; final BitcoinOrdering outputOrdering; + final List? inputPrivKeyInfos; + final List? vinOutpoints; + BitcoinTransactionBuilder({ required this.outputs, required this.fee, @@ -40,6 +43,8 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { this.memo, this.enableRBF = false, this.isFakeTransaction = false, + this.inputPrivKeyInfos, + this.vinOutpoints, }) : utxosInfo = utxos { _validateBuilder(); } @@ -54,8 +59,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { final tokenInput = outputs.whereType(); final burn = outputs.whereType(); if (token || tokenInput.isNotEmpty || burn.isNotEmpty) { - throw const MessageException( - "Cash Token only work on Bitcoin cash network"); + throw const MessageException("Cash Token only work on Bitcoin cash network"); } for (final i in utxosInfo) { /// Verify each input for its association with this network's address. Raise an exception if the address is incorrect. @@ -72,12 +76,15 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { /// This method is used to create a dummy transaction, /// allowing us to obtain the size of the original transaction /// before conducting the actual transaction. This helps us estimate the transaction cost - static int estimateTransactionSize( - {required List utxos, - required List outputs, - required BasedUtxoNetwork network, - String? memo, - bool enableRBF = false}) { + static int estimateTransactionSize({ + required List utxos, + required List outputs, + required BasedUtxoNetwork network, + String? memo, + bool enableRBF = false, + List? inputPrivKeyInfos, + List? vinOutpoints, + }) { final transactionBuilder = BitcoinTransactionBuilder( /// Now, we provide the UTXOs we want to spend. utxos: utxos, @@ -109,6 +116,9 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { /// We consider the transaction to be fake so that it doesn't check the amounts /// and doesn't generate errors when determining the transaction size. isFakeTransaction: true, + + inputPrivKeyInfos: inputPrivKeyInfos, + vinOutpoints: vinOutpoints, ); /// 64 byte schnorr signature length @@ -119,8 +129,8 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { const String fakeECDSASignatureBytes = "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; - final transaction = transactionBuilder - .buildTransaction((trDigest, utxo, multiSigPublicKey, int sighash) { + final transaction = + transactionBuilder.buildTransaction((trDigest, utxo, multiSigPublicKey, int sighash) { if (utxo.utxo.isP2tr()) { return fakeSchnorSignaturBytes; } else { @@ -130,8 +140,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { /// Now we need the size of the transaction. If the transaction is a SegWit transaction, /// we use the getVSize method; otherwise, we use the getSize method to obtain the transaction size - final size = - transaction.hasSegwit ? transaction.getVSize() : transaction.getSize(); + final size = transaction.hasSegwit ? transaction.getVSize() : transaction.getSize(); return size; } @@ -174,16 +183,12 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { switch (utxo.utxo.scriptType) { case P2shAddressType.p2wshInP2sh: if (isTaproot) { - return multiSigAAddr - .toP2wshInP2shAddress(network: network) - .toScriptPubKey(); + return multiSigAAddr.toP2wshInP2shAddress(network: network).toScriptPubKey(); } return script; case SegwitAddresType.p2wsh: if (isTaproot) { - return multiSigAAddr - .toP2wshAddress(network: network) - .toScriptPubKey(); + return multiSigAAddr.toP2wshAddress(network: network).toScriptPubKey(); } return script; case P2shAddressType.p2pkhInP2sh: @@ -192,8 +197,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { } return script; default: - throw ArgumentError( - "unsuported multi-sig type ${utxo.utxo.scriptType}"); + throw ArgumentError("unsuported multi-sig type ${utxo.utxo.scriptType}"); } } @@ -256,13 +260,8 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { // /// Returns: /// - List: representing the transaction digest to be used for signing the input. - List _generateTransactionDigest( - Script scriptPubKeys, - int input, - UtxoWithAddress utox, - BtcTransaction transaction, - List taprootAmounts, - List