diff --git a/README.md b/README.md index a02d9b5..16ba6b9 100644 --- a/README.md +++ b/README.md @@ -144,10 +144,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(); @@ -286,8 +286,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(); @@ -336,9 +336,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(), @@ -365,11 +365,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 @@ -396,13 +396,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, @@ -465,7 +465,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. @@ -514,7 +514,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, @@ -678,7 +678,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 9b25036..4acdb9b 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 bb93937..c0b8754 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(), @@ -66,7 +66,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 28404cd..4db99f2 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 aa71f8b..6415852 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 092fb24..6c2e1fa 100644 --- a/example/lib/bitcoin_cash/minting_nft_example.dart +++ b/example/lib/bitcoin_cash/minting_nft_example.dart @@ -28,7 +28,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(), @@ -54,7 +54,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 d46b23d..c7af331 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 d875661..2e9b82d 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 f9631f9..12fdbd2 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 6b31303..3f5bdd4 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 6b4542e..794deb6 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 @@ -60,7 +60,7 @@ void main() async { // tb1qxt3c7849m0m6cv3z3s35c3zvdna3my3yz0r609qd9g0dcyyk580sgyldhe final p2wshMultiSigAddress = - multiSignatureAddress.toP2wshAddress(network: network).toAddress(network); + multiSignatureAddress.toP2wshAddress(network: network).toP2pkhAddress(network); // p2sh(p2wsh) multisig final signerP2sh1 = @@ -80,12 +80,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 @@ -203,7 +203,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 5da3143..851259f 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 @@ -37,7 +37,7 @@ void main() async { final public4 = private4.getPublic(); // P2PKH ADDRESS - final exampleAddr1 = public1.toAddress(); + final exampleAddr1 = public1.toP2pkhAddress(); // P2TR final exampleAddr2 = public2.toTaprootAddress(); @@ -45,7 +45,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 @@ -55,7 +55,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(); @@ -143,7 +143,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/example/pubspec.lock b/example/pubspec.lock index d7800dd..318e7a4 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -19,10 +19,11 @@ packages: blockchain_utils: dependency: "direct main" description: - name: blockchain_utils - sha256: aebc3a32b927b34f638817c4bfdb85f86a97e6ad35f0cd962660b0c6e8d5c56b - url: "https://pub.dev" - source: hosted + path: "." + ref: cake-update-v2 + resolved-ref: "2767a54ed2b0a23494e4e96a3fe5b5022b834b70" + url: "https://github.com/cake-tech/blockchain_utils" + source: git version: "3.3.0" boolean_selector: dependency: transitive @@ -110,26 +111,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -158,10 +159,10 @@ packages: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.0" path: dependency: transitive description: @@ -219,10 +220,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.6.1" typed_data: dependency: transitive description: @@ -243,10 +244,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "13.0.0" web: dependency: transitive description: @@ -257,4 +258,3 @@ packages: version: "0.5.1" sdks: dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index ca4fb33..25d16d4 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.2.3 <4.0.0' + sdk: '>=3.0.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -37,9 +37,10 @@ dependencies: cupertino_icons: ^1.0.2 bitcoin_base: path: ../ - # blockchain_utils: - # path: ../../blockchain_utils - blockchain_utils: ^3.3.0 + blockchain_utils: + git: + url: https://github.com/cake-tech/blockchain_utils + ref: cake-update-v2 http: ^1.2.0 dev_dependencies: diff --git a/lib/bitcoin_base.dart b/lib/bitcoin_base.dart index ef45ef4..cb559d7 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'; @@ -16,8 +18,12 @@ export 'package:bitcoin_base/src/models/network.dart'; export 'package:bitcoin_base/src/provider/api_provider.dart'; -export 'package:bitcoin_base/src/utils/btc_utils.dart'; +export 'package:bitcoin_base/src/utils/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'; + +export 'package:bitcoin_base/src/bitcoin/script/op_code/constant.dart'; diff --git a/lib/src/bitcoin/address/address.dart b/lib/src/bitcoin/address/address.dart index 8f1d988..4dd7d7e 100644 --- a/lib/src/bitcoin/address/address.dart +++ b/lib/src/bitcoin/address/address.dart @@ -12,6 +12,7 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/src/exception/exception.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 1211396..de0045d 100644 --- a/lib/src/bitcoin/address/core.dart +++ b/lib/src/bitcoin/address/core.dart @@ -9,8 +9,25 @@ 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 BitcoinBasePluginException( - 'Invalid BitcoinAddressType: $value')); + orElse: () => throw BitcoinBasePluginException('Invalid BitcoinAddressType: $value')); + } + + static BitcoinAddressType fromAddress(BitcoinBaseAddress address) { + if (address is P2pkhAddress) { + return P2pkhAddressType.p2pkh; + } else if (address is P2shAddress) { + return P2shAddressType.p2wpkhInP2sh; + } else if (address is P2wshAddress) { + return SegwitAddresType.p2wsh; + } else if (address is P2trAddress) { + return SegwitAddresType.p2tr; + } else if (address is SilentPaymentsAddresType) { + return SilentPaymentsAddresType.p2sp; + } else if (address is P2wpkhAddress) { + return SegwitAddresType.p2wpkh; + } + + throw BitcoinBasePluginException('Invalid BitcoinAddressType: $address'); } /// Check if the address type is Pay-to-Script-Hash (P2SH). @@ -24,6 +41,7 @@ abstract class BitcoinAddressType implements Enumerate { SegwitAddresType.p2wpkh, SegwitAddresType.p2tr, SegwitAddresType.p2wsh, + SegwitAddresType.mweb, P2shAddressType.p2wshInP2sh, P2shAddressType.p2wpkhInP2sh, P2shAddressType.p2pkhInP2sh, @@ -34,20 +52,52 @@ abstract class BitcoinAddressType implements Enumerate { P2shAddressType.p2pkInP2sh32wt, P2shAddressType.p2pkhInP2shwt, P2shAddressType.p2pkInP2shwt, - P2pkhAddressType.p2pkhwt + P2pkhAddressType.p2pkhwt, + SilentPaymentsAddresType.p2sp ]; @override - String toString() { - return "BitcoinAddressType.$value"; - } + String toString() => value; } abstract class BitcoinBaseAddress { + BitcoinBaseAddress({this.network}); + BitcoinAddressType get type; - String toAddress(BasedUtxoNetwork network); + String toAddress([BasedUtxoNetwork? network]); Script toScriptPubKey(); String pubKeyHash(); String get addressProgram; + BasedUtxoNetwork? network; + + static BitcoinBaseAddress fromString( + String address, [ + BasedUtxoNetwork network = BitcoinNetwork.mainnet, + ]) { + if (network is BitcoinCashNetwork) { + if (!address.startsWith("bitcoincash:") && + (address.startsWith("q") || address.startsWith("p"))) { + address = "bitcoincash:$address"; + } + + return BitcoinCashAddress(address).baseAddress; + } + + if (P2pkhAddress.regex.hasMatch(address)) { + return P2pkhAddress.fromAddress(address: address, network: network); + } else if (P2shAddress.regex.hasMatch(address)) { + return P2shAddress.fromAddress(address: address, network: network); + } else if (P2wshAddress.regex.hasMatch(address)) { + return P2wshAddress.fromAddress(address: address, network: network); + } else if (P2trAddress.regex.hasMatch(address)) { + return P2trAddress.fromAddress(address: address, network: network); + } else if (SilentPaymentAddress.regex.hasMatch(address)) { + return SilentPaymentAddress.fromAddress(address); + } else if (P2wpkhAddress.regex.hasMatch(address)) { + return P2wpkhAddress.fromAddress(address: address, network: network); + } + + throw BitcoinBasePluginException('Invalid BitcoinBaseAddress: $address'); + } } class PubKeyAddressType implements BitcoinAddressType { @@ -62,9 +112,7 @@ class PubKeyAddressType implements BitcoinAddressType { @override int get hashLength => 20; @override - String toString() { - return "PubKeyAddressType.$value"; - } + String toString() => value; } class P2pkhAddressType implements BitcoinAddressType { @@ -83,21 +131,19 @@ class P2pkhAddressType implements BitcoinAddressType { @override int get hashLength => 20; @override - String toString() { - return "P2pkhAddressType.$value"; - } + String toString() => value; } 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 @@ -109,35 +155,33 @@ 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; @override - String toString() { - return "P2shAddressType.$value"; - } + String toString() => value; } class SegwitAddresType implements BitcoinAddressType { @@ -145,6 +189,7 @@ class SegwitAddresType implements BitcoinAddressType { static const SegwitAddresType p2wpkh = SegwitAddresType._("P2WPKH"); static const SegwitAddresType p2tr = SegwitAddresType._("P2TR"); static const SegwitAddresType p2wsh = SegwitAddresType._("P2WSH"); + static const SegwitAddresType mweb = SegwitAddresType._("MWEB"); @override bool get isP2sh => false; @override @@ -158,13 +203,33 @@ class SegwitAddresType implements BitcoinAddressType { switch (this) { case SegwitAddresType.p2wpkh: return 20; + case SegwitAddresType.mweb: + return 66; default: return 32; } } @override - String toString() { - return "SegwitAddresType.$value"; + 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/legacy_address.dart b/lib/src/bitcoin/address/legacy_address.dart index c3952cf..c2b9798 100644 --- a/lib/src/bitcoin/address/legacy_address.dart +++ b/lib/src/bitcoin/address/legacy_address.dart @@ -1,30 +1,77 @@ part of 'package:bitcoin_base/src/bitcoin/address/address.dart'; -abstract class LegacyAddress implements BitcoinBaseAddress { +abstract class LegacyAddress extends 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( - address: address, type: type, network: network); + LegacyAddress.fromHash160({ + required String h160, + required BitcoinAddressType type, + super.network, + }) : _addressProgram = _BitcoinAddressUtils.validateAddressProgram(h160, type), + super(); + + LegacyAddress.fromAddress({required String address, required BasedUtxoNetwork network}) + : super(network: network) { + final decode = _BitcoinAddressUtils.decodeLegacyAddressWithNetworkAndType( + address: address, + type: type, + network: network, + ); + if (decode == null) { - throw BitcoinBasePluginException( - "Invalid ${network.conf.coinName} address"); + throw BitcoinBasePluginException("Invalid ${network.conf.coinName} address"); } + _addressProgram = decode; } - LegacyAddress.fromScript({required Script script}) + + LegacyAddress.fromPubkey({required ECPublic pubkey, super.network}) + : _pubkey = pubkey, + _addressProgram = _BitcoinAddressUtils.pubkeyToHash160(pubkey.toHex()); + + LegacyAddress.fromRedeemScript({required Script script, super.network}) : _addressProgram = _BitcoinAddressUtils.scriptToHash160(script); - LegacyAddress._(); + LegacyAddress.fromScriptSig({required Script script, super.network}) { + 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 => _pubkey; + String? get signature => _signature; + @override String get addressProgram { if (type == PubKeyAddressType.p2pk) throw UnimplementedError(); @@ -32,9 +79,22 @@ abstract class LegacyAddress implements BitcoinBaseAddress { } @override - String toAddress(BasedUtxoNetwork network) { + String toAddress([BasedUtxoNetwork? network]) { + network ??= this.network; + + if (network == null) { + throw const BitcoinBasePluginException("Network is required"); + } + + if (!network.supportedAddress.contains(type)) { + throw BitcoinBasePluginException("network does not support ${type.value} address"); + } + return _BitcoinAddressUtils.legacyToAddress( - network: network, addressProgram: addressProgram, type: type); + network: network, + addressProgram: addressProgram, + type: type, + ); } @override @@ -44,91 +104,147 @@ abstract class LegacyAddress implements BitcoinBaseAddress { } class P2shAddress extends LegacyAddress { - P2shAddress.fromScript( - {required Script script, this.type = P2shAddressType.p2pkInP2sh}) - : super.fromScript(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); + static RegExp get regex => RegExp(r'[23M][a-km-zA-HJ-NP-Z1-9]{25,34}'); + + P2shAddress.fromRedeemScript({ + required super.script, + super.network, + this.type = P2shAddressType.p2pkInP2sh, + }) : super.fromRedeemScript(); + + P2shAddress.fromAddress({ + required super.address, + required super.network, + this.type = P2shAddressType.p2pkInP2sh, + }) : super.fromAddress(); + + P2shAddress.fromHash160({ + required super.h160, + super.network, + this.type = P2shAddressType.p2pkInP2sh, + }) : super.fromHash160(type: type); + + factory P2shAddress.fromScriptPubkey({ + required Script script, + BasedUtxoNetwork? network, + type = P2shAddressType.p2pkInP2sh, + }) { + if (script.getAddressType() is! P2shAddressType) { + throw ArgumentError("Invalid scriptPubKey"); + } - @override - final P2shAddressType type; + return P2shAddress.fromHash160(h160: script.findScriptParam(1), network: network, type: type); + } @override - String toAddress(BasedUtxoNetwork network) { - if (!network.supportedAddress.contains(type)) { - throw BitcoinBasePluginException( - "network does not support ${type.value} address"); - } - return super.toAddress(network); - } + final P2shAddressType type; /// Returns the scriptPubKey (P2SH) that corresponds to this address @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], + ); + } else { + return Script( + script: [BitcoinOpCodeConst.OP_HASH160, addressProgram, BitcoinOpCodeConst.OP_EQUAL], + ); } - return Script(script: ['OP_HASH160', addressProgram, 'OP_EQUAL']); } } class P2pkhAddress extends LegacyAddress { - P2pkhAddress.fromScript( - {required Script script, this.type = P2pkhAddressType.p2pkh}) - : super.fromScript(script: script); - 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); + static RegExp get regex => RegExp(r'[1mnL][a-km-zA-HJ-NP-Z1-9]{25,34}'); + + factory P2pkhAddress.fromScriptPubkey({ + required Script script, + BasedUtxoNetwork? network, + P2pkhAddressType type = P2pkhAddressType.p2pkh, + }) { + if (script.getAddressType() != P2pkhAddressType.p2pkh) { + throw ArgumentError("Invalid scriptPubKey"); + } + + return P2pkhAddress.fromHash160(h160: script.findScriptParam(2), network: network, type: type); + } + + P2pkhAddress.fromAddress({ + required super.address, + required super.network, + this.type = P2pkhAddressType.p2pkh, + }) : super.fromAddress(); + + P2pkhAddress.fromHash160({ + required super.h160, + super.network, + this.type = P2pkhAddressType.p2pkh, + }) : super.fromHash160(type: type); + + P2pkhAddress.fromScriptSig({ + required super.script, + super.network, + this.type = P2pkhAddressType.p2pkh, + }) : super.fromScriptSig(); @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 BitcoinBasePluginException("Invalid secp256k1 public key"); + static RegExp get regex => RegExp(r'1([A-Za-z0-9]{34})'); + + P2pkAddress({required ECPublic publicKey, super.network}) + : _pubkeyHex = publicKey.toHex(), + super.fromPubkey(pubkey: publicKey); + + factory P2pkAddress.fromPubkey({required ECPublic pubkey}) => pubkey.toP2pkAddress(); + + P2pkAddress.fromAddress({required super.address, required super.network}) : super.fromAddress(); + + 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; - /// Returns the scriptPubKey (P2SH) that corresponds to this address + late final String _pubkeyHex; + @override Script toScriptPubKey() { - return Script(script: [publicHex, 'OP_CHECKSIG']); + return Script(script: [_pubkeyHex, BitcoinOpCodeConst.OP_CHECKSIG]); } @override - String toAddress(BasedUtxoNetwork network) { + String toAddress([BasedUtxoNetwork? network]) { + network ??= this.network; + + if (network == null) { + throw const BitcoinBasePluginException("Network is required"); + } + return _BitcoinAddressUtils.legacyToAddress( - network: network, - addressProgram: _BitcoinAddressUtils.pubkeyToHash160(publicHex), - type: type); + network: network, + addressProgram: _BitcoinAddressUtils.pubkeyToHash160(_pubkeyHex), + type: type, + ); } @override diff --git a/lib/src/bitcoin/address/network_address.dart b/lib/src/bitcoin/address/network_address.dart index e1c4307..8b8fd7f 100644 --- a/lib/src/bitcoin/address/network_address.dart +++ b/lib/src/bitcoin/address/network_address.dart @@ -9,7 +9,7 @@ abstract class BitcoinNetworkAddress { /// Converts the address to a string representation for the specified network [T]. String toAddress([T? network]) { - return network == null ? address : baseAddress.toAddress(network); + return network == null ? address : baseAddress.toAddress(); } /// The type of the Bitcoin address. @@ -22,15 +22,13 @@ abstract class BitcoinNetworkAddress { /// A concrete implementation of [BitcoinNetworkAddress] for Bitcoin network. class BitcoinAddress extends BitcoinNetworkAddress { const BitcoinAddress._(this.baseAddress, this.address); - factory BitcoinAddress(String address, - {BitcoinNetwork network = BitcoinNetwork.mainnet}) { - return BitcoinAddress._( - _BitcoinAddressUtils.decodeAddress(address, network), address); + factory BitcoinAddress(String address, {BitcoinNetwork network = BitcoinNetwork.mainnet}) { + return BitcoinAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); } factory BitcoinAddress.fromBaseAddress(BitcoinBaseAddress address, {DashNetwork network = DashNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return BitcoinAddress._(baseAddress, baseAddress.toAddress(network)); + return BitcoinAddress._(baseAddress, baseAddress.toAddress()); } @override final BitcoinBaseAddress baseAddress; @@ -41,15 +39,13 @@ class BitcoinAddress extends BitcoinNetworkAddress { /// A concrete implementation of [BitcoinNetworkAddress] for Doge network. class DogeAddress extends BitcoinNetworkAddress { const DogeAddress._(this.baseAddress, this.address); - factory DogeAddress(String address, - {DogecoinNetwork network = DogecoinNetwork.mainnet}) { - return DogeAddress._( - _BitcoinAddressUtils.decodeAddress(address, network), address); + factory DogeAddress(String address, {DogecoinNetwork network = DogecoinNetwork.mainnet}) { + return DogeAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); } factory DogeAddress.fromBaseAddress(BitcoinBaseAddress address, {DogecoinNetwork network = DogecoinNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return DogeAddress._(baseAddress, baseAddress.toAddress(network)); + return DogeAddress._(baseAddress, baseAddress.toAddress()); } @override final BitcoinBaseAddress baseAddress; @@ -61,15 +57,13 @@ class DogeAddress extends BitcoinNetworkAddress { /// A concrete implementation of [BitcoinNetworkAddress] for Pepecoin network. class PepeAddress extends BitcoinNetworkAddress { const PepeAddress._(this.baseAddress, this.address); - factory PepeAddress(String address, - {PepeNetwork network = PepeNetwork.mainnet}) { - return PepeAddress._( - _BitcoinAddressUtils.decodeAddress(address, network), address); + factory PepeAddress(String address, {PepeNetwork network = PepeNetwork.mainnet}) { + return PepeAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); } factory PepeAddress.fromBaseAddress(BitcoinBaseAddress address, {PepeNetwork network = PepeNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return PepeAddress._(baseAddress, baseAddress.toAddress(network)); + return PepeAddress._(baseAddress, baseAddress.toAddress()); } @override final BitcoinBaseAddress baseAddress; @@ -81,15 +75,13 @@ class PepeAddress extends BitcoinNetworkAddress { /// A concrete implementation of [BitcoinNetworkAddress] for Litecoin network. class LitecoinAddress extends BitcoinNetworkAddress { LitecoinAddress._(this.baseAddress, this.address); - factory LitecoinAddress(String address, - {LitecoinNetwork network = LitecoinNetwork.mainnet}) { - return LitecoinAddress._( - _BitcoinAddressUtils.decodeAddress(address, network), address); + factory LitecoinAddress(String address, {LitecoinNetwork network = LitecoinNetwork.mainnet}) { + return LitecoinAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); } factory LitecoinAddress.fromBaseAddress(BitcoinBaseAddress address, {LitecoinNetwork network = LitecoinNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return LitecoinAddress._(baseAddress, baseAddress.toAddress(network)); + return LitecoinAddress._(baseAddress, baseAddress.toAddress()); } @override final BitcoinBaseAddress baseAddress; @@ -104,8 +96,10 @@ class BitcoinCashAddress extends BitcoinNetworkAddress { {BitcoinCashNetwork network = BitcoinCashNetwork.mainnet, bool validateNetworkPrefix = false}) { final decodeAddress = _BitcoinAddressUtils.decodeBchAddress( - address, network, - validateNetworkHRP: validateNetworkPrefix); + address, + network, + validateNetworkHRP: validateNetworkPrefix, + ); if (decodeAddress == null) { throw BitcoinBasePluginException("Invalid ${network.value} address."); } @@ -114,7 +108,7 @@ class BitcoinCashAddress extends BitcoinNetworkAddress { factory BitcoinCashAddress.fromBaseAddress(BitcoinBaseAddress address, {BitcoinCashNetwork network = BitcoinCashNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return BitcoinCashAddress._(baseAddress, baseAddress.toAddress(network)); + return BitcoinCashAddress._(baseAddress, baseAddress.toAddress()); } @override final BitcoinBaseAddress baseAddress; @@ -133,15 +127,13 @@ class BitcoinCashAddress extends BitcoinNetworkAddress { /// A concrete implementation of [BitcoinNetworkAddress] for Dash network. class DashAddress extends BitcoinNetworkAddress { const DashAddress._(this.baseAddress, this.address); - factory DashAddress(String address, - {DashNetwork network = DashNetwork.mainnet}) { - return DashAddress._( - _BitcoinAddressUtils.decodeAddress(address, network), address); + factory DashAddress(String address, {DashNetwork network = DashNetwork.mainnet}) { + return DashAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); } factory DashAddress.fromBaseAddress(BitcoinBaseAddress address, {DashNetwork network = DashNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return DashAddress._(baseAddress, baseAddress.toAddress(network)); + return DashAddress._(baseAddress, baseAddress.toAddress()); } @override final BitcoinBaseAddress baseAddress; @@ -152,15 +144,13 @@ class DashAddress extends BitcoinNetworkAddress { /// A concrete implementation of [BitcoinNetworkAddress] for bitcoinSV network. class BitcoinSVAddress extends BitcoinNetworkAddress { const BitcoinSVAddress._(this.baseAddress, this.address); - factory BitcoinSVAddress(String address, - {BitcoinSVNetwork network = BitcoinSVNetwork.mainnet}) { - return BitcoinSVAddress._( - _BitcoinAddressUtils.decodeAddress(address, network), address); + factory BitcoinSVAddress(String address, {BitcoinSVNetwork network = BitcoinSVNetwork.mainnet}) { + return BitcoinSVAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); } factory BitcoinSVAddress.fromBaseAddress(BitcoinBaseAddress address, {BitcoinSVNetwork network = BitcoinSVNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return BitcoinSVAddress._(baseAddress, baseAddress.toAddress(network)); + return BitcoinSVAddress._(baseAddress, baseAddress.toAddress()); } @override final BitcoinBaseAddress baseAddress; diff --git a/lib/src/bitcoin/address/segwit_address.dart b/lib/src/bitcoin/address/segwit_address.dart index a68574f..740e1ea 100644 --- a/lib/src/bitcoin/address/segwit_address.dart +++ b/lib/src/bitcoin/address/segwit_address.dart @@ -1,42 +1,55 @@ 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}) { - if (!network.supportedAddress.contains(type)) { - throw BitcoinBasePluginException( - "network does not support ${type.value} address"); - } +abstract class SegwitAddress extends BitcoinBaseAddress { + SegwitAddress.fromAddress({ + required String address, + required BasedUtxoNetwork network, + required this.segwitVersion, + }) : super(network: network) { addressProgram = _BitcoinAddressUtils.toSegwitProgramWithVersionAndNetwork( - address: address, version: segwitVersion, network: network); + 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}) - : addressProgram = _BitcoinAddressUtils.segwitScriptToSHA256(script); + + SegwitAddress.fromProgram({ + required String program, + required SegwitAddresType addressType, + super.network, + required this.segwitVersion, + this.pubkey, + }) : addressProgram = _BitcoinAddressUtils.validateAddressProgram(program, addressType), + super(); + + SegwitAddress.fromRedeemScript({ + required Script script, + super.network, + required this.segwitVersion, + }) : addressProgram = _BitcoinAddressUtils.segwitScriptToSHA256(script); @override late final String addressProgram; - final int segwitVersion; + ECPublic? pubkey; @override - String toAddress(BasedUtxoNetwork network) { + String toAddress([BasedUtxoNetwork? network]) { + network ??= this.network; + + if (network == null) { + throw const BitcoinBasePluginException("Network is required"); + } + if (!network.supportedAddress.contains(type)) { - throw BitcoinBasePluginException( - "network does not support ${type.value} address"); + throw BitcoinBasePluginException("network does not support ${type.value} address"); } + return _BitcoinAddressUtils.segwitToAddress( - addressProgram: addressProgram, - network: network, - segwitVersion: segwitVersion); + addressProgram: addressProgram, + network: network, + segwitVersion: segwitVersion, + ); } @override @@ -46,26 +59,32 @@ abstract class SegwitAddress implements BitcoinBaseAddress { } class P2wpkhAddress extends SegwitAddress { - P2wpkhAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) - : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV0, - address: address, - network: network); - - P2wpkhAddress.fromProgram({required String program}) + static RegExp get regex => RegExp(r'(bc|tb|ltc)1q[ac-hj-np-z02-9]{25,39}'); + + P2wpkhAddress.fromAddress({required super.address, required super.network}) + : super.fromAddress(segwitVersion: _BitcoinAddressUtils.segwitV0); + + P2wpkhAddress.fromProgram({required super.program, super.network}) : super.fromProgram( - segwitVersion: _BitcoinAddressUtils.segwitV0, - program: program, - addresType: SegwitAddresType.p2wpkh); - P2wpkhAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + segwitVersion: _BitcoinAddressUtils.segwitV0, + addressType: SegwitAddresType.p2wpkh, + ); + + P2wpkhAddress.fromRedeemScript({required super.script, super.network}) + : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV0); + + factory P2wpkhAddress.fromScriptPubkey({required Script script, BasedUtxoNetwork? network}) { + if (script.getAddressType() != SegwitAddresType.p2wpkh) { + throw ArgumentError("Invalid scriptPubKey"); + } + + return P2wpkhAddress.fromProgram(program: script.findScriptParam(1), network: network); + } /// 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 @@ -74,25 +93,33 @@ class P2wpkhAddress extends SegwitAddress { } 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}) + 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 super.address, required super.network}) + : super.fromAddress(segwitVersion: _BitcoinAddressUtils.segwitV1); + + P2trAddress.fromProgram({required super.program, super.network, super.pubkey}) : super.fromProgram( - segwitVersion: _BitcoinAddressUtils.segwitV1, - program: program, - addresType: SegwitAddresType.p2tr); - P2trAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV1, script: script); + segwitVersion: _BitcoinAddressUtils.segwitV1, + addressType: SegwitAddresType.p2tr, + ); + + P2trAddress.fromRedeemScript({required super.script, super.network}) + : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV1); + + factory P2trAddress.fromScriptPubkey({required Script script, BasedUtxoNetwork? network}) { + if (script.getAddressType() != SegwitAddresType.p2tr) { + throw ArgumentError("Invalid scriptPubKey"); + } + + return P2trAddress.fromProgram(program: script.findScriptParam(1), network: network); + } /// 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 @@ -101,28 +128,87 @@ class P2trAddress extends SegwitAddress { } class P2wshAddress extends SegwitAddress { - P2wshAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) - : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV0, - address: address, - network: network); - P2wshAddress.fromProgram({required String program}) + static RegExp get regex => RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{40,80}'); + + P2wshAddress.fromAddress({required super.address, required super.network}) + : super.fromAddress(segwitVersion: _BitcoinAddressUtils.segwitV0); + + P2wshAddress.fromProgram({required super.program, super.network}) : super.fromProgram( - segwitVersion: _BitcoinAddressUtils.segwitV0, - program: program, - addresType: SegwitAddresType.p2wsh); - P2wshAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + segwitVersion: _BitcoinAddressUtils.segwitV0, + addressType: SegwitAddresType.p2wsh, + ); + + P2wshAddress.fromRedeemScript({required super.script, super.network}) + : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV0); + + factory P2wshAddress.fromScriptPubkey({required Script script, BasedUtxoNetwork? network}) { + if (script.getAddressType() != SegwitAddresType.p2wsh) { + throw ArgumentError("Invalid scriptPubKey"); + } + + return P2wshAddress.fromProgram(program: script.findScriptParam(1), network: network); + } /// 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 @override SegwitAddresType get type => SegwitAddresType.p2wsh; } + +class MwebAddress extends SegwitAddress { + static RegExp get regex => RegExp(r'(ltc|t)mweb1q[ac-hj-np-z02-9]{90,120}'); + + factory MwebAddress.fromAddress({required String address, required BasedUtxoNetwork network}) { + final decoded = Bech32DecoderBase.decodeBech32( + address, + Bech32Const.separator, + Bech32Const.checksumStrLen, + (hrp, data) => Bech32Utils.verifyChecksum(hrp, data, Bech32Encodings.bech32)); + final hrp = decoded.item1; + final data = decoded.item2; + if (hrp != 'ltcmweb') { + throw ArgumentException('Invalid format (HRP not valid, expected ltcmweb, got $hrp)'); + } + if (data[0] != _BitcoinAddressUtils.segwitV0) { + throw const ArgumentException("Invalid segwit version"); + } + final convData = Bech32BaseUtils.convertFromBase32(data.sublist(1)); + if (convData.length != 66) { + throw ArgumentException( + 'Invalid format (witness program length not valid: ${convData.length})'); + } + + return MwebAddress.fromProgram(program: BytesUtils.toHexString(convData)); + } + + MwebAddress.fromProgram({required super.program}) + : super.fromProgram( + segwitVersion: _BitcoinAddressUtils.segwitV0, + addressType: SegwitAddresType.mweb, + ); + MwebAddress.fromRedeemScript({required super.script}) + : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV0); + + factory MwebAddress.fromScriptPubkey({required Script script, type = SegwitAddresType.mweb}) { + if (script.getAddressType() != SegwitAddresType.mweb) { + throw ArgumentError("Invalid scriptPubKey"); + } + return MwebAddress.fromProgram(program: BytesUtils.toHexString(script.script as List)); + } + + /// returns the scriptPubKey of a MWEB witness script + @override + Script toScriptPubKey() { + return Script(script: BytesUtils.fromHexString(addressProgram)); + } + + /// returns the type of address + @override + SegwitAddresType get type => SegwitAddresType.mweb; +} diff --git a/lib/src/bitcoin/address/util.dart b/lib/src/bitcoin/address/util.dart new file mode 100644 index 0000000..7ec257d --- /dev/null +++ b/lib/src/bitcoin/address/util.dart @@ -0,0 +1,44 @@ +import 'package:bitcoin_base/src/utils/utils.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:bitcoin_base/src/bitcoin/address/address.dart'; +import 'package:bitcoin_base/src/models/network.dart'; + +class BitcoinAddressUtils { + static bool validateAddress({required String address, required BasedUtxoNetwork network}) { + try { + addressToOutputScript(address: address, network: network); + return true; + } catch (_) { + return false; + } + } + + static List addressToOutputScript( + {required String address, required BasedUtxoNetwork network}) { + final addressType = RegexUtils.addressTypeFromStr(address, network); + + if (addressType.type == SegwitAddresType.mweb) { + return BytesUtils.fromHexString( + MwebAddress.fromAddress(address: address, network: network).addressProgram, + ); + } + + return addressType.toScriptPubKey().toBytes(); + } + + static String scriptHash(String address, {required BasedUtxoNetwork network}) { + final outputScript = addressToOutputScript(address: address, network: network); + final parts = BytesUtils.toHexString(QuickCrypto.sha256Hash(outputScript)).split(''); + var res = ''; + + for (var i = parts.length - 1; i >= 0; i--) { + final char = parts[i]; + i--; + final nextChar = parts[i]; + res += nextChar; + res += char; + } + + return res; + } +} diff --git a/lib/src/bitcoin/address/utils/address_utils.dart b/lib/src/bitcoin/address/utils/address_utils.dart index f6d2baa..9a27306 100644 --- a/lib/src/bitcoin/address/utils/address_utils.dart +++ b/lib/src/bitcoin/address/utils/address_utils.dart @@ -18,15 +18,13 @@ class _BitcoinAddressUtils { /// /// [address]: The legacy Bitcoin address to be decoded. /// Returns a tuple with script bytes and version if decoding is successful, otherwise null. - static Tuple, List>? decodeLagacyAddress( - {required String address}) { + static Tuple, List>? decodeLagacyAddress({required String address}) { try { /// Decode the base58-encoded address. final decode = List.unmodifiable(Base58Decoder.decode(address)); /// Extract script bytes excluding version and checksum. - final List scriptBytes = - decode.sublist(1, decode.length - Base58Const.checksumByteLen); + final List scriptBytes = decode.sublist(1, decode.length - Base58Const.checksumByteLen); /// Ensure the script bytes have the expected length. if (scriptBytes.length != hash160DigestLength) { @@ -35,14 +33,11 @@ class _BitcoinAddressUtils { /// Extract version, data, and checksum. final List version = [decode[0]]; - List data = - decode.sublist(0, decode.length - Base58Const.checksumByteLen); - List checksum = - decode.sublist(decode.length - Base58Const.checksumByteLen); + List data = decode.sublist(0, decode.length - Base58Const.checksumByteLen); + List checksum = decode.sublist(decode.length - Base58Const.checksumByteLen); /// Verify the checksum. - List hash = QuickCrypto.sha256DoubleHash(data) - .sublist(0, Base58Const.checksumByteLen); + List hash = QuickCrypto.sha256DoubleHash(data).sublist(0, Base58Const.checksumByteLen); if (!BytesUtils.bytesEqual(checksum, hash)) { return null; } @@ -66,9 +61,9 @@ class _BitcoinAddressUtils { } final decodedHex = BytesUtils.toHexString(decode.item1); if (BytesUtils.bytesEqual(decode.item2, networks.p2pkhNetVer)) { - return P2pkhAddress.fromHash160(addrHash: decodedHex); + return P2pkhAddress.fromHash160(h160: decodedHex); } else if (BytesUtils.bytesEqual(decode.item2, networks.p2shNetVer)) { - return P2shAddress.fromHash160(addrHash: decodedHex); + return P2shAddress.fromHash160(h160: decodedHex); } return null; } @@ -83,9 +78,7 @@ class _BitcoinAddressUtils { /// /// Throws a [MessageException] if the witness version does not match the specified version. static String toSegwitProgramWithVersionAndNetwork( - {required String address, - required BasedUtxoNetwork network, - required int version}) { + {required String address, required BasedUtxoNetwork network, required int version}) { final convert = SegwitBech32Decoder.decode(network.p2wpkhHrp, address); final witnessVersion = convert.item1; if (witnessVersion != version) { @@ -102,8 +95,7 @@ class _BitcoinAddressUtils { /// /// Returns a SegwitAddress instance representing the converted SegWit address, /// or null if the conversion is not successful. - static SegwitAddress? toSegwitAddress( - String address, BasedUtxoNetwork network) { + static SegwitAddress? toP2wpkhAddress(String address, BasedUtxoNetwork network) { try { final convert = SegwitBech32Decoder.decode(network.p2wpkhHrp, address); final witnessVersion = convert.item1; @@ -132,8 +124,7 @@ class _BitcoinAddressUtils { /// Returns the validated Bitcoin base address if it belongs to a supported type for the given network. /// /// Throws a [MessageException] if the address type is not supported by the specified network. - static BitcoinBaseAddress validateAddress( - BitcoinBaseAddress address, BasedUtxoNetwork network) { + static BitcoinBaseAddress validateAddress(BitcoinBaseAddress address, BasedUtxoNetwork network) { if (network.supportedAddress.contains(address.type)) { return address; } @@ -150,15 +141,19 @@ class _BitcoinAddressUtils { /// Returns a BitcoinBaseAddress instance representing the decoded address. /// /// Throws a [MessageException] if the address is invalid or not supported by the network. - static BitcoinBaseAddress decodeAddress( - String address, BasedUtxoNetwork network) { + static BitcoinBaseAddress decodeAddress(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) { - throw const BitcoinBasePluginException("Invalid Bitcoin address"); + try { + throw const BitcoinBasePluginException("test2 Bitcoin address"); + } catch (e, s) { + print(s); + } + throw const BitcoinBasePluginException("test Bitcoin address"); } return validateAddress(baseAddress, network); } @@ -171,8 +166,7 @@ class _BitcoinAddressUtils { /// Returns the validated hash160 value if its length matches the expected length for the specified address type. /// /// Throws a [MessageException] if the hash160 value is invalid or has an incorrect length. - static String validateAddressProgram( - String hash160, BitcoinAddressType addressType) { + static String validateAddressProgram(String hash160, BitcoinAddressType addressType) { try { final toBytes = BytesUtils.fromHexString(hash160); if (toBytes.length == addressType.hashLength) { @@ -193,18 +187,15 @@ class _BitcoinAddressUtils { /// /// Returns a LegacyAddress instance representing the decoded BCH address if successful, /// or null if the decoding process fails. - static LegacyAddress? decodeBchAddress( - String address, BitcoinCashNetwork network, + static LegacyAddress? decodeBchAddress(String address, BitcoinCashNetwork network, {bool validateNetworkHRP = false}) { try { - final String hrp = validateNetworkHRP - ? network.networkHRP - : address.substring(0, address.indexOf(":")); + final String hrp = + validateNetworkHRP ? network.networkHRP : address.substring(0, address.indexOf(":")); final decode = BchBech32Decoder.decode(hrp, address); final scriptBytes = decode.item2; final version = decode.item1; - return _validateBchScriptBytes( - network: network, scriptBytes: scriptBytes, version: version); + return _validateBchScriptBytes(network: network, scriptBytes: scriptBytes, version: version); } catch (e) { return null; } @@ -224,8 +215,7 @@ class _BitcoinAddressUtils { required BitcoinCashNetwork network}) { final scriptHex = BytesUtils.toHexString(scriptBytes); final scriptLength = scriptBytes.length; - if (scriptLength != hash160DigestLength && - scriptLength != scriptHashLenght) { + if (scriptLength != hash160DigestLength && scriptLength != scriptHashLenght) { return null; } if (scriptLength == hash160DigestLength) { @@ -234,28 +224,22 @@ class _BitcoinAddressUtils { if (BytesUtils.bytesEqual(network.p2pkhNetVer, version) || BytesUtils.bytesEqual(network.p2pkhWtNetVer, version)) { return P2pkhAddress.fromHash160( - addrHash: scriptHex, - type: - legacyP2pk ? P2pkhAddressType.p2pkh : P2pkhAddressType.p2pkhwt); + h160: scriptHex, type: legacyP2pk ? P2pkhAddressType.p2pkh : P2pkhAddressType.p2pkhwt); } final legacyP2sh = BytesUtils.bytesEqual(network.p2shNetVer, version); if (BytesUtils.bytesEqual(network.p2shNetVer, version) || BytesUtils.bytesEqual(network.p2shwt20NetVer, version)) { return P2shAddress.fromHash160( - addrHash: scriptHex, - type: legacyP2sh - ? P2shAddressType.p2pkhInP2sh - : P2shAddressType.p2pkhInP2shwt); + h160: scriptHex, + type: legacyP2sh ? P2shAddressType.p2pkhInP2sh : P2shAddressType.p2pkhInP2shwt); } } else { final legacyP2sh = BytesUtils.bytesEqual(network.p2sh32NetVer, version); if (BytesUtils.bytesEqual(network.p2sh32NetVer, version) || BytesUtils.bytesEqual(network.p2shwt32NetVer, version)) { return P2shAddress.fromHash160( - addrHash: scriptHex, - type: legacyP2sh - ? P2shAddressType.p2pkhInP2sh32 - : P2shAddressType.p2pkhInP2sh32wt); + h160: scriptHex, + type: legacyP2sh ? P2shAddressType.p2pkhInP2sh32 : P2shAddressType.p2pkhInP2sh32wt); } } return null; @@ -271,7 +255,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}) { @@ -336,8 +320,7 @@ class _BitcoinAddressUtils { required BasedUtxoNetwork network, required int segwitVersion}) { final programBytes = BytesUtils.fromHexString(addressProgram); - return SegwitBech32Encoder.encode( - network.p2wpkhHrp, segwitVersion, programBytes); + return SegwitBech32Encoder.encode(network.p2wpkhHrp, segwitVersion, programBytes); } /// Converts a Bitcoin legacy address program to its corresponding Bitcoin Cash (BCH) address. @@ -352,11 +335,10 @@ class _BitcoinAddressUtils { required String addressProgram, required BitcoinAddressType type}) { List programBytes = BytesUtils.fromHexString(addressProgram); - final List netVersion = _getBchNetVersion( - network: network, type: type, secriptLength: programBytes.length); + final List netVersion = + _getBchNetVersion(network: network, type: type, secriptLength: programBytes.length); - return BchBech32Encoder.encode( - network.networkHRP, netVersion, programBytes); + return BchBech32Encoder.encode(network.networkHRP, netVersion, programBytes); } /// Helper method to obtain the Bitcoin Cash network version bytes based on the address type and script length. @@ -403,8 +385,7 @@ class _BitcoinAddressUtils { required String addressProgram, required BitcoinAddressType type}) { if (network is BitcoinCashNetwork) { - return legacyToBchAddress( - addressProgram: addressProgram, network: network, type: type); + return legacyToBchAddress(addressProgram: addressProgram, network: network, type: type); } List programBytes = BytesUtils.fromHexString(addressProgram); switch (type) { @@ -448,7 +429,7 @@ class _BitcoinAddressUtils { /// Returns the hexadecimal representation of the reversed SHA-256 hash160 of the script's static String pubKeyHash(Script scriptPubKey) { - return BytesUtils.toHexString(List.from( - QuickCrypto.sha256Hash(scriptPubKey.toBytes()).reversed)); + return BytesUtils.toHexString( + List.from(QuickCrypto.sha256Hash(scriptPubKey.toBytes()).reversed)); } } diff --git a/lib/src/bitcoin/script/input.dart b/lib/src/bitcoin/script/input.dart index b55b68a..149d545 100644 --- a/lib/src/bitcoin/script/input.dart +++ b/lib/src/bitcoin/script/input.dart @@ -14,9 +14,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}) { @@ -24,7 +24,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; @@ -35,7 +35,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 @@ -82,7 +82,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/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/output.dart b/lib/src/bitcoin/script/output.dart index 8a25497..09b11e2 100644 --- a/lib/src/bitcoin/script/output.dart +++ b/lib/src/bitcoin/script/output.dart @@ -8,56 +8,58 @@ import 'package:blockchain_utils/utils/utils.dart'; /// [amount] the value we want to send to this output in satoshis /// [scriptPubKey] the script that will lock this amount class TxOutput { - const TxOutput( - {required this.amount, required this.scriptPubKey, this.cashToken}); + const TxOutput({ + required this.amount, + required this.scriptPubKey, + this.cashToken, + this.isSilentPayment = false, + this.isChange = false, + }); final CashToken? cashToken; final BigInt amount; final Script scriptPubKey; + final bool isSilentPayment; + final bool isChange; /// creates a copy of the object TxOutput copy() { return TxOutput( - amount: amount, - scriptPubKey: Script(script: List.from(scriptPubKey.script)), - cashToken: cashToken); + amount: amount, + scriptPubKey: Script(script: List.from(scriptPubKey.script)), + cashToken: cashToken, + isSilentPayment: isSilentPayment, + isChange: isChange, + ); } List toBytes() { - final amountBytes = - BigintUtils.toBytes(amount, length: 8, order: Endian.little); - List scriptBytes = [ - ...cashToken?.toBytes() ?? [], - ...scriptPubKey.toBytes() - ]; - final data = [ - ...amountBytes, - ...IntUtils.encodeVarint(scriptBytes.length), - ...scriptBytes - ]; + final amountBytes = BigintUtils.toBytes(amount, length: 8, order: Endian.little); + List scriptBytes = [...cashToken?.toBytes() ?? [], ...scriptPubKey.toBytes()]; + final data = [...amountBytes, ...IntUtils.encodeVarint(scriptBytes.length), ...scriptBytes]; return data; } static Tuple fromRaw( {required String raw, required int cursor, bool hasSegwit = false}) { final txBytes = BytesUtils.fromHexString(raw); - final value = BigintUtils.fromBytes(txBytes.sublist(cursor, cursor + 8), - byteOrder: Endian.little) - .toSigned(64); + final value = + BigintUtils.fromBytes(txBytes.sublist(cursor, cursor + 8), byteOrder: Endian.little) + .toSigned(64); cursor += 8; final vi = IntUtils.decodeVarint(txBytes.sublist(cursor, cursor + 9)); cursor += vi.item2; final token = CashToken.fromRaw(txBytes.sublist(cursor)); - List lockScript = - txBytes.sublist(cursor + token.item2, cursor + vi.item1); + List lockScript = txBytes.sublist(cursor + token.item2, cursor + vi.item1); cursor += vi.item1; return Tuple( TxOutput( amount: value, cashToken: token.item1, scriptPubKey: Script.fromRaw( - hexData: BytesUtils.toHexString(lockScript), - hasSegwit: hasSegwit)), + hexData: BytesUtils.toHexString(lockScript), + hasSegwit: hasSegwit, + )), cursor); } diff --git a/lib/src/bitcoin/script/script.dart b/lib/src/bitcoin/script/script.dart index 7c46945..47dc269 100644 --- a/lib/src/bitcoin/script/script.dart +++ b/lib/src/bitcoin/script/script.dart @@ -1,4 +1,6 @@ +import 'package:bitcoin_base/src/bitcoin/address/address.dart'; import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; +import 'package:bitcoin_base/src/models/network.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; /// A Script contains just a list of OP_CODES and also knows how to serialize into bytes @@ -16,10 +18,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,41 +33,120 @@ 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; + + if (script.every((x) => x is int) && + script.length == 66 && + (script[0] == 2 || script[0] == 3) && + (script[33] == 2 || script[33] == 3)) { + return SegwitAddresType.mweb; + } + + 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; + } + + String toAddress() { + final addressType = getAddressType(); + if (addressType == null) { + throw ArgumentError("Invalid script"); + } + + switch (addressType) { + case P2pkhAddressType.p2pkh: + return P2pkhAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); + case P2shAddressType.p2pkhInP2sh: + return P2shAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); + case SegwitAddresType.p2wpkh: + return P2wpkhAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); + case SegwitAddresType.p2wsh: + return P2wshAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); + case SegwitAddresType.p2tr: + return P2trAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); + } + + throw ArgumentError("Invalid script"); + } + + /// returns a serialized byte version of the script List toBytes() { if (script.isEmpty) return []; + if (script.every((x) => x is int)) return script.cast(); DynamicByteTracker scriptBytes = DynamicByteTracker(); for (var token in script) { if (BitcoinOpCodeConst.OP_CODES.containsKey(token)) { 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/script/transaction.dart b/lib/src/bitcoin/script/transaction.dart index da9b409..4e53e71 100644 --- a/lib/src/bitcoin/script/transaction.dart +++ b/lib/src/bitcoin/script/transaction.dart @@ -5,6 +5,7 @@ import 'package:bitcoin_base/src/crypto/crypto.dart'; import 'package:bitcoin_base/src/exception/exception.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/crypto/quick_crypto.dart'; +import 'package:collection/collection.dart'; import 'input.dart'; import 'output.dart'; import 'script.dart'; @@ -19,17 +20,18 @@ import 'witness.dart'; /// [hasSegwit] Specifies a tx that includes segwit inputs /// [witnesses] The witness structure that corresponds to the inputs class BtcTransaction { - BtcTransaction( - {required List inputs, - required List outputs, - List witnesses = const [], - this.hasSegwit = false, - List? lock, - List? version}) - : locktime = List.unmodifiable( - lock ?? BitcoinOpCodeConst.DEFAULT_TX_LOCKTIME), - version = List.unmodifiable( - version ?? BitcoinOpCodeConst.DEFAULT_TX_VERSION), + BtcTransaction({ + required List inputs, + required List outputs, + List witnesses = const [], + this.hasSegwit = false, + this.canReplaceByFee = false, + this.mwebBytes, + List? lock, + List? version, + this.hasSilentPayment = false, + }) : locktime = List.unmodifiable(lock ?? BitcoinOpCodeConst.DEFAULT_TX_LOCKTIME), + version = List.unmodifiable(version ?? BitcoinOpCodeConst.DEFAULT_TX_VERSION), inputs = List.unmodifiable(inputs), outputs = List.unmodifiable(outputs), witnesses = List.unmodifiable(witnesses); @@ -38,7 +40,10 @@ class BtcTransaction { final List locktime; final List version; final bool hasSegwit; + final bool canReplaceByFee; final List witnesses; + final List? mwebBytes; + final bool hasSilentPayment; BtcTransaction copyWith({ List? inputs, @@ -53,8 +58,10 @@ class BtcTransaction { outputs: outputs ?? this.outputs, witnesses: witnesses ?? this.witnesses, hasSegwit: hasSegwit ?? this.hasSegwit, + mwebBytes: mwebBytes, lock: lock ?? List.from(locktime), version: version ?? List.from(this.version), + hasSilentPayment: hasSilentPayment, ); } @@ -65,6 +72,7 @@ class BtcTransaction { inputs: tx.inputs.map((e) => e.copy()).toList(), outputs: tx.outputs.map((e) => e.copy()).toList(), witnesses: tx.witnesses.map((e) => e.copy()).toList(), + mwebBytes: tx.mwebBytes, lock: tx.locktime, version: tx.version); } @@ -76,37 +84,49 @@ class BtcTransaction { int cursor = 4; List? flag; bool hasSegwit = false; + bool hasMweb = false; if (rawtx[4] == 0) { flag = List.from(rawtx.sublist(5, 6)); - if (flag[0] == 1) { + if (flag[0] & 1 > 0) { hasSegwit = true; } + if (flag[0] & 8 > 0) { + hasMweb = true; + } cursor += 2; } final vi = IntUtils.decodeVarint(rawtx.sublist(cursor, cursor + 9)); cursor += vi.item2; + bool canReplaceByFee = false; List inputs = []; for (int index = 0; index < vi.item1; index++) { - final inp = - TxInput.fromRaw(raw: raw, hasSegwit: hasSegwit, cursor: cursor); + final inp = TxInput.fromRaw(raw: raw, hasSegwit: hasSegwit, cursor: cursor); - inputs.add(inp.item1); + final input = inp.item1; + inputs.add(input); cursor = inp.item2; + + if (canReplaceByFee == false) { + canReplaceByFee = + const ListEquality().equals(input.sequence, BitcoinOpCodeConst.REPLACE_BY_FEE_SEQUENCE); + } } List outputs = []; final viOut = IntUtils.decodeVarint(rawtx.sublist(cursor, cursor + 9)); cursor += viOut.item2; for (int index = 0; index < viOut.item1; index++) { - final inp = - TxOutput.fromRaw(raw: raw, hasSegwit: hasSegwit, cursor: cursor); + final inp = TxOutput.fromRaw(raw: raw, hasSegwit: hasSegwit, cursor: cursor); outputs.add(inp.item1); cursor = inp.item2; } List witnesses = []; if (hasSegwit) { for (int n = 0; n < inputs.length; n++) { + final input = inputs[n]; + if (input.scriptSig.script.isNotEmpty) continue; + final wVi = IntUtils.decodeVarint(rawtx.sublist(cursor, cursor + 9)); cursor += wVi.item2; List witnessesTmp = []; @@ -114,8 +134,7 @@ class BtcTransaction { List witness = []; final wtVi = IntUtils.decodeVarint(rawtx.sublist(cursor, cursor + 9)); if (wtVi.item1 != 0) { - witness = rawtx.sublist( - cursor + wtVi.item2, cursor + wtVi.item1 + wtVi.item2); + witness = rawtx.sublist(cursor + wtVi.item2, cursor + wtVi.item1 + wtVi.item2); } cursor += wtVi.item1 + wtVi.item2; witnessesTmp.add(BytesUtils.toHexString(witness)); @@ -124,14 +143,22 @@ class BtcTransaction { witnesses.add(TxWitnessInput(stack: witnessesTmp)); } } + List? mwebBytes; + if (hasMweb) { + mwebBytes = rawtx.sublist(cursor, rawtx.length - 4); + } + cursor = rawtx.length - 4; List lock = rawtx.sublist(cursor, cursor + 4); return BtcTransaction( - inputs: inputs, - outputs: outputs, - witnesses: witnesses, - hasSegwit: hasSegwit, - version: version, - lock: lock); + inputs: inputs, + outputs: outputs, + witnesses: witnesses, + hasSegwit: hasSegwit, + canReplaceByFee: canReplaceByFee, + mwebBytes: mwebBytes, + version: version, + lock: lock, + ); } /// returns the transaction input's digest that is to be signed according. @@ -153,8 +180,7 @@ class BtcTransaction { tx = tx.copyWith(outputs: []); for (int i = 0; i < tx.inputs.length; i++) { if (i != txInIndex) { - tx.inputs[i].sequence = - List.unmodifiable(BitcoinOpCodeConst.EMPTY_TX_SEQUENCE); + tx.inputs[i].sequence = List.unmodifiable(BitcoinOpCodeConst.EMPTY_TX_SEQUENCE); } } } else if ((sighash & 0x1f) == BitcoinOpCodeConst.SIGHASH_SINGLE) { @@ -172,8 +198,7 @@ class BtcTransaction { tx = tx.copyWith(outputs: [...outputs, tx.outputs[txInIndex]]); for (int i = 0; i < tx.inputs.length; i++) { if (i != txInIndex) { - tx.inputs[i].sequence = - List.unmodifiable(BitcoinOpCodeConst.EMPTY_TX_SEQUENCE); + tx.inputs[i].sequence = List.unmodifiable(BitcoinOpCodeConst.EMPTY_TX_SEQUENCE); } } } @@ -182,10 +207,8 @@ class BtcTransaction { } List txForSign = tx.toBytes(segwit: false); - txForSign = List.from([ - ...txForSign, - ...IntUtils.toBytes(sighash, length: 4, byteOrder: Endian.little) - ]); + txForSign = List.from( + [...txForSign, ...IntUtils.toBytes(sighash, length: 4, byteOrder: Endian.little)]); return QuickCrypto.sha256DoubleHash(txForSign); } @@ -193,8 +216,11 @@ class BtcTransaction { List toBytes({bool segwit = false}) { DynamicByteTracker data = DynamicByteTracker(); data.add(version); - if (segwit) { - data.add([0x00, 0x01]); + var flag = 0; + if (segwit) flag |= 1; + if (mwebBytes != null) flag |= 8; + if (flag > 0) { + data.add([0x00, flag]); } final txInCountBytes = IntUtils.encodeVarint(inputs.length); final txOutCountBytes = IntUtils.encodeVarint(outputs.length); @@ -214,6 +240,9 @@ class BtcTransaction { data.add(wit.toBytes()); } } + if (mwebBytes != null) { + data.add(mwebBytes!); + } data.add(locktime); return data.toBytes(); } @@ -235,15 +264,13 @@ class BtcTransaction { List hashSequence = List.filled(32, 0); List hashOutputs = List.filled(32, 0); int basicSigHashType = sighash & 0x1F; - bool anyoneCanPay = - (sighash & 0xF0) == BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; + bool anyoneCanPay = (sighash & 0xF0) == BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; bool signAll = (basicSigHashType != BitcoinOpCodeConst.SIGHASH_SINGLE) && (basicSigHashType != BitcoinOpCodeConst.SIGHASH_NONE); if (!anyoneCanPay) { hashPrevouts = []; for (final txin in tx.inputs) { - List txidBytes = List.from( - BytesUtils.fromHexString(txin.txId).reversed.toList()); + List txidBytes = List.from(BytesUtils.fromHexString(txin.txId).reversed.toList()); hashPrevouts = List.from([ ...hashPrevouts, ...txidBytes, @@ -269,12 +296,10 @@ class BtcTransaction { } else if (basicSigHashType == BitcoinOpCodeConst.SIGHASH_SINGLE && txInIndex < tx.outputs.length) { final out = tx.outputs[txInIndex]; - List packedAmount = - BigintUtils.toBytes(out.amount, length: 8, order: Endian.little); + List packedAmount = BigintUtils.toBytes(out.amount, length: 8, order: Endian.little); final scriptBytes = out.scriptPubKey.toBytes(); List lenScriptBytes = List.from([scriptBytes.length]); - hashOutputs = - List.from([...packedAmount, ...lenScriptBytes, ...scriptBytes]); + hashOutputs = List.from([...packedAmount, ...lenScriptBytes, ...scriptBytes]); hashOutputs = QuickCrypto.sha256DoubleHash(hashOutputs); } @@ -284,26 +309,21 @@ class BtcTransaction { txForSigning.add(hashSequence); final txIn = inputs[txInIndex]; - List txidBytes = - List.from(BytesUtils.fromHexString(txIn.txId).reversed.toList()); - txForSigning.add(List.from([ - ...txidBytes, - ...IntUtils.toBytes(txIn.txIndex, length: 4, byteOrder: Endian.little) - ])); + List txidBytes = List.from(BytesUtils.fromHexString(txIn.txId).reversed.toList()); + txForSigning.add(List.from( + [...txidBytes, ...IntUtils.toBytes(txIn.txIndex, length: 4, byteOrder: Endian.little)])); if (token != null) { txForSigning.add(token.toBytes()); } txForSigning.add(List.from([script.toBytes().length])); txForSigning.add(script.toBytes()); - List packedAmount = - BigintUtils.toBytes(amount, length: 8, order: Endian.little); + List packedAmount = BigintUtils.toBytes(amount, length: 8, order: Endian.little); txForSigning.add(packedAmount); txForSigning.add(txIn.sequence); txForSigning.add(hashOutputs); txForSigning.add(locktime); - txForSigning - .add(IntUtils.toBytes(sighash, length: 4, byteOrder: Endian.little)); + txForSigning.add(IntUtils.toBytes(sighash, length: 4, byteOrder: Endian.little)); return QuickCrypto.sha256DoubleHash(txForSigning.toBytes()); } @@ -328,8 +348,7 @@ class BtcTransaction { final newTx = copy(this); bool sighashNone = (sighash & 0x03) == BitcoinOpCodeConst.SIGHASH_NONE; bool sighashSingle = (sighash & 0x03) == BitcoinOpCodeConst.SIGHASH_SINGLE; - bool anyoneCanPay = - (sighash & 0x80) == BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; + bool anyoneCanPay = (sighash & 0x80) == BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; DynamicByteTracker txForSign = DynamicByteTracker(); txForSign.add([0]); txForSign.add([sighash]); @@ -342,8 +361,7 @@ class BtcTransaction { List hashOutputs = []; if (!anyoneCanPay) { for (final txin in newTx.inputs) { - List txidBytes = List.from( - BytesUtils.fromHexString(txin.txId).reversed.toList()); + List txidBytes = List.from(BytesUtils.fromHexString(txin.txId).reversed.toList()); hashPrevouts = List.from([ ...hashPrevouts, ...txidBytes, @@ -354,8 +372,7 @@ class BtcTransaction { txForSign.add(hashPrevouts); for (final i in amounts) { - List bytes = - BigintUtils.toBytes(i, length: 8, order: Endian.little); + List bytes = BigintUtils.toBytes(i, length: 8, order: Endian.little); hashAmounts = List.from([...hashAmounts, ...bytes]); } @@ -369,8 +386,7 @@ class BtcTransaction { int scriptLen = h.length ~/ 2; List scriptBytes = BytesUtils.fromHexString(h); List lenBytes = List.from([scriptLen]); - hashScriptPubkeys = - List.from([...hashScriptPubkeys, ...lenBytes, ...scriptBytes]); + hashScriptPubkeys = List.from([...hashScriptPubkeys, ...lenBytes, ...scriptBytes]); } hashScriptPubkeys = QuickCrypto.sha256Hash(hashScriptPubkeys); txForSign.add(hashScriptPubkeys); @@ -383,17 +399,12 @@ class BtcTransaction { } if (!(sighashNone || sighashSingle)) { for (final txOut in newTx.outputs) { - List packedAmount = - BigintUtils.toBytes(txOut.amount, length: 8, order: Endian.little); + List packedAmount = BigintUtils.toBytes(txOut.amount, length: 8, order: Endian.little); List scriptBytes = txOut.scriptPubKey.toBytes(); final lenScriptBytes = List.from([scriptBytes.length]); - hashOutputs = List.from([ - ...hashOutputs, - ...packedAmount, - ...lenScriptBytes, - ...scriptBytes - ]); + hashOutputs = + List.from([...hashOutputs, ...packedAmount, ...lenScriptBytes, ...scriptBytes]); } hashOutputs = QuickCrypto.sha256Hash(hashOutputs); txForSign.add(hashOutputs); @@ -404,15 +415,11 @@ class BtcTransaction { if (anyoneCanPay) { final txin = newTx.inputs[txIndex]; - List txidBytes = - List.from(BytesUtils.fromHexString(txin.txId).reversed.toList()); - List result = List.from([ - ...txidBytes, - ...IntUtils.toBytes(txin.txIndex, length: 4, byteOrder: Endian.little) - ]); + List txidBytes = List.from(BytesUtils.fromHexString(txin.txId).reversed.toList()); + List result = List.from( + [...txidBytes, ...IntUtils.toBytes(txin.txIndex, length: 4, byteOrder: Endian.little)]); txForSign.add(result); - txForSign.add(BigintUtils.toBytes(amounts[txIndex], - length: 8, order: Endian.little)); + txForSign.add(BigintUtils.toBytes(amounts[txIndex], length: 8, order: Endian.little)); final sPubKey = scriptPubKeys[txIndex].toHex(); final sLength = sPubKey.length ~/ 2; txForSign.add([sLength]); @@ -427,18 +434,16 @@ class BtcTransaction { if (sighashSingle) { final txOut = newTx.outputs[txIndex]; - List packedAmount = - BigintUtils.toBytes(txOut.amount, length: 8, order: Endian.little); + List packedAmount = BigintUtils.toBytes(txOut.amount, length: 8, order: Endian.little); final sBytes = txOut.scriptPubKey.toBytes(); List lenScriptBytes = List.from([sBytes.length]); - final hashOut = - List.from([...packedAmount, ...lenScriptBytes, ...sBytes]); + final hashOut = List.from([...packedAmount, ...lenScriptBytes, ...sBytes]); txForSign.add(QuickCrypto.sha256Hash(hashOut)); } if (extFlags == 1) { - final leafVarBytes = List.from( - [leafVar, ...IntUtils.prependVarint(script?.toBytes() ?? [])]); + final leafVarBytes = + List.from([leafVar, ...IntUtils.prependVarint(script?.toBytes() ?? [])]); txForSign.add(taggedHash(leafVarBytes, "TapLeaf")); txForSign.add([0]); txForSign.add(List.filled(4, mask8)); diff --git a/lib/src/bitcoin/script/witness.dart b/lib/src/bitcoin/script/witness.dart index 86b0db5..88c7aca 100644 --- a/lib/src/bitcoin/script/witness.dart +++ b/lib/src/bitcoin/script/witness.dart @@ -1,12 +1,27 @@ +import 'dart:typed_data'; + import 'package:blockchain_utils/utils/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() { @@ -18,8 +33,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..e622851 --- /dev/null +++ b/lib/src/bitcoin/silent_payments/address.dart @@ -0,0 +1,202 @@ +// 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 { + final ECPrivate b_scan; + final ECPrivate b_spend; + + SilentPaymentOwner({ + required super.version, + required super.B_scan, + required super.B_spend, + required this.b_scan, + required this.b_spend, + super.network, + }) : super(); + + factory SilentPaymentOwner.fromPrivateKeys({ + required ECPrivate b_scan, + required ECPrivate b_spend, + required BasedUtxoNetwork network, + int? version, + }) { + return SilentPaymentOwner( + b_scan: b_scan, + b_spend: b_spend, + B_scan: b_scan.getPublic(), + B_spend: b_spend.getPublic(), + network: network, + 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.fromBip32(scanDerivation.publicKey), + B_spend: ECPublic.fromBip32(spendDerivation.publicKey), + network: hrp == "tsp" ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet, + 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); + } + + List generateLabel(int m) { + return taggedHash(BytesUtils.concatBytes([b_scan.toBytes(), serUint32(m)]), "BIP0352/Label"); + } + + SilentPaymentOwner toLabeledSilentPaymentAddress(int 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, + network: network, + version: version, + ); + } +} + +class SilentPaymentDestination extends SilentPaymentAddress { + SilentPaymentDestination({ + required super.version, + required ECPublic scanPubkey, + required ECPublic spendPubkey, + super.network, + required this.amount, + }) : super(B_scan: scanPubkey, B_spend: spendPubkey); + + int amount; + + factory SilentPaymentDestination.fromAddress(String address, int amount) { + final receiver = SilentPaymentAddress.fromAddress(address); + + return SilentPaymentDestination( + scanPubkey: receiver.B_scan, + spendPubkey: receiver.B_spend, + network: receiver.network, + version: receiver.version, + amount: amount, + ); + } +} + +class SilentPaymentAddress implements BitcoinBaseAddress { + static RegExp get regex => RegExp(r'(tsp|sp|sprt)1[0-9a-zA-Z]{113}'); + + final int version; + final ECPublic B_scan; + final ECPublic B_spend; + @override + BasedUtxoNetwork? network; + final String hrp; + + SilentPaymentAddress({ + required this.B_scan, + required this.B_spend, + this.network = BitcoinNetwork.mainnet, + this.version = 0, + }) : 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 = 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)), + network: prefix == 'tsp' ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet, + version: version, + ); + } + + @override + String toAddress([BasedUtxoNetwork? network]) { + return toString(network: network); + } + + @override + String toString({BasedUtxoNetwork? network}) { + return Bech32EncoderBase.encodeBech32( + hrp, + [ + version, + ...Bech32BaseUtils.convertToBase32( + [...B_scan.toCompressedBytes(), ...B_spend.toCompressedBytes()]) + ], + SegwitBech32Const.separator, + (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 { + 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..c04cf12 --- /dev/null +++ b/lib/src/bitcoin/silent_payments/payment.dart @@ -0,0 +1,242 @@ +// 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 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; + final bool tweak; + + ECPrivateInfo(this.privkey, this.isTaproot, {this.tweak = false}); +} + +class SilentPaymentBuilder { + final List vinOutpoints; + final List? pubkeys; + ECPublic? A_sum; + List? inputHash; + String? receiverTweak; + + SilentPaymentBuilder({ + required this.vinOutpoints, + this.pubkeys, + this.receiverTweak, + }) { + if (receiverTweak == null && pubkeys != null) { + _getAsum(); + _getInputHash(); + } + } + + void _getAsum() { + final head = pubkeys!.first; + final tail = pubkeys!.sublist(1); + + A_sum = + tail.fold(head, (acc, item) => ECPublic.fromBip32(acc.publicKey).pubkeyAdd(item)); + } + + void _getInputHash() { + final sortedOutpoints = >[]; + + 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) + ])); + } + + 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) { + var k = info.privkey; + final isTaproot = info.isTaproot; + + if (isTaproot) { + if (info.tweak) { + k = k.toTweakedTaprootKey(); + } + + final xOnlyPubkey = k.getPublic(); + final isOdd = xOnlyPubkey.publicKey.point.y % BigInt.two != BigInt.zero; + + if (isOdd) { + k = k.negate(); + } + } + + if (a_sum == null) { + a_sum = k; + } else { + a_sum = a_sum.tweakAdd(BigintUtils.fromBytes(k.toBytes())); + } + } + + A_sum = a_sum!.getPublic(); + _getInputHash(); + + Map>> silentPaymentGroups = {}; + + for (final silentPaymentDestination in silentPaymentDestinations) { + final B_scan = silentPaymentDestination.B_scan; + final scanPubkey = B_scan.toHex(); + + if (silentPaymentGroups.containsKey(scanPubkey)) { + // Current key already in silentPaymentGroups, simply add up the new destination + // with the already calculated ecdhSharedSecret + final group = silentPaymentGroups[scanPubkey]!; + final ecdhSharedSecret = group.keys.first; + final recipients = group.values.first; + + silentPaymentGroups[scanPubkey] = { + ecdhSharedSecret: [...recipients, silentPaymentDestination] + }; + } else { + final senderPartialSecret = a_sum.tweakMul(BigintUtils.fromBytes(inputHash!)).toBytes(); + final ecdhSharedSecret = + B_scan.tweakMul(BigintUtils.fromBytes(senderPartialSecret)).toHex(); + + silentPaymentGroups[scanPubkey] = { + ecdhSharedSecret: [silentPaymentDestination] + }; + } + } + + 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 = 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(resOutput); + } else { + result[destination.toString()] = [resOutput]; + } + + k++; + } + } + + return result; + } + + Map scanOutputs( + ECPrivate b_scan, + ECPublic B_spend, + List outputsToCheck, { + Map? precomputedLabels, + }) { + final tweakDataForRecipient = receiverTweak != null + ? ECPublic.fromHex(receiverTweak!) + : A_sum!.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.tweakAdd(BigintUtils.fromBytes(t_k)); + final length = outputsToCheck.length; + + for (var i = 0; i < length; i++) { + 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.fromBytes(output).pubkeyAdd(P_k.negate()); + var m_G = precomputedLabels[m_G_sub.toHex()]; + + if (m_G == null) { + 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.tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))); + + matches[outputPubkey] = SilentPaymentScanningOutput( + output: SilentPaymentOutput(P_km.toTaprootAddress(tweak: false), outputAmount), + tweak: ECPrivate.fromBytes(t_k) + .tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(m_G))) + .toHex(), + label: m_G, + ); + + outputsToCheck.removeAt(i); + k++; + break; + } + } + + outputsToCheck.removeAt(i); + + if (i + 1 >= outputsToCheck.length) { + break; + } + } + } while (outputsToCheck.isNotEmpty); + + 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 new file mode 100644 index 0000000..d839c6b --- /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/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'; +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..1f68c07 --- /dev/null +++ b/lib/src/bitcoin/silent_payments/utils.dart @@ -0,0 +1,112 @@ +// 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); +} diff --git a/lib/src/crypto/keypair/ec_private.dart b/lib/src/crypto/keypair/ec_private.dart index ee1b212..e8ace7a 100644 --- a/lib/src/crypto/keypair/ec_private.dart +++ b/lib/src/crypto/keypair/ec_private.dart @@ -1,5 +1,11 @@ +import 'dart:typed_data'; + import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:pointycastle/export.dart'; +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip32/src/utils/ecurve.dart' as ecc; /// Represents an ECDSA private key. class ECPrivate { @@ -13,28 +19,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 +47,44 @@ 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'}) { - final btcSigner = BitcoinSigner.fromKeyBytes(toBytes()); - final signature = btcSigner.signMessage(message, messagePrefix); - return BytesUtils.toHexString(signature); + String signMessage(List message, {String messagePrefix = '\x18Bitcoin Signed Message:\n'}) { + + final messageHash = + QuickCrypto.sha256Hash(BitcoinSignerUtils.magicMessage(message, messagePrefix)); + + final messageHashBytes = Uint8List.fromList(messageHash); + final privBytes = Uint8List.fromList(prive.raw); + final rs = ecc.sign(messageHashBytes, privBytes); + final rawSig = rs.toECSignature(); + + final pub = prive.publicKey; + final ECDomainParameters curve = ECCurve_secp256k1(); + final point = curve.curve.decodePoint(pub.point.toBytes()); + + final recId = SignUtils.findRecoveryId( + SignUtils.getHexString(messageHash, offset: 0, length: messageHash.length), + rawSig, + Uint8List.fromList(pub.uncompressed), + ); + + final v = recId + 27 + (point!.isCompressed ? 4 : 0); + + final combined = Uint8List.fromList([v, ...rs]); + + return BytesUtils.toHexString(combined); } /// 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,20 +103,52 @@ 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]; } 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); } + + 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 03f85b2..18a5ec4 100644 --- a/lib/src/crypto/keypair/ec_public.dart +++ b/lib/src/crypto/keypair/ec_public.dart @@ -1,7 +1,9 @@ import 'package:bitcoin_base/src/bitcoin/address/address.dart'; import 'package:bitcoin_base/src/bitcoin/script/script.dart'; import 'package:bitcoin_base/src/exception/exception.dart'; +import 'package:bitcoin_base/src/models/network.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:blockchain_utils/crypto/crypto/cdsa/point/base.dart'; class ECPublic { final Bip32PublicKey publicKey; @@ -9,16 +11,15 @@ class ECPublic { factory ECPublic.fromBip32(Bip32PublicKey publicKey) { if (publicKey.curveType != EllipticCurveTypes.secp256k1) { - throw const BitcoinBasePluginException( - "invalid public key curve for bitcoin"); + throw const BitcoinBasePluginException("invalid public key curve for bitcoin"); } return ECPublic._(publicKey); } /// 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); } @@ -50,17 +51,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); @@ -70,8 +71,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. @@ -85,67 +85,56 @@ 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); - return P2trAddress.fromProgram(program: pubKey); + P2trAddress toTaprootAddress({List>? scripts, bool tweak = true}) { + final pubKey = toTapRotHex(script: scripts, tweak: tweak); + return P2trAddress.fromProgram(program: pubKey, pubkey: ECPublic.fromHex(pubKey)); } /// toP2wpkhInP2sh generates a P2SH (Pay-to-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. 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); } /// 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 @@ -153,10 +142,28 @@ 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); } + bool compareToAddress(BitcoinBaseAddress other, BasedUtxoNetwork network) { + late BitcoinBaseAddress address; + + if (other is P2pkAddress) { + address = toP2pkAddress(); + } else if (other is P2pkhAddress) { + address = toP2pkhAddress(); + } else if (other is P2wpkhAddress) { + address = toP2wpkhAddress(); + } else if (other is P2wshAddress) { + address = toP2wshAddress(); + } else if (other is P2trAddress) { + address = toTaprootAddress(); + } + + return address.toAddress(network) == other.toAddress(network); + } + /// toBytes returns the uncompressed byte representation of the ECPublic key. List toBytes({bool whitPrefix = true}) { if (!whitPrefix) { @@ -170,15 +177,20 @@ 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}) { - 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 @@ -207,4 +219,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/lib/src/crypto/keypair/sign_utils.dart b/lib/src/crypto/keypair/sign_utils.dart new file mode 100644 index 0000000..0b3f226 --- /dev/null +++ b/lib/src/crypto/keypair/sign_utils.dart @@ -0,0 +1,190 @@ +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:pointycastle/export.dart'; +import 'package:pointycastle/src/utils.dart'; +import 'package:pointycastle/ecc/ecc_fp.dart' as fp; + +final ECDomainParameters curve = ECCurve_secp256k1(); + +extension ECUtils on Uint8List { + ECSignature toECSignature() { + final sigLength = (this.length / 2).round(); + final r = BigInt.parse( + SignUtils.getHexString(this, offset: 0, length: sigLength), + radix: 16, + ); + final s = BigInt.parse( + SignUtils.getHexString(this, offset: sigLength, length: sigLength), + radix: 16, + ); + return ECSignature(r, s); + } + + bool isCompressedPoint() => curve.curve.decodePoint(this)!.isCompressed; +} + +class SignUtils { + /// Returns the recovery ID, a byte with value between 0 and 3, inclusive, that specifies which of 4 possible + /// curve points was used to sign a message. This value is also referred to as "v". + /// + /// @throws RuntimeException if no recovery ID can be found. + static int findRecoveryId(String hash, ECSignature sig, Uint8List pub) { + var recId = -1; + final Q = curve.curve.decodePoint(pub); + for (var i = 0; i < 4; i++) { + final k = recoverFromSignature(i, sig, hash); + if (k != null && k == Q) { + recId = i; + break; + } + } + if (recId == -1) { + throw Exception("Could not construct a recoverable key. This should never happen."); + } + return recId; + } + + static String getHexString( + List list, { + required int offset, + required int length, + }) { + final sublist = list.getRange(offset, offset + length); + return [for (var byte in sublist) byte.toRadixString(16).padLeft(2, '0').toUpperCase()].join(); + } + + ///

Given the components of a signature and a selector value, recover and return the public key + /// that generated the signature according to the algorithm in SEC1v2 section 4.1.6.

+ /// + ///

The recId is an index from 0 to 3 which indicates which of the 4 possible keys is the correct one. Because + /// the key recovery operation yields multiple potential keys, the correct key must either be stored alongside the + /// signature, or you must be willing to try each recId in turn until you find one that outputs the key you are + /// expecting.

+ /// + ///

If this method returns null it means recovery was not possible and recId should be iterated.

+ /// + ///

Given the above two points, a correct usage of this method is inside a for loop from 0 to 3, and if the + /// output is null OR a key that is not the one you expect, you try again with the next recId.

+ /// + /// @param recId Which possible key to recover. + /// @param sig the R and S components of the signature, wrapped. + /// @param message Hash of the data that was signed. + /// @param compressed Whether or not the original pubkey was compressed. + /// @return An ECKey containing only the public part, or null if recovery wasn't possible. + static ECPoint? recoverFromSignature(int recId, ECSignature sig, String message) { + // see https://www.secg.org/sec1-v2.pdf, section 4.1.6 + // 1.0 For j from 0 to h (h == recId here and the loop is outside this function) + // 1.1 Let x = r + jn + final n = curve.n; // Curve order. + final i = BigInt.from(recId / 2); + final x = sig.r + (i * n); + // 1.2. Convert the integer x to an octet string X of length mlen using the conversion routine + // specified in Section 2.3.7, where mlen = ⌈(log2 p)/8⌉ or mlen = ⌈m/8⌉. + // 1.3. Convert the octet string (16 set binary digits)||X to an elliptic curve point R using the + // conversion routine specified in Section 2.3.4. If this conversion routine outputs "invalid", then + // do another iteration of Step 1. + // + // More concisely, what these points mean is to use X as a compressed public key. + final prime = (curve.curve as fp.ECCurve).q!; + if (x.compareTo(prime) >= 0) { + // Cannot have point co-ordinates larger than this as everything takes place modulo Q. + return null; + } + // Compressed keys require you to know an extra bit of data about the y-coord as there are two possibilities. + // So it's encoded in the recId. + final R = _decompressKey(x, (recId & 1) == 1); + // 1.4. If nR != point at infinity, then do another iteration of Step 1 (callers responsibility). + if (!(R * n)!.isInfinity) return null; + // 1.5. Compute e from M using Steps 2 and 3 of ECDSA signature verification. + final e = BigInt.parse(message, radix: 16); + // 1.6. For k from 1 to 2 do the following. (loop is outside this function via iterating recId) + // 1.6.1. Compute a candidate public key as: + // Q = mi(r) * (sR - eG) + // + // Where mi(x) is the modular multiplicative inverse. We transform this into the following: + // Q = (mi(r) * s ** R) + (mi(r) * -e ** G) + // Where -e is the modular additive inverse of e, that is z such that z + e = 0 (mod n). In the above equation + // ** is point multiplication and + is point addition (the EC group operator). + // + // We can find the additive inverse by subtracting e from zero then taking the mod. For example the additive + // inverse of 3 modulo 11 is 8 because 3 + 8 mod 11 = 0, and -3 mod 11 = 8. + final eInv = (BigInt.zero - e) % n; + final rInv = sig.r.modInverse(n); + final srInv = (rInv * sig.s) % n; + final eInvrInv = (rInv * eInv) % n; + return sumOfTwoMultiplies(curve.G, eInvrInv, R, srInv)!; + } + + /// Decompress a compressed public key (x co-ord and low-bit of y-coord). + static ECPoint _decompressKey(BigInt xBN, bool yBit) { + final curveByteLength = ((curve.curve.fieldSize + 7) ~/ 8); + final compEnc = _x9IntegerToBytes(xBN, 1 + curveByteLength); + compEnc[0] = (yBit ? 0x03 : 0x02); + return curve.curve.decodePoint(compEnc)!; + } + +// Extracted from pointycastle/lib/ecc/ecc_fp.dart + static Uint8List _x9IntegerToBytes(BigInt? s, int qLength) { + var bytes = Uint8List.fromList(encodeBigInt(s)); + + if (qLength < bytes.length) { + return bytes.sublist(bytes.length - qLength); + } else if (qLength > bytes.length) { + return Uint8List(qLength)..setAll(qLength - bytes.length, bytes); + } + + return bytes; + } + + // Extracted from pointycastle/lib/signers/ecdsa_signer.dart + static ECPoint? sumOfTwoMultiplies(ECPoint P, BigInt a, ECPoint Q, BigInt b) { + var c = P.curve; + + if (c != Q.curve) { + throw ArgumentError('P and Q must be on same curve'); + } + + // Point multiplication for Koblitz curves (using WTNAF) beats Shamir's trick + // TODO: uncomment this when F2m available + /* + if( c is ECCurve.F2m ) { + ECCurve.F2m f2mCurve = (ECCurve.F2m)c; + if( f2mCurve.isKoblitz() ) { + return P.multiply(a).add(Q.multiply(b)); + } + } + */ + + return _implShamirsTrick(P, a, Q, b); + } + + // Extracted from pointycastle/lib/signers/ecdsa_signer.dart + static ECPoint? _implShamirsTrick(ECPoint P, BigInt k, ECPoint Q, BigInt l) { + var m = max(k.bitLength, l.bitLength); + + var Z = P + Q; + var R = P.curve.infinity; + + for (var i = m - 1; i >= 0; --i) { + R = R!.twice(); + + if (_testBit(k, i)) { + if (_testBit(l, i)) { + R = R! + Z; + } else { + R = R! + P; + } + } else { + if (_testBit(l, i)) { + R = R! + Q; + } + } + } + + return R; + } + + // Extracted from pointycastle/lib/signers/ecdsa_signer.dart + static bool _testBit(BigInt i, int n) => (i & (BigInt.one << n)) != BigInt.zero; +} diff --git a/lib/src/models/network.dart b/lib/src/models/network.dart index 89e32b3..adeaafc 100644 --- a/lib/src/models/network.dart +++ b/lib/src/models/network.dart @@ -27,9 +27,7 @@ abstract class BasedUtxoNetwork implements Enumerate { @override operator ==(other) { if (identical(other, this)) return true; - return other is BasedUtxoNetwork && - other.runtimeType == runtimeType && - value == other.value; + return other is BasedUtxoNetwork && other.runtimeType == runtimeType && value == other.value; } @override @@ -103,8 +101,7 @@ class BitcoinSVNetwork implements BasedUtxoNetwork { bool get isMainnet => this == BitcoinSVNetwork.mainnet; @override - List get supportedAddress => - [P2pkhAddressType.p2pkh, PubKeyAddressType.p2pk]; + List get supportedAddress => [P2pkhAddressType.p2pkh, PubKeyAddressType.p2pk]; @override List get coins { @@ -232,6 +229,7 @@ class LitecoinNetwork implements BasedUtxoNetwork { SegwitAddresType.p2wpkh, PubKeyAddressType.p2pk, SegwitAddresType.p2wsh, + SegwitAddresType.mweb, P2shAddressType.p2wshInP2sh, P2shAddressType.p2wpkhInP2sh, P2shAddressType.p2pkhInP2sh, @@ -243,23 +241,17 @@ class LitecoinNetwork implements BasedUtxoNetwork { if (isMainnet) { return [Bip44Coins.litecoin, Bip49Coins.litecoin, Bip84Coins.litecoin]; } - return [ - Bip44Coins.litecoinTestnet, - Bip49Coins.litecoinTestnet, - Bip84Coins.litecoinTestnet - ]; + return [Bip44Coins.litecoinTestnet, Bip49Coins.litecoinTestnet, Bip84Coins.litecoinTestnet]; } } /// Class representing a Dash network, implementing the `BasedUtxoNetwork` abstract class. class DashNetwork implements BasedUtxoNetwork { /// Mainnet configuration with associated `CoinConf`. - static const DashNetwork mainnet = - DashNetwork._("dashMainnet", CoinsConf.dashMainNet); + static const DashNetwork mainnet = DashNetwork._("dashMainnet", CoinsConf.dashMainNet); /// Testnet configuration with associated `CoinConf`. - static const DashNetwork testnet = - DashNetwork._("dashTestnet", CoinsConf.dashTestNet); + static const DashNetwork testnet = DashNetwork._("dashTestnet", CoinsConf.dashTestNet); /// Overrides the `conf` property from `BasedUtxoNetwork` with the associated `CoinConf`. @override @@ -282,8 +274,8 @@ class DashNetwork implements BasedUtxoNetwork { /// Retrieves the Human-Readable Part (HRP) for Pay-to-Witness-Public-Key-Hash (P2WPKH) addresses. @override - String get p2wpkhHrp => throw const BitcoinBasePluginException( - "DashNetwork network does not support P2WPKH/P2WSH"); + String get p2wpkhHrp => + throw const BitcoinBasePluginException("DashNetwork network does not support P2WPKH/P2WSH"); /// Checks if the current network is the mainnet. @override @@ -409,8 +401,8 @@ class BitcoinCashNetwork implements BasedUtxoNetwork { /// Retrieves the Human-Readable Part (HRP) for Pay-to-Witness-Public-Key-Hash (P2WPKH) addresses /// from the associated `CoinConf`. @override - String get p2wpkhHrp => throw const BitcoinBasePluginException( - "network does not support p2wpkh HRP"); + String get p2wpkhHrp => + throw const BitcoinBasePluginException("network does not support p2wpkh HRP"); String get networkHRP => conf.params.p2pkhStdHrp!; @@ -443,8 +435,7 @@ class BitcoinCashNetwork implements BasedUtxoNetwork { /// Class representing a Dogecoin network, implementing the `BasedUtxoNetwork` abstract class. class PepeNetwork implements BasedUtxoNetwork { /// Mainnet configuration with associated `CoinConf`. - static const PepeNetwork mainnet = - PepeNetwork._("pepecoinMainnet", CoinsConf.pepeMainnet); + static const PepeNetwork mainnet = PepeNetwork._("pepecoinMainnet", CoinsConf.pepeMainnet); /// Overrides the `conf` property from `BasedUtxoNetwork` with the associated `CoinConf`. @override diff --git a/lib/src/provider/api_provider/api_provider.dart b/lib/src/provider/api_provider/api_provider.dart index 1dc5c81..3f470df 100644 --- a/lib/src/provider/api_provider/api_provider.dart +++ b/lib/src/provider/api_provider/api_provider.dart @@ -5,16 +5,14 @@ import 'package:bitcoin_base/src/models/network.dart'; import 'package:blockchain_utils/utils/string/string.dart'; class ApiProvider { - ApiProvider( - {required this.api, Map? header, required this.service}) + ApiProvider({required this.api, Map? header, required this.service}) : _header = header ?? {"Content-Type": "application/json"}; factory ApiProvider.fromMempool(BasedUtxoNetwork network, ApiService service, {Map? header}) { final api = APIConfig.mempool(network); return ApiProvider(api: api, header: header, service: service); } - factory ApiProvider.fromBlocCypher( - BasedUtxoNetwork network, ApiService service, + factory ApiProvider.fromBlocCypher(BasedUtxoNetwork network, ApiService service, {Map? header}) { final api = APIConfig.fromBlockCypher(network); return ApiProvider(api: api, header: header, service: service); @@ -42,8 +40,7 @@ class ApiProvider { "params": params }; final response = await _postReqiest>( - "https://btc.getblock.io/786c97b8-f53f-427b-80f7-9af7bd5bdb84/testnet/", - json.encode(data)); + "https://btc.getblock.io/786c97b8-f53f-427b-80f7-9af7bd5bdb84/testnet/", json.encode(data)); return response; } @@ -54,8 +51,7 @@ class ApiProvider { final response = await _getRequest(url); switch (api.apiType) { case APIType.mempool: - final utxos = - (response as List).map((e) => MempolUtxo.fromJson(e)).toList(); + final utxos = (response as List).map((e) => MempolUtxo.fromJson(e)).toList(); return utxos.toUtxoWithOwnerList(owner); default: final blockCypherUtxo = BlockCypherUtxo.fromJson(response); @@ -63,8 +59,7 @@ class ApiProvider { } } - Future sendRawTransaction(String txDigest, - {String Function(String)? tokenize}) async { + Future sendRawTransaction(String txDigest, {String Function(String)? tokenize}) async { final apiUrl = api.sendTransaction; final url = tokenize?.call(apiUrl) ?? apiUrl; @@ -74,8 +69,7 @@ class ApiProvider { return response; default: final Map digestData = {"tx": txDigest}; - final response = await _postReqiest>( - url, json.encode(digestData)); + final response = await _postReqiest>(url, json.encode(digestData)); BlockCypherTransaction? tr; if (response["tx"] != null) { tr = BlockCypherTransaction.fromJson(response["tx"]); @@ -86,8 +80,7 @@ class ApiProvider { } } - Future getNetworkFeeRate( - {String Function(String)? tokenize}) async { + Future getNetworkFeeRate({String Function(String)? tokenize}) async { final apiUrl = api.getFeeApiUrl(); final url = tokenize?.call(apiUrl) ?? apiUrl; final response = await _getRequest>(url); @@ -99,8 +92,7 @@ class ApiProvider { } } - Future getTransaction(String transactionId, - {String Function(String)? tokenize}) async { + Future getTransaction(String transactionId, {String Function(String)? tokenize}) async { final apiUrl = api.getTransactionUrl(transactionId); final url = tokenize?.call(apiUrl) ?? apiUrl; final response = await _getRequest>(url); @@ -119,9 +111,8 @@ class ApiProvider { final response = await _getRequest(url); switch (api.apiType) { case APIType.mempool: - final transactions = (response as List) - .map((e) => MempoolTransaction.fromJson(e) as T) - .toList(); + final transactions = + (response as List).map((e) => MempoolTransaction.fromJson(e) as T).toList(); return transactions; default: if (response is Map) { @@ -133,9 +124,8 @@ class ApiProvider { } return []; } - final transactions = (response as List) - .map((e) => BlockCypherTransaction.fromJson(e) as T) - .toList(); + final transactions = + (response as List).map((e) => BlockCypherTransaction.fromJson(e) as T).toList(); return transactions; } } diff --git a/lib/src/provider/api_provider/electrum_api_provider.dart b/lib/src/provider/api_provider/electrum_api_provider.dart index 9cd90ec..4db27db 100644 --- a/lib/src/provider/api_provider/electrum_api_provider.dart +++ b/lib/src/provider/api_provider/electrum_api_provider.dart @@ -11,19 +11,16 @@ class ElectrumApiProvider { /// Sends a request to the Electrum server using the specified [request] parameter. /// /// The [timeout] parameter, if provided, sets the maximum duration for the request. - Future request(ElectrumRequest request, - [Duration? timeout]) async { + Future request(ElectrumRequest request, [Duration? timeout]) async { final id = ++_id; final params = request.toRequest(id); final data = await rpc.call(params, timeout); return request.onResonse(_findResult(data, params)); } - dynamic _findResult( - Map data, ElectrumRequestDetails request) { + dynamic _findResult(Map data, ElectrumRequestDetails request) { if (data["error"] != null) { - final code = - int.tryParse(((data["error"]?['code']?.toString()) ?? "0")) ?? 0; + final code = int.tryParse(((data["error"]?['code']?.toString()) ?? "0")) ?? 0; final message = data["error"]?['message'] ?? ""; throw RPCError( errorCode: code, diff --git a/lib/src/provider/models/multisig_script.dart b/lib/src/provider/models/multisig_script.dart index 657f912..0fb091a 100644 --- a/lib/src/provider/models/multisig_script.dart +++ b/lib/src/provider/models/multisig_script.dart @@ -54,12 +54,12 @@ class MultiSignatureAddress { throw BitcoinBasePluginException( "${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); } @@ -72,11 +72,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 a6ecb98..f6299e3 100644 --- a/lib/src/provider/models/utxo_details.dart +++ b/lib/src/provider/models/utxo_details.dart @@ -48,8 +48,7 @@ class UtxoWithAddress { ECPublic public() { if (isMultiSig()) { - throw const BitcoinBasePluginException( - "Cannot access public key in multi-signature address"); + throw const BitcoinBasePluginException("Cannot access public key in multi-signature address"); } if (ownerDetails._publicKey == null) { throw const BitcoinBasePluginException( @@ -95,15 +94,25 @@ class BitcoinOutput implements BitcoinSpendableBaseOutput { /// Value is a pointer to a BigInt representing the amount of bitcoins sent to the recipient. @override final BigInt value; + + bool isSilentPayment; + bool isChange; + // final CashToken? token; BitcoinOutput({ required this.address, required this.value, + this.isSilentPayment = false, + this.isChange = false, }); @override - TxOutput get toOutput => - TxOutput(amount: value, scriptPubKey: address.toScriptPubKey()); + TxOutput get toOutput => TxOutput( + amount: value, + scriptPubKey: address.toScriptPubKey(), + isSilentPayment: isSilentPayment, + isChange: isChange, + ); } /// Represents a custom script-based Bitcoin output, implementing BitcoinBaseOutput. @@ -120,8 +129,7 @@ class BitcoinScriptOutput implements BitcoinBaseOutput { /// Convert the custom script output to a standard TxOutput. @override - TxOutput get toOutput => - TxOutput(amount: value, scriptPubKey: script, cashToken: null); + TxOutput get toOutput => TxOutput(amount: value, scriptPubKey: script, cashToken: null); } /// BitcoinTokenOutput represents details about a Bitcoin cash transaction with cash token output, including @@ -143,8 +151,8 @@ class BitcoinTokenOutput implements BitcoinSpendableBaseOutput { /// Convert the custom script output to a standard TxOutput. @override - TxOutput get toOutput => TxOutput( - amount: value, scriptPubKey: address.toScriptPubKey(), cashToken: token); + TxOutput get toOutput => + TxOutput(amount: value, scriptPubKey: address.toScriptPubKey(), cashToken: token); } /// Represents a burnable output, specifically related to [BitcoinTokenOutput] for burning Cash Tokens. @@ -201,6 +209,7 @@ class BitcoinUtxo { required this.scriptType, this.blockHeight, this.token, + this.isSilentPayment, }); /// check if utxos is p2tr @@ -208,6 +217,8 @@ class BitcoinUtxo { return scriptType == SegwitAddresType.p2tr; } + bool? isSilentPayment; + /// check if utxos is segwit bool isSegwit() { return scriptType.isSegwit || isP2shSegwit(); @@ -215,13 +226,12 @@ class BitcoinUtxo { /// checl if utxos is p2sh neasted segwit bool isP2shSegwit() { - return scriptType == P2shAddressType.p2wpkhInP2sh || - scriptType == P2shAddressType.p2wshInP2sh; + return scriptType == P2shAddressType.p2wpkhInP2sh || scriptType == P2shAddressType.p2wshInP2sh; } /// 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/service/electrum/methods.dart b/lib/src/provider/service/electrum/methods.dart index 027fd44..66984e5 100644 --- a/lib/src/provider/service/electrum/methods.dart +++ b/lib/src/provider/service/electrum/methods.dart @@ -12,8 +12,7 @@ class ElectrumRequestMethods { ElectrumRequestMethods._("server.donation_address"); /// A newly-started server uses this call to get itself into other servers’ peers lists. It should not be used by wallet clients. - static const ElectrumRequestMethods serverAddPeer = - ElectrumRequestMethods._("server.add_peer"); + static const ElectrumRequestMethods serverAddPeer = ElectrumRequestMethods._("server.add_peer"); /// Subscribe to a script hash. static const ElectrumRequestMethods scriptHashSubscribe = @@ -72,28 +71,23 @@ class ElectrumRequestMethods { ElectrumRequestMethods._("blockchain.transaction.broadcast"); /// Return a banner to be shown in the Electrum console. - static const ElectrumRequestMethods serverBanner = - ElectrumRequestMethods._("server.banner"); + static const ElectrumRequestMethods serverBanner = ElectrumRequestMethods._("server.banner"); /// Return a list of features and services supported by the server. - static const ElectrumRequestMethods serverFeatures = - ElectrumRequestMethods._("server.features"); + static const ElectrumRequestMethods serverFeatures = ElectrumRequestMethods._("server.features"); /// Ping the server to ensure it is responding, and to keep the session alive. The server may disconnect clients that have sent no requests for roughly 10 minutes. - static const ElectrumRequestMethods ping = - ElectrumRequestMethods._("server.ping"); + static const ElectrumRequestMethods ping = ElectrumRequestMethods._("server.ping"); /// Identify the client to the server and negotiate the protocol version. Only the first server.version() message is accepted. - static const ElectrumRequestMethods version = - ElectrumRequestMethods._("server.version"); + static const ElectrumRequestMethods version = ElectrumRequestMethods._("server.version"); /// Subscribe to receive block headers when a new block is found. static const ElectrumRequestMethods headersSubscribe = ElectrumRequestMethods._("blockchain.headers.subscribe"); /// Return the minimum fee a low-priority transaction must pay in order to be accepted to the daemon’s memory pool. - static const ElectrumRequestMethods relayFee = - ElectrumRequestMethods._("blockchain.relayfee"); + static const ElectrumRequestMethods relayFee = ElectrumRequestMethods._("blockchain.relayfee"); /// Pass through the masternode announce message to be broadcast by the daemon. static const ElectrumRequestMethods masternodeAnnounceBroadcast = @@ -104,20 +98,18 @@ class ElectrumRequestMethods { ElectrumRequestMethods._("masternode.subscribe"); /// Returns the list of masternodes. - static const ElectrumRequestMethods masternodeList = - ElectrumRequestMethods._("masternode.list"); + static const ElectrumRequestMethods masternodeList = ElectrumRequestMethods._("masternode.list"); /// Returns a diff between two deterministic masternode lists. The result also contains proof data. - static const ElectrumRequestMethods protxDiff = - ElectrumRequestMethods._("protx.diff"); + static const ElectrumRequestMethods protxDiff = ElectrumRequestMethods._("protx.diff"); /// Returns detailed information about a deterministic masternode. - static const ElectrumRequestMethods protxInfo = - ElectrumRequestMethods._("protx.info"); + static const ElectrumRequestMethods protxInfo = ElectrumRequestMethods._("protx.info"); /// Returns a name resolution proof, suitable for low-latency (single round-trip) resolution. static const ElectrumRequestMethods getValueProof = ElectrumRequestMethods._("blockchain.name.get_value_proof"); + @override String toString() { return method; diff --git a/lib/src/provider/service/electrum/params.dart b/lib/src/provider/service/electrum/params.dart index cb245c2..2945af7 100644 --- a/lib/src/provider/service/electrum/params.dart +++ b/lib/src/provider/service/electrum/params.dart @@ -31,8 +31,7 @@ class ElectrumRequestDetails { } /// Abstract class representing an Electrum request with generic result and response types. -abstract class ElectrumRequest - implements ElectrumRequestParams { +abstract class ElectrumRequest implements ElectrumRequestParams { String? get validate => null; RESULT onResonse(RESPONSE result) { @@ -48,7 +47,6 @@ abstract class ElectrumRequest "params": inJson, "id": requestId, }; - return ElectrumRequestDetails( - id: requestId, params: params, method: method); + return ElectrumRequestDetails(id: requestId, params: params, method: method); } } diff --git a/lib/src/provider/service/electrum/service.dart b/lib/src/provider/service/electrum/service.dart index 889b194..03e4ca2 100644 --- a/lib/src/provider/service/electrum/service.dart +++ b/lib/src/provider/service/electrum/service.dart @@ -8,6 +8,5 @@ mixin BitcoinBaseElectrumRPCService { /// Makes an HTTP GET request to the Tron network with the specified [params]. /// /// The optional [timeout] parameter sets the maximum duration for the request. - Future> call(ElectrumRequestDetails params, - [Duration? timeout]); + Future> call(ElectrumRequestDetails params, [Duration? timeout]); } diff --git a/lib/src/provider/service/http/http_service.dart b/lib/src/provider/service/http/http_service.dart index 99631b8..be0e94a 100644 --- a/lib/src/provider/service/http/http_service.dart +++ b/lib/src/provider/service/http/http_service.dart @@ -13,6 +13,5 @@ abstract class ApiService { /// - [headers]: A map of headers to be included in the request. /// - [body]: The request body, typically in JSON format. Future post(String url, - {Map headers = const {"Content-Type": "application/json"}, - Object? body}); + {Map headers = const {"Content-Type": "application/json"}, Object? body}); } diff --git a/lib/src/provider/transaction_builder/forked_transaction_builder.dart b/lib/src/provider/transaction_builder/forked_transaction_builder.dart index 862b0f0..3403ffb 100644 --- a/lib/src/provider/transaction_builder/forked_transaction_builder.dart +++ b/lib/src/provider/transaction_builder/forked_transaction_builder.dart @@ -9,7 +9,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). @@ -21,7 +21,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; @@ -31,7 +31,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, @@ -52,7 +52,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); } @@ -74,7 +74,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. @@ -144,7 +144,7 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { case P2pkhAddressType.p2pkhwt: case P2shAddressType.p2pkhInP2shwt: case P2shAddressType.p2pkhInP2sh32wt: - return senderPub.toAddress().toScriptPubKey(); + return senderPub.toP2pkhAddress().toScriptPubKey(); default: throw BitcoinBasePluginException( "${utxo.utxo.scriptType} does not sudpport on ${network.conf.coinName.name}"); @@ -216,7 +216,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: @@ -257,20 +257,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); @@ -282,7 +282,7 @@ that demonstrate the right to spend the bitcoins associated with the correspondi }, ); } - return List.unmodifiable(outputs); + return List.unmodifiable(builtOutputs); } /* @@ -344,7 +344,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( @@ -368,12 +368,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 a20130d..aace073 100644 --- a/lib/src/provider/transaction_builder/transaction_builder.dart +++ b/lib/src/provider/transaction_builder/transaction_builder.dart @@ -22,7 +22,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,8 +31,12 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { final bool isFakeTransaction; final BitcoinOrdering inputOrdering; final BitcoinOrdering outputOrdering; + final List? inputPrivKeyInfos; + final List? vinOutpoints; + bool _hasSilentPayment = false; + BitcoinTransactionBuilder({ - required this.outPuts, + required this.outputs, required this.fee, required this.network, required List utxos, @@ -41,6 +45,8 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { this.memo, this.enableRBF = false, this.isFakeTransaction = false, + this.inputPrivKeyInfos, + this.vinOutpoints, }) : utxosInfo = utxos { _validateBuilder(); } @@ -52,17 +58,16 @@ 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 BitcoinBasePluginException( - "Cash Token only work on Bitcoin cash network"); + throw const BitcoinBasePluginException("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. 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); @@ -73,18 +78,21 @@ 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, /// We select transaction outputs - outPuts: outputs, + outputs: outputs, /* Transaction fee Ensure that you have accurately calculated the amounts. @@ -110,6 +118,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 @@ -120,8 +131,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 { @@ -131,8 +142,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; } @@ -175,16 +185,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: @@ -193,8 +199,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { } return script; default: - throw BitcoinBasePluginException( - "unsuported multi-sig type ${utxo.utxo.scriptType}"); + throw BitcoinBasePluginException("unsuported multi-sig type ${utxo.utxo.scriptType}"); } } @@ -206,31 +211,35 @@ 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(); + return senderPub + .toTaprootAddress(tweak: utxo.utxo.isSilentPayment != true) + .toScriptPubKey(); + case SegwitAddresType.mweb: + return Script(script: []); 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(); @@ -255,13 +264,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