diff --git a/.gitignore b/.gitignore index 7613527..2bfb912 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,4 @@ migrate_working_dir/ .packages build/ *.zip +/example/test diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c89343..696858f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,58 @@ +## 6.1.0 + +- Fix der signature validation. + +## 6.0.0 + +- Added support for BIP-327: MuSig2 for BIP340-compatible multi-signatures +- Implemented BIP-174: Partially Signed Bitcoin Transaction (PSBT) format +- Integrated BIP-370: PSBT Version 2 enhancements +- Included BIP-371: Taproot fields for PSBT +- Extended support for BIP-373: MuSig2-related PSBT fields + +## 5.3.0 + +* Update dependencies + +## 5.2.0 + +* Update dependencies + +## 5.1.0 + +* Update dependencies + +## 5.0.0 + +* Update dependencies +* Minimum required Dart SDK version updated to 3.3. + +## 4.9.4 + +* Improved serialization process for large transaction scripts. +* Added support for the Electra network. +* Create and spent from uncompressed public key format. +* Important Notice: This is the final version supporting Dart v2. The next release will require Dart v3.3 or higher. + +## 4.9.2 + +* Update dependencies +* Resolved issue with transaction deserialization (unsigned tx) + +## 4.9.1 + +* Resolved issue with transaction deserialization (Issue #9) + +## 4.9.0 + +* Correct Bitcoin address network configuration. +* Resolve issue with Electrum fee estimation results. + + +## 4.8.0 + +* Update dependencies + ## 4.7.0 * Update dependencies diff --git a/README.md b/README.md index 16ba6b9..c0fc6c2 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,23 @@ Using this package, you can create a Bitcoin transaction in two ways: either thr - BitcoinTransactionBuilder: Even with limited prior knowledge, you can utilize this class to send various types of transactions. Below, I've provided an example in which a transaction features 8 distinct input addresses with different types and private keys, as well as 10 different output addresses. Furthermore, additional examples have been prepared, which you can find in the [`example`](https://github.com/mrtnetwork/bitcoin_base/tree/main/example) folder. +### PSBT + Find example implementations [here](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/lib/psbt) + +- BIP-0174: Partially Signed Bitcoin Transaction Format +- BIP-0370: PSBT Version 2 +- BIP-0371: Taproot Fields for PSBT +- BIP-0373: MuSig2 PSBT Fields + +### MuSig2 (BIP-327): + Find example implementations [here](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/lib/musig) + + - Sign/Verify: Supports signing and verifying multisignature transactions using MuSig2 + - NonceAgg: Aggregates nonces from multiple participants for secure signature generation. + - KeyAgg: Combines multiple public keys into a single aggregated public key for efficient multisignature verification + + + ### Addresses - P2PKH A P2PKH (Pay-to-Public-Key-Hash) address in Bitcoin represents ownership of a cryptocurrency wallet by encoding a hashed public key @@ -217,11 +234,11 @@ We have integrated three APIs—Mempool, BlockCypher, and Electrum—into the pl Script(script: ["OP_2", public1, public2, "OP_2", "OP_CHECKMULTISIG"]); /// Generate a P2WSH 3-of-5 address. - final p2wsh3of5Address = P2wshAddress.fromScript(script: newScript); + final p2wsh3of5Address = P2wshAddress.fromRedeemScript(script: newScript); /// Generate a P2SH 3-of-5 address from the P2WSH address. final p2sh3Of5 = - P2shAddress.fromScript(script: p2wsh3of5Address.toScriptPubKey()); + P2shAddress.fromRedeemScript(script: p2wsh3of5Address.toScriptPubKey()); /// Implemented classes for each network to better manage network-specific addresses. /// Integrated these classes to eliminate the necessity of using the main class and type for each address. @@ -280,7 +297,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li await ElectrumWebSocketService.connect("184...."); /// create provider with service - final provider = ElectrumApiProvider(service); + final provider = ElectrumProvider(service); /// spender details final privateKey = ECPrivate.fromHex( @@ -310,7 +327,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li for (final i in spenders) { /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account final elctrumUtxos = await provider - .request(ElectrumScriptHashListUnspent(scriptHash: i.pubKeyHash())); + .request(ElectrumRequestScriptHashListUnspent(scriptHash: i.pubKeyHash())); /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. /// read spender utxos @@ -427,7 +444,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li final raw = transaction.serialize(); /// send to network - await provider.request(ElectrumBroadCastTransaction(transactionRaw: raw)); + await provider.request(ElectrumRequestBroadCastTransaction(transactionRaw: raw)); /// Once completed, we verify the status by checking the mempool or using another explorer to review the transaction details. /// https://mempool.space/testnet/tx/70cf664bba4b5ac9edc6133e9c6891ffaf8a55eaea9d2ac99aceead1c3db8899 @@ -446,7 +463,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li "wss://chipnet.imaginary.cash:50004"); /// create provider with service - final provider = ElectrumApiProvider(service); + final provider = ElectrumProvider(service); /// initialize private key final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( @@ -470,7 +487,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li /// 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( + final elctrumUtxos = await provider.request(ElectrumRequestScriptHashListUnspent( scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), includeTokens: true, )); @@ -566,7 +583,7 @@ In the [example](https://github.com/mrtnetwork/bitcoin_base/tree/main/example/li /// send transaction to network await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); + .request(ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); /// done! check the transaction in block explorer /// https://chipnet.imaginary.cash/tx/97030c1236a024de7cad7ceadf8571833029c508e016bcc8173146317e367ae6 @@ -704,7 +721,7 @@ I haven't implemented any specific HTTP service or socket service within this pl await ElectrumSSLService.connect("testnet.aranguren.org:51002"); /// create provider with service - final provider = ElectrumApiProvider(service); + final provider = ElectrumProvider(service); final address = P2trAddress.fromAddress(address: ".....", network: network); @@ -714,7 +731,7 @@ I haven't implemented any specific HTTP service or socket service within this pl /// Return an ordered list of UTXOs sent to a script hash. final accountUnspend = await provider - .request(ElectrumScriptHashListUnspent(scriptHash: address.pubKeyHash())); + .request(ElectrumRequestScriptHashListUnspent(scriptHash: address.pubKeyHash())); /// Return the confirmed and unconfirmed history of a script hash. final accountHistory = await provider @@ -722,7 +739,7 @@ I haven't implemented any specific HTTP service or socket service within this pl /// Broadcast a transaction to the network. final broadcastTransaction = await provider - .request(ElectrumBroadCastTransaction(transactionRaw: "txDigest")); + .request(ElectrumRequestBroadCastTransaction(transactionRaw: "txDigest")); /// .... ``` diff --git a/analysis_options.yaml b/analysis_options.yaml index d429a46..3c029d1 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,56 @@ -# include: package:lints/recommended.yaml -include: package:flutter_lints/flutter.yaml +include: package:lints/recommended.yaml +# include: package:flutter_lints/flutter.yaml # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options +# Uncomment the following section to specify additional rules. +linter: + rules: + # - always_declare_return_types + # - annotate_overrides + # - avoid_init_to_null + # - avoid_null_checks_in_equality_operators + # - avoid_relative_lib_imports + # - avoid_return_types_on_setters + # - avoid_shadowing_type_parameters + # - avoid_single_cascade_in_expression_statements + # - avoid_types_as_parameter_names + # - await_only_futures + # - camel_case_extensions + # - curly_braces_in_flow_control_structures + # - empty_catches + # - empty_constructor_bodies + # - library_names + # - library_prefixes + # - no_duplicate_case_values + # - null_closures + # - omit_local_variable_types + # - prefer_adjacent_string_concatenation + # - prefer_collection_literals + # - prefer_conditional_assignment + # - prefer_contains + # - prefer_equal_for_default_values + # - prefer_final_fields + # - prefer_for_elements_to_map_fromIterable + # - prefer_generic_function_type_aliases + # - prefer_if_null_operators + # - prefer_inlined_adds + # - prefer_is_empty + # - prefer_is_not_empty + # - prefer_iterable_whereType + # - prefer_single_quotes + # - prefer_spread_collections + # - recursive_getters + # - slash_for_doc_comments + # - sort_child_properties_last + # - type_init_formals + # - unawaited_futures + # - unnecessary_brace_in_string_interps + # - unnecessary_const + # - unnecessary_getters_setters + # - unnecessary_new + # - unnecessary_null_in_if_null_operators + # - unnecessary_this + # - unrelated_type_equality_checks + # - use_function_type_syntax_for_parameters + # - use_rethrow_when_possible + # - valid_regexps \ No newline at end of file diff --git a/example/lib/bitcoin_cash/burn_token_example.dart b/example/lib/bitcoin_cash/burn_token_example.dart index 4acdb9b..23283aa 100644 --- a/example/lib/bitcoin_cash/burn_token_example.dart +++ b/example/lib/bitcoin_cash/burn_token_example.dart @@ -1,149 +1,148 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; /// https://github.com/cashtokens/cashtokens void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = await ElectrumWebSocketService.connect( - "wss://chipnet.imaginary.cash:50004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkInP2sh(useBCHP2sh32: true)); - - /// p2pkh with token address () - final receiver1 = P2pkhAddress.fromHash160( - h160: publicKey.toP2pkhAddress().addressProgram, - type: P2pkhAddressType.p2pkhwt); - - /// 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(), - includeTokens: true, - )); - // return; - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = elctrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) - .toList() - - /// we only filter the utxos for this token or none token utxos - .where((element) => - element.utxo.token?.category == - "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" || - element.utxo.token == null) - .toList(); - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - if (sumOfUtxo == BigInt.zero) { - return; - } - // print(sumOfUtxo); - // return; - /// CashToken{bitfield: 16, commitment: null, amount: 2000, category: 4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430} - final CashToken token = elctrumUtxos - .firstWhere((e) => - e.token?.category == - "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") - .token!; - - /// sum of ft token amounts with category "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" - final sumofTokenUtxos = utxos - .where((element) => - element.utxo.token?.category == - "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") - .fold( - BigInt.zero, - (previousValue, element) => - previousValue + element.utxo.token!.amount); - - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - /// change address for bch values (sum of bch amout - (outputs amount + fee)) - BitcoinOutput( - address: p2pkhAddress.baseAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.00002") + BtcUtils.toSatoshi("0.00003")), - ), - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: receiver1, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - - /// clone the token with new token amount for output1 (15 amount of category) - token: token.copyWith(amount: BigInt.from(15))), - - /// another change token value to change account like bch - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: p2pkhAddress.baseAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - - /// clone the token with new token amount for change amount in this case we want to burn 20 token (output1 + 20) - token: token.copyWith(amount: sumofTokenUtxos - BigInt.from(35))), - - /// create burnable output for this category - /// for nft token you should use utxoHash - BitcoinBurnableOutput( - categoryID: token.category, value: BigInt.from(20), utxoHash: null) - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - - /// Bitcoin Cash Metadata Registries - /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata - /// we does not create metadata for this token - memo: null, - utxos: utxos, - ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://chipnet.imaginary.cash/tx/d85da44ba0c12ab8b0f4c636ca5451ae2c3a90b0f6d9e47fe381d0f5c6966ff3 + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = await ElectrumWebSocketService.connect( + // "wss://chipnet.imaginary.cash:50004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkInP2sh(useBCHP2sh32: true)); + + // /// p2pkh with token address () + // final receiver1 = P2pkhAddress.fromHash160( + // h160: publicKey.toP2pkhAddress().addressProgram, + // type: P2pkhAddressType.p2pkhwt); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), + // includeTokens: true, + // )); + // // return; + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = elctrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2pkhAddress.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) + // .toList() + + // /// we only filter the utxos for this token or none token utxos + // .where((element) => + // element.utxo.token?.category == + // "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" || + // element.utxo.token == null) + // .toList(); + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + // if (sumOfUtxo == BigInt.zero) { + // return; + // } + + // /// CashToken{bitfield: 16, commitment: null, amount: 2000, category: 4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430} + // final CashToken token = elctrumUtxos + // .firstWhere((e) => + // e.token?.category == + // "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") + // .token!; + + // /// sum of ft token amounts with category "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" + // final sumofTokenUtxos = utxos + // .where((element) => + // element.utxo.token?.category == + // "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") + // .fold( + // BigInt.zero, + // (previousValue, element) => + // previousValue + element.utxo.token!.amount); + + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // /// change address for bch values (sum of bch amout - (outputs amount + fee)) + // BitcoinOutput( + // address: p2pkhAddress.baseAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.00002") + BtcUtils.toSatoshi("0.00003")), + // ), + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: receiver1, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + + // /// clone the token with new token amount for output1 (15 amount of category) + // token: token.copyWith(amount: BigInt.from(15))), + + // /// another change token value to change account like bch + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: p2pkhAddress.baseAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + + // /// clone the token with new token amount for change amount in this case we want to burn 20 token (output1 + 20) + // token: token.copyWith(amount: sumofTokenUtxos - BigInt.from(35))), + + // /// create burnable output for this category + // /// for nft token you should use utxoHash + // BitcoinBurnableOutput( + // categoryID: token.category, value: BigInt.from(20), utxoHash: null) + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + + // /// Bitcoin Cash Metadata Registries + // /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata + // /// we does not create metadata for this token + // memo: null, + // utxos: utxos, + // ); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://chipnet.imaginary.cash/tx/d85da44ba0c12ab8b0f4c636ca5451ae2c3a90b0f6d9e47fe381d0f5c6966ff3 } diff --git a/example/lib/bitcoin_cash/create_cash_token_example.dart b/example/lib/bitcoin_cash/create_cash_token_example.dart index c0b8754..c2a425b 100644 --- a/example/lib/bitcoin_cash/create_cash_token_example.dart +++ b/example/lib/bitcoin_cash/create_cash_token_example.dart @@ -1,143 +1,141 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:blockchain_utils/blockchain_utils.dart'; /// please make sure read this before create transaction on mainnet /// https://github.com/cashtokens/cashtokens void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = await ElectrumWebSocketService.connect( - "wss://chipnet.imaginary.cash:50004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkhAddress(); - - /// 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(), - includeTokens: false, - )); - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = elctrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress))) - .toList(); - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - - // return; - - /// Every token category ID is a transaction ID: - /// the ID must be selected from the inputs of its genesis transaction, - /// and only token genesis inputs – inputs which spend output 0 of their - /// parent transaction – are eligible - /// (i.e. outpoint transaction hashes of inputs with an outpoint index of 0). - /// As such, implementations can locate the genesis transaction of any category - /// by identifying the transaction that spent the 0th output of the transaction referenced by the category ID. - String? vout0Hash; - try { - // Retrieve the transaction hash of the 0th output UTXO - vout0Hash = - utxos.firstWhere((element) => element.utxo.vout == 0).utxo.txHash; - } on StateError { - /// if we dont have utxos with index 0 we must create them with some estimate transaction before create transaction - return; - } - // print("vout $vout0Hash"); - // return; - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - BitcoinTokenOutput( - address: p2pkhAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - token: CashToken( - category: vout0Hash, - - /// The commitment contents of the NFT held in this output (0 to 40 bytes). T - /// his field is omitted if no NFT is present. In this case, it is null as it is not an NFT. - commitment: null, - - /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). - /// This field is omitted if no fungible tokens are present. - amount: BigInt.from(800000000000000000), - bitfield: CashTokenUtils.buildBitfield( - hasAmount: true, - - /// nfts field - capability: null, - hasCommitmentLength: false, - hasNFT: false))), - - /// change address- back amount to account exclude fee and token input value - BitcoinOutput( - address: p2pkhAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.00001") + BtcUtils.toSatoshi("0.00003")), - ), - - /// add token meta data to transaction - /// see https://cashtokens.org/docs/bcmr/chip/ how to create BCMR - /// also you can use Registery class for create BCMR - BCMR( - uris: [ - "ipfs://bafkreihfrxykireezlcp2jstp7cjwx5nl7of3nuul3qnubxygwvwcjun44" - ], - hash: - "e58df0a44484cac4fd26537fc49b5fad5fdc5db6945ee0da06f835ab61268de7") - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - - /// Bitcoin Cash Metadata Registries - /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata - /// we does not create metadata for this token - memo: null, - utxos: utxos, - - /// disable ordering - outputOrdering: BitcoinOrdering.none); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://chipnet.imaginary.cash/tx/fe0f9f84bd8782b8037160c09a515d39a9cc5bbeda6dcca6fb8a89e952bc9dea + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = await ElectrumWebSocketService.connect( + // "wss://chipnet.imaginary.cash:50004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkhAddress(); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2pkhAddress.pubKeyHash(), + // includeTokens: false, + // )); + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = elctrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2pkhAddress.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), address: p2pkhAddress))) + // .toList(); + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + + // // return; + + // /// Every token category ID is a transaction ID: + // /// the ID must be selected from the inputs of its genesis transaction, + // /// and only token genesis inputs – inputs which spend output 0 of their + // /// parent transaction – are eligible + // /// (i.e. outpoint transaction hashes of inputs with an outpoint index of 0). + // /// As such, implementations can locate the genesis transaction of any category + // /// by identifying the transaction that spent the 0th output of the transaction referenced by the category ID. + // String? vout0Hash; + // try { + // // Retrieve the transaction hash of the 0th output UTXO + // vout0Hash = + // utxos.firstWhere((element) => element.utxo.vout == 0).utxo.txHash; + // } on StateError { + // /// if we dont have utxos with index 0 we must create them with some estimate transaction before create transaction + // return; + // } + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // BitcoinTokenOutput( + // address: p2pkhAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + // token: CashToken( + // category: vout0Hash, + + // /// The commitment contents of the NFT held in this output (0 to 40 bytes). T + // /// his field is omitted if no NFT is present. In this case, it is null as it is not an NFT. + // commitment: null, + + // /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). + // /// This field is omitted if no fungible tokens are present. + // amount: BigInt.from(800000000000000000), + // bitfield: CashTokenUtils.buildBitfield( + // hasAmount: true, + + // /// nfts field + // capability: null, + // hasCommitmentLength: false, + // hasNFT: false))), + + // /// change address- back amount to account exclude fee and token input value + // BitcoinOutput( + // address: p2pkhAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.00001") + BtcUtils.toSatoshi("0.00003")), + // ), + + // /// add token meta data to transaction + // /// see https://cashtokens.org/docs/bcmr/chip/ how to create BCMR + // /// also you can use Registery class for create BCMR + // BCMR( + // uris: [ + // "ipfs://bafkreihfrxykireezlcp2jstp7cjwx5nl7of3nuul3qnubxygwvwcjun44" + // ], + // hash: + // "e58df0a44484cac4fd26537fc49b5fad5fdc5db6945ee0da06f835ab61268de7") + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + + // /// Bitcoin Cash Metadata Registries + // /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata + // /// we does not create metadata for this token + // memo: null, + // utxos: utxos, + + // /// disable ordering + // outputOrdering: BitcoinOrdering.none); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://chipnet.imaginary.cash/tx/fe0f9f84bd8782b8037160c09a515d39a9cc5bbeda6dcca6fb8a89e952bc9dea } diff --git a/example/lib/bitcoin_cash/create_nft_example.dart b/example/lib/bitcoin_cash/create_nft_example.dart index 4db99f2..a1e912d 100644 --- a/example/lib/bitcoin_cash/create_nft_example.dart +++ b/example/lib/bitcoin_cash/create_nft_example.dart @@ -1,129 +1,129 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:blockchain_utils/blockchain_utils.dart'; /// please make sure read this before create transaction on mainnet //// https://github.com/cashtokens/cashtokens void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = await ElectrumWebSocketService.connect( - "wss://chipnet.imaginary.cash:50004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkhAddress()); - - /// 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(), - includeTokens: false, - )); - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = elctrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) - .toList(); - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - if (sumOfUtxo == BigInt.zero) { - return; - } - - /// Every token category ID is a transaction ID: - /// the ID must be selected from the inputs of its genesis transaction, - /// and only token genesis inputs – inputs which spend output 0 of their - /// parent transaction – are eligible - /// (i.e. outpoint transaction hashes of inputs with an outpoint index of 0). - /// As such, implementations can locate the genesis transaction of any category - /// by identifying the transaction that spent the 0th output of the transaction referenced by the category ID. - String? vout0Hash; - try { - vout0Hash = - utxos.firstWhere((element) => element.utxo.vout == 0).utxo.txHash; - } on StateError { - /// if we dont have utxos with index 0 we must create them with some estimate transaction before create transaction - return; - } - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - BitcoinOutput( - address: p2pkhAddress.baseAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.00001") + BtcUtils.toSatoshi("0.00003")), - ), - BitcoinTokenOutput( - address: p2pkhAddress.baseAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - token: CashToken( - category: vout0Hash, - - /// The commitment contents of the NFT held in this output (0 to 40 bytes). T - /// his field is omitted if no NFT is present - commitment: null, - - /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). - /// This field is omitted if no fungible tokens are present. - amount: null, - bitfield: CashTokenUtils.buildBitfield( - hasAmount: false, - - /// nfts field - /// mintable nft - capability: CashTokenCapability.minting, - hasCommitmentLength: false, - hasNFT: true))), - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - - /// Bitcoin Cash Metadata Registries - /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata - /// we does not create metadata for this token - memo: null, - utxos: utxos, - ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://chipnet.imaginary.cash/tx/4e153029c75963f39920184233756f8f55d5a8f86e01cbdaf0340320c814e25e + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = await ElectrumWebSocketService.connect( + // "wss://chipnet.imaginary.cash:50004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkhAddress()); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), + // includeTokens: false, + // )); + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = elctrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2pkhAddress.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) + // .toList(); + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + // if (sumOfUtxo == BigInt.zero) { + // return; + // } + + // /// Every token category ID is a transaction ID: + // /// the ID must be selected from the inputs of its genesis transaction, + // /// and only token genesis inputs – inputs which spend output 0 of their + // /// parent transaction – are eligible + // /// (i.e. outpoint transaction hashes of inputs with an outpoint index of 0). + // /// As such, implementations can locate the genesis transaction of any category + // /// by identifying the transaction that spent the 0th output of the transaction referenced by the category ID. + // String? vout0Hash; + // try { + // vout0Hash = + // utxos.firstWhere((element) => element.utxo.vout == 0).utxo.txHash; + // } on StateError { + // /// if we dont have utxos with index 0 we must create them with some estimate transaction before create transaction + // return; + // } + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // BitcoinOutput( + // address: p2pkhAddress.baseAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.00001") + BtcUtils.toSatoshi("0.00003")), + // ), + // BitcoinTokenOutput( + // address: p2pkhAddress.baseAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + // token: CashToken( + // category: vout0Hash, + + // /// The commitment contents of the NFT held in this output (0 to 40 bytes). T + // /// his field is omitted if no NFT is present + // commitment: null, + + // /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). + // /// This field is omitted if no fungible tokens are present. + // amount: null, + // bitfield: CashTokenUtils.buildBitfield( + // hasAmount: false, + + // /// nfts field + // /// mintable nft + // capability: CashTokenCapability.minting, + // hasCommitmentLength: false, + // hasNFT: true))), + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + + // /// Bitcoin Cash Metadata Registries + // /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata + // /// we does not create metadata for this token + // memo: null, + // utxos: utxos, + // ); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://chipnet.imaginary.cash/tx/4e153029c75963f39920184233756f8f55d5a8f86e01cbdaf0340320c814e25e } diff --git a/example/lib/bitcoin_cash/make_vout0_example.dart b/example/lib/bitcoin_cash/make_vout0_example.dart index 6415852..5a9ba62 100644 --- a/example/lib/bitcoin_cash/make_vout0_example.dart +++ b/example/lib/bitcoin_cash/make_vout0_example.dart @@ -1,6 +1,5 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_ssl_service.dart'; /// make vout 0 for account for create token hash /// estimate transaction to your self with input 0 @@ -8,15 +7,14 @@ import 'package:example/services_examples/electrum/electrum_ssl_service.dart'; void main() async { /// connect to electrum service with ssl /// please see `services_examples` folder for how to create electrum ssl service - final service = - await ElectrumSSLService.connect("chipnet.imaginary.cash:50002"); + final service = ElectrumSSLService.connect(Uri.parse("tcp://chipnet.imaginary.cash:50002")); /// create provider with service - final provider = ElectrumApiProvider(service); + final provider = await ElectrumProvider.connect(service); /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + final privateKey = ECPrivate.fromBytes( + BytesUtils.fromHexString("f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); /// public key final publicKey = privateKey.getPublic(); @@ -30,7 +28,7 @@ void main() async { /// 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( + final elctrumUtxos = await provider.request(ElectrumRequestScriptHashListUnspent( scriptHash: p2pkhAddress.pubKeyHash(), includeTokens: false, )); @@ -39,8 +37,7 @@ void main() async { final List utxos = elctrumUtxos .map((e) => UtxoWithAddress( utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress))) + ownerDetails: UtxoAddressDetails(publicKey: publicKey.toHex(), address: p2pkhAddress))) .toList(); /// som of utxos in satoshi @@ -62,8 +59,7 @@ void main() async { memo: null, utxos: utxos, ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + final transaaction = bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { return privateKey.signInput(trDigest, sigHash: sighash); }); @@ -77,8 +73,7 @@ void main() async { final transactionRaw = transaaction.toHex(); /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); + await provider.request(ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); /// done! check the transaction in block explorer /// https://chipnet.imaginary.cash/tx/b20d4c13fe67adc2f73aee0161eb51c7e813643ddc8eb655c6bd9ae72b7562cb diff --git a/example/lib/bitcoin_cash/minting_nft_example.dart b/example/lib/bitcoin_cash/minting_nft_example.dart index 6c2e1fa..3ce9d5a 100644 --- a/example/lib/bitcoin_cash/minting_nft_example.dart +++ b/example/lib/bitcoin_cash/minting_nft_example.dart @@ -1,185 +1,185 @@ -import 'dart:convert'; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; +// import 'dart:convert'; +// import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:blockchain_utils/blockchain_utils.dart'; /// https://github.com/cashtokens/cashtokens void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = - await ElectrumWebSocketService.connect("wss://tbch4.loping.net:62004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkInP2sh(useBCHP2sh32: true)); - - /// 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(), - includeTokens: true, - )); - // return; - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = elctrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) - .toList(); - // return; - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - if (sumOfUtxo == BigInt.zero) { - return; - } - const String nftCategoryId = - "3f0d87791e5996aaddbce16c12651dd8b5b881cf7338340504bb7b2c6c08bfc4"; - - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - BitcoinOutput( - address: p2pkhAddress.baseAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.00004") + BtcUtils.toSatoshi("0.00003")), - ), - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: p2pkhAddress.baseAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - token: CashToken( - category: nftCategoryId, - - /// The commitment contents of the NFT held in this output (0 to 40 bytes). T - /// his field is omitted if no NFT is present - commitment: utf8.encode("github.com/mrtnetwork"), - - /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). - /// This field is omitted if no fungible tokens are present. - amount: BigInt.from(160000000) ~/ BigInt.from(4), - bitfield: CashTokenUtils.buildBitfield( - hasAmount: true, - capability: CashTokenCapability.minting, - hasCommitmentLength: true, - hasNFT: true))), - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: p2pkhAddress.baseAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - token: CashToken( - category: nftCategoryId, - - /// The commitment contents of the NFT held in this output (0 to 40 bytes). T - /// his field is omitted if no NFT is present - commitment: utf8.encode("github.com/mrtnetwork"), - - /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). - /// This field is omitted if no fungible tokens are present. - amount: BigInt.from(160000000) ~/ BigInt.from(4), - bitfield: CashTokenUtils.buildBitfield( - hasAmount: true, - capability: CashTokenCapability.mutable, - hasCommitmentLength: true, - hasNFT: true))), - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: p2pkhAddress.baseAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - token: CashToken( - category: nftCategoryId, - - /// The commitment contents of the NFT held in this output (0 to 40 bytes). T - /// his field is omitted if no NFT is present - commitment: utf8.encode("github.com/mrtnetwork"), - - /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). - /// This field is omitted if no fungible tokens are present. - amount: BigInt.from(160000000) ~/ BigInt.from(4), - bitfield: CashTokenUtils.buildBitfield( - hasAmount: true, - capability: CashTokenCapability.mutable, - hasCommitmentLength: true, - hasNFT: true))), - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: p2pkhAddress.baseAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - token: CashToken( - category: nftCategoryId, - - /// The commitment contents of the NFT held in this output (0 to 40 bytes). T - /// his field is omitted if no NFT is present - commitment: utf8.encode("github.com/mrtnetwork"), - - /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). - /// This field is omitted if no fungible tokens are present. - amount: BigInt.from(160000000) ~/ BigInt.from(4), - bitfield: CashTokenUtils.buildBitfield( - hasAmount: true, - capability: CashTokenCapability.mutable, - hasCommitmentLength: true, - hasNFT: true))), - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - - /// Bitcoin Cash Metadata Registries - /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata - /// we does not create metadata for this token - memo: null, - utxos: utxos, - ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://tbch4.loping.net/tx/caa91b0fea2843a99c3cd7375ac4d3102b6b74a25e52cd866ad7ecc486204f0d + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = + // await ElectrumWebSocketService.connect("wss://tbch4.loping.net:62004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkInP2sh(useBCHP2sh32: true)); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), + // includeTokens: true, + // )); + // // return; + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = elctrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2pkhAddress.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) + // .toList(); + // // return; + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + // if (sumOfUtxo == BigInt.zero) { + // return; + // } + // const String nftCategoryId = + // "3f0d87791e5996aaddbce16c12651dd8b5b881cf7338340504bb7b2c6c08bfc4"; + + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // BitcoinOutput( + // address: p2pkhAddress.baseAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.00004") + BtcUtils.toSatoshi("0.00003")), + // ), + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: p2pkhAddress.baseAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + // token: CashToken( + // category: nftCategoryId, + + // /// The commitment contents of the NFT held in this output (0 to 40 bytes). T + // /// his field is omitted if no NFT is present + // commitment: utf8.encode("github.com/mrtnetwork"), + + // /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). + // /// This field is omitted if no fungible tokens are present. + // amount: BigInt.from(160000000) ~/ BigInt.from(4), + // bitfield: CashTokenUtils.buildBitfield( + // hasAmount: true, + // capability: CashTokenCapability.minting, + // hasCommitmentLength: true, + // hasNFT: true))), + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: p2pkhAddress.baseAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + // token: CashToken( + // category: nftCategoryId, + + // /// The commitment contents of the NFT held in this output (0 to 40 bytes). T + // /// his field is omitted if no NFT is present + // commitment: utf8.encode("github.com/mrtnetwork"), + + // /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). + // /// This field is omitted if no fungible tokens are present. + // amount: BigInt.from(160000000) ~/ BigInt.from(4), + // bitfield: CashTokenUtils.buildBitfield( + // hasAmount: true, + // capability: CashTokenCapability.mutable, + // hasCommitmentLength: true, + // hasNFT: true))), + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: p2pkhAddress.baseAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + // token: CashToken( + // category: nftCategoryId, + + // /// The commitment contents of the NFT held in this output (0 to 40 bytes). T + // /// his field is omitted if no NFT is present + // commitment: utf8.encode("github.com/mrtnetwork"), + + // /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). + // /// This field is omitted if no fungible tokens are present. + // amount: BigInt.from(160000000) ~/ BigInt.from(4), + // bitfield: CashTokenUtils.buildBitfield( + // hasAmount: true, + // capability: CashTokenCapability.mutable, + // hasCommitmentLength: true, + // hasNFT: true))), + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: p2pkhAddress.baseAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + // token: CashToken( + // category: nftCategoryId, + + // /// The commitment contents of the NFT held in this output (0 to 40 bytes). T + // /// his field is omitted if no NFT is present + // commitment: utf8.encode("github.com/mrtnetwork"), + + // /// The number of fungible tokens held in this output (an integer between 1 and 9223372036854775807). + // /// This field is omitted if no fungible tokens are present. + // amount: BigInt.from(160000000) ~/ BigInt.from(4), + // bitfield: CashTokenUtils.buildBitfield( + // hasAmount: true, + // capability: CashTokenCapability.mutable, + // hasCommitmentLength: true, + // hasNFT: true))), + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + + // /// Bitcoin Cash Metadata Registries + // /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata + // /// we does not create metadata for this token + // memo: null, + // utxos: utxos, + // ); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://tbch4.loping.net/tx/caa91b0fea2843a99c3cd7375ac4d3102b6b74a25e52cd866ad7ecc486204f0d } diff --git a/example/lib/bitcoin_cash/p2sh32_spend_example.dart b/example/lib/bitcoin_cash/p2sh32_spend_example.dart index c7af331..18691af 100644 --- a/example/lib/bitcoin_cash/p2sh32_spend_example.dart +++ b/example/lib/bitcoin_cash/p2sh32_spend_example.dart @@ -1,119 +1,118 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:blockchain_utils/blockchain_utils.dart'; /// CHIP-2022-05 Pay-to-Script-Hash-32 (P2SH32) for Bitcoin Cash /// https://bitcoincashresearch.org/t/chip-2022-05-pay-to-script-hash-32-p2sh32-for-bitcoin-cash/806 /// Send funds to a BCH P2SH32 address. void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = await ElectrumWebSocketService.connect( - "wss://chipnet.imaginary.cash:50004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkhAddress()); - - /// Initialize two P2SH32 addresses for receiving funds. - /// bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7 - /// Avoid using `BitcoinCashAddress('address')` when obtaining the address type for `UtxoWithAddress`. - /// This is crucial for spending, as the current P2SH type is needed for unlocking the script. - final p2sh32Example1 = BitcoinCashAddress.fromBaseAddress( - publicKey.toP2pkhInP2sh(useBCHP2sh32: true)); - - /// bchtest:pw054wtjjc70rrvx4ftl4p63gluedyt0qmpgz705f8x3gxygrzarzls7vp2sj - /// Avoid using `BitcoinCashAddress('address')` when obtaining the address type for `UtxoWithAddress`. - /// This is crucial for spending, as the current P2SH type is needed for unlocking the script. - final p2sh32Example2 = BitcoinCashAddress.fromBaseAddress( - publicKey.toP2pkInP2sh(useBCHP2sh32: true)); - - /// 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( - scriptHash: p2sh32Example1.baseAddress.pubKeyHash(), - includeTokens: false, - )); - final example2ElectrumUtxos = - await provider.request(ElectrumScriptHashListUnspent( - scriptHash: p2sh32Example2.baseAddress.pubKeyHash(), - includeTokens: false, - )); - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = [ - ...example2ElectrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2sh32Example2.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), - address: p2pkhAddress.baseAddress))) - .toList(), - ...example1ElectrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2sh32Example1.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), - address: p2pkhAddress.baseAddress))) - .toList() - ]; - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - if (sumOfUtxo == BigInt.zero) { - return; - } - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - BitcoinOutput( - address: p2pkhAddress.baseAddress, - value: BtcUtils.toSatoshi("0.00001"), - ), - - /// change input (sumofutxos - spend) - BitcoinOutput( - address: p2sh32Example1.baseAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.00001") + BtcUtils.toSatoshi("0.00003")), - ), - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - utxos: utxos, - ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://chipnet.imaginary.cash/tx/b76b851ce0374504591db414d7469aadb68649079defb26e44c62e970afda729 + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = await ElectrumWebSocketService.connect( + // "wss://chipnet.imaginary.cash:50004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkhAddress()); + + // /// Initialize two P2SH32 addresses for receiving funds. + // /// bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7 + // /// Avoid using `BitcoinCashAddress('address')` when obtaining the address type for `UtxoWithAddress`. + // /// This is crucial for spending, as the current P2SH type is needed for unlocking the script. + // final p2sh32Example1 = BitcoinCashAddress.fromBaseAddress( + // publicKey.toP2pkhInP2sh(useBCHP2sh32: true)); + + // /// bchtest:pw054wtjjc70rrvx4ftl4p63gluedyt0qmpgz705f8x3gxygrzarzls7vp2sj + // /// Avoid using `BitcoinCashAddress('address')` when obtaining the address type for `UtxoWithAddress`. + // /// This is crucial for spending, as the current P2SH type is needed for unlocking the script. + // final p2sh32Example2 = BitcoinCashAddress.fromBaseAddress( + // publicKey.toP2pkInP2sh(useBCHP2sh32: true)); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2sh32Example1.baseAddress.pubKeyHash(), + // includeTokens: false, + // )); + // final example2ElectrumUtxos = + // await provider.request(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2sh32Example2.baseAddress.pubKeyHash(), + // includeTokens: false, + // )); + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = [ + // ...example2ElectrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2sh32Example2.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), + // address: p2pkhAddress.baseAddress))) + // .toList(), + // ...example1ElectrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2sh32Example1.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), + // address: p2pkhAddress.baseAddress))) + // .toList() + // ]; + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + // if (sumOfUtxo == BigInt.zero) { + // return; + // } + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // BitcoinOutput( + // address: p2pkhAddress.baseAddress, + // value: BtcUtils.toSatoshi("0.00001"), + // ), + + // /// change input (sumofutxos - spend) + // BitcoinOutput( + // address: p2sh32Example1.baseAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.00001") + BtcUtils.toSatoshi("0.00003")), + // ), + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + // utxos: utxos, + // ); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://chipnet.imaginary.cash/tx/b76b851ce0374504591db414d7469aadb68649079defb26e44c62e970afda729 } diff --git a/example/lib/bitcoin_cash/send_ft_token_example.dart b/example/lib/bitcoin_cash/send_ft_token_example.dart index 2e9b82d..d405c18 100644 --- a/example/lib/bitcoin_cash/send_ft_token_example.dart +++ b/example/lib/bitcoin_cash/send_ft_token_example.dart @@ -1,144 +1,144 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:blockchain_utils/blockchain_utils.dart'; /// please make sure read this before create transaction on mainnet //// https://github.com/cashtokens/cashtokens void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = await ElectrumWebSocketService.connect( - "wss://chipnet.imaginary.cash:50004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkInP2sh(useBCHP2sh32: true)); - - /// p2pkh with token address () - final receiver1 = P2pkhAddress.fromHash160( - h160: publicKey.toP2pkhAddress().addressProgram, - type: P2pkhAddressType.p2pkhwt); - - /// 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(), - includeTokens: true, - )); - // return; - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = elctrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) - .toList() - - /// we only filter the utxos for this token or none token utxos - .where((element) => - element.utxo.token?.category == - "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" || - element.utxo.token == null) - .toList(); - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - if (sumOfUtxo == BigInt.zero) { - return; - } - - /// CashToken{bitfield: 16, commitment: null, amount: 2000, category: 4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430} - final CashToken token = elctrumUtxos - .firstWhere((e) => - e.token?.category == - "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") - .token!; - - /// sum of ft token amounts with category "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" - final sumofTokenUtxos = utxos - .where((element) => - element.utxo.token?.category == - "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") - .fold( - BigInt.zero, - (previousValue, element) => - previousValue + element.utxo.token!.amount); - - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - /// change address for bch values (sum of bch amout - (outputs amount + fee)) - BitcoinOutput( - address: p2pkhAddress.baseAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.00002") + BtcUtils.toSatoshi("0.00003")), - ), - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: receiver1, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - - /// clone the token with new token amount for output1 (15 amount of category) - token: token.copyWith(amount: BigInt.from(15))), - - /// another change token value to change account like bch - BitcoinTokenOutput( - utxoHash: utxos.first.utxo.txHash, - address: p2pkhAddress.baseAddress, - - /// for a token-bearing output (600-700) satoshi - /// hard-coded value which is expected to be enough to allow - /// all conceivable token-bearing UTXOs (1000 satoshi) - value: BtcUtils.toSatoshi("0.00001"), - - /// clone the token with new token amount for output1 (15 amount of category) - token: token.copyWith(amount: sumofTokenUtxos - BigInt.from(15))), - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - - /// Bitcoin Cash Metadata Registries - /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata - /// we does not create metadata for this token - memo: null, - utxos: utxos, - ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://chipnet.imaginary.cash/tx/97030c1236a024de7cad7ceadf8571833029c508e016bcc8173146317e367ae6 + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = await ElectrumWebSocketService.connect( + // "wss://chipnet.imaginary.cash:50004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkInP2sh(useBCHP2sh32: true)); + + // /// p2pkh with token address () + // final receiver1 = P2pkhAddress.fromHash160( + // h160: publicKey.toP2pkhAddress().addressProgram, + // type: P2pkhAddressType.p2pkhwt); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), + // includeTokens: true, + // )); + // // return; + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = elctrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2pkhAddress.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) + // .toList() + + // /// we only filter the utxos for this token or none token utxos + // .where((element) => + // element.utxo.token?.category == + // "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" || + // element.utxo.token == null) + // .toList(); + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + // if (sumOfUtxo == BigInt.zero) { + // return; + // } + + // /// CashToken{bitfield: 16, commitment: null, amount: 2000, category: 4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430} + // final CashToken token = elctrumUtxos + // .firstWhere((e) => + // e.token?.category == + // "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") + // .token!; + + // /// sum of ft token amounts with category "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430" + // final sumofTokenUtxos = utxos + // .where((element) => + // element.utxo.token?.category == + // "4e7873d4529edfd2c6459139257042950230baa9297f111b8675829443f70430") + // .fold( + // BigInt.zero, + // (previousValue, element) => + // previousValue + element.utxo.token!.amount); + + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // /// change address for bch values (sum of bch amout - (outputs amount + fee)) + // BitcoinOutput( + // address: p2pkhAddress.baseAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.00002") + BtcUtils.toSatoshi("0.00003")), + // ), + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: receiver1, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + + // /// clone the token with new token amount for output1 (15 amount of category) + // token: token.copyWith(amount: BigInt.from(15))), + + // /// another change token value to change account like bch + // BitcoinTokenOutput( + // utxoHash: utxos.first.utxo.txHash, + // address: p2pkhAddress.baseAddress, + + // /// for a token-bearing output (600-700) satoshi + // /// hard-coded value which is expected to be enough to allow + // /// all conceivable token-bearing UTXOs (1000 satoshi) + // value: BtcUtils.toSatoshi("0.00001"), + + // /// clone the token with new token amount for output1 (15 amount of category) + // token: token.copyWith(amount: sumofTokenUtxos - BigInt.from(15))), + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + + // /// Bitcoin Cash Metadata Registries + // /// pleas see https://cashtokens.org/docs/bcmr/chip/ for how to create cash metadata + // /// we does not create metadata for this token + // memo: null, + // utxos: utxos, + // ); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://chipnet.imaginary.cash/tx/97030c1236a024de7cad7ceadf8571833029c508e016bcc8173146317e367ae6 } diff --git a/example/lib/bitcoin_cash/transfer_bch_example.dart b/example/lib/bitcoin_cash/transfer_bch_example.dart index 12fdbd2..6b4cbf3 100644 --- a/example/lib/bitcoin_cash/transfer_bch_example.dart +++ b/example/lib/bitcoin_cash/transfer_bch_example.dart @@ -1,104 +1,104 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:blockchain_utils/blockchain_utils.dart'; /// CHIP-2022-05 Pay-to-Script-Hash-32 (P2SH32) for Bitcoin Cash /// https://bitcoincashresearch.org/t/chip-2022-05-pay-to-script-hash-32-p2sh32-for-bitcoin-cash/806 /// Send funds to a BCH P2SH32 address. void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = await ElectrumWebSocketService.connect( - "wss://chipnet.imaginary.cash:50004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkhAddress()); - - /// Initialize two P2SH32 addresses for receiving funds. - final p2sh32Example1 = BitcoinCashAddress( - "bchtest:pw054wtjjc70rrvx4ftl4p63gluedyt0qmpgz705f8x3gxygrzarzls7vp2sj", - network: network); - final p2sh32Example2 = BitcoinCashAddress( - "bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7", - network: network); - - /// 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(), - includeTokens: false, - )); - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = elctrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) - .toList(); - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - if (sumOfUtxo == BigInt.zero) { - return; - } - - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - /// change input (sumofutxos - spend) - BitcoinOutput( - address: p2pkhAddress.baseAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.0001") + - BtcUtils.toSatoshi("0.0001") + - BtcUtils.toSatoshi("0.00003")), - ), - BitcoinOutput( - address: p2sh32Example1.baseAddress, - value: BtcUtils.toSatoshi("0.0001"), - ), - BitcoinOutput( - address: p2sh32Example2.baseAddress, - value: BtcUtils.toSatoshi("0.0001"), - ), - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - utxos: utxos, - ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://chipnet.imaginary.cash/tx/9e534f8a64f76b1af5ccf2522392697f2242fd215206a458cfe286bca4a3ec0a + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = await ElectrumWebSocketService.connect( + // "wss://chipnet.imaginary.cash:50004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkhAddress()); + + // /// Initialize two P2SH32 addresses for receiving funds. + // final p2sh32Example1 = BitcoinCashAddress( + // "bchtest:pw054wtjjc70rrvx4ftl4p63gluedyt0qmpgz705f8x3gxygrzarzls7vp2sj", + // network: network); + // final p2sh32Example2 = BitcoinCashAddress( + // "bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7", + // network: network); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), + // includeTokens: false, + // )); + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = elctrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2pkhAddress.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) + // .toList(); + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + // if (sumOfUtxo == BigInt.zero) { + // return; + // } + + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // /// change input (sumofutxos - spend) + // BitcoinOutput( + // address: p2pkhAddress.baseAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.0001") + + // BtcUtils.toSatoshi("0.0001") + + // BtcUtils.toSatoshi("0.00003")), + // ), + // BitcoinOutput( + // address: p2sh32Example1.baseAddress, + // value: BtcUtils.toSatoshi("0.0001"), + // ), + // BitcoinOutput( + // address: p2sh32Example2.baseAddress, + // value: BtcUtils.toSatoshi("0.0001"), + // ), + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + // utxos: utxos, + // ); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://chipnet.imaginary.cash/tx/9e534f8a64f76b1af5ccf2522392697f2242fd215206a458cfe286bca4a3ec0a } diff --git a/example/lib/global/bch_example.dart b/example/lib/global/bch_example.dart index 3f5bdd4..277282b 100644 --- a/example/lib/global/bch_example.dart +++ b/example/lib/global/bch_example.dart @@ -1,101 +1,101 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/electrum/electrum_websocket_service.dart'; +// import 'package:bitcoin_base/bitcoin_base.dart'; +// import 'package:blockchain_utils/blockchain_utils.dart'; void main() async { - /// connect to electrum service with websocket - /// please see `services_examples` folder for how to create electrum websocket service - final service = await ElectrumWebSocketService.connect( - "wss://chipnet.imaginary.cash:50004"); - - /// create provider with service - final provider = ElectrumApiProvider(service); - - /// initialize private key - final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( - "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); - - /// public key - final publicKey = privateKey.getPublic(); - - /// network - const network = BitcoinCashNetwork.testnet; - - /// 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.toP2pkhAddress()); - - /// Initialize two P2SH32 addresses for receiving funds. - final p2sh32Example1 = BitcoinCashAddress( - "bchtest:pw054wtjjc70rrvx4ftl4p63gluedyt0qmpgz705f8x3gxygrzarzls7vp2sj", - network: network); - final p2sh32Example2 = BitcoinCashAddress( - "bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7", - network: network); - - /// 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(), - includeTokens: false, - )); - - /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. - final List utxos = elctrumUtxos - .map((e) => UtxoWithAddress( - utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) - .toList(); - - /// som of utxos in satoshi - final sumOfUtxo = utxos.sumOfUtxosValue(); - if (sumOfUtxo == BigInt.zero) { - return; - } - - final bchTransaction = ForkedTransactionBuilder( - outputs: [ - /// change input (sumofutxos - spend) - BitcoinOutput( - address: p2pkhAddress.baseAddress, - value: sumOfUtxo - - (BtcUtils.toSatoshi("0.0001") + - BtcUtils.toSatoshi("0.0001") + - BtcUtils.toSatoshi("0.00003")), - ), - BitcoinOutput( - address: p2sh32Example1.baseAddress, - value: BtcUtils.toSatoshi("0.0001"), - ), - BitcoinOutput( - address: p2sh32Example2.baseAddress, - value: BtcUtils.toSatoshi("0.0001"), - ), - ], - fee: BtcUtils.toSatoshi("0.00003"), - network: network, - utxos: utxos, - ); - final transaaction = - bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { - return privateKey.signInput(trDigest, sigHash: sighash); - }); - - /// transaction ID - transaaction.txId(); - - /// for calculation fee - transaaction.getSize(); - - /// raw of encoded transaction in hex - final transactionRaw = transaaction.toHex(); - - /// send transaction to network - await provider - .request(ElectrumBroadCastTransaction(transactionRaw: transactionRaw)); - - /// done! check the transaction in block explorer - /// https://chipnet.imaginary.cash/tx/9e534f8a64f76b1af5ccf2522392697f2242fd215206a458cfe286bca4a3ec0a + // /// connect to electrum service with websocket + // /// please see `services_examples` folder for how to create electrum websocket service + // final service = await ElectrumWebSocketService.connect( + // "wss://chipnet.imaginary.cash:50004"); + + // /// create provider with service + // final provider = ElectrumProvider(service); + + // /// initialize private key + // final privateKey = ECPrivate.fromBytes(BytesUtils.fromHexString( + // "f9061c5cb343c6b6a73900ee29509bb0bd2213319eea46d2f2a431068c9da06b")); + + // /// public key + // final publicKey = privateKey.getPublic(); + + // /// network + // const network = BitcoinCashNetwork.testnet; + + // /// 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.toP2pkhAddress()); + + // /// Initialize two P2SH32 addresses for receiving funds. + // final p2sh32Example1 = BitcoinCashAddress( + // "bchtest:pw054wtjjc70rrvx4ftl4p63gluedyt0qmpgz705f8x3gxygrzarzls7vp2sj", + // network: network); + // final p2sh32Example2 = BitcoinCashAddress( + // "bchtest:pvw39llgap0a4vm8jn9sjsvfsthah4wgemjlh6epdtzr3pl2fqtmsn3s4vcm7", + // network: network); + + // /// 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(ElectrumRequestScriptHashListUnspent( + // scriptHash: p2pkhAddress.baseAddress.pubKeyHash(), + // includeTokens: false, + // )); + + // /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. + // final List utxos = elctrumUtxos + // .map((e) => UtxoWithAddress( + // utxo: e.toUtxo(p2pkhAddress.type), + // ownerDetails: UtxoAddressDetails( + // publicKey: publicKey.toHex(), address: p2pkhAddress.baseAddress))) + // .toList(); + + // /// som of utxos in satoshi + // final sumOfUtxo = utxos.sumOfUtxosValue(); + // if (sumOfUtxo == BigInt.zero) { + // return; + // } + + // final bchTransaction = ForkedTransactionBuilder( + // outputs: [ + // /// change input (sumofutxos - spend) + // BitcoinOutput( + // address: p2pkhAddress.baseAddress, + // value: sumOfUtxo - + // (BtcUtils.toSatoshi("0.0001") + + // BtcUtils.toSatoshi("0.0001") + + // BtcUtils.toSatoshi("0.00003")), + // ), + // BitcoinOutput( + // address: p2sh32Example1.baseAddress, + // value: BtcUtils.toSatoshi("0.0001"), + // ), + // BitcoinOutput( + // address: p2sh32Example2.baseAddress, + // value: BtcUtils.toSatoshi("0.0001"), + // ), + // ], + // fee: BtcUtils.toSatoshi("0.00003"), + // network: network, + // utxos: utxos, + // ); + // final transaaction = + // bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + // return privateKey.signInput(trDigest, sigHash: sighash); + // }); + + // /// transaction ID + // transaaction.txId(); + + // /// for calculation fee + // transaaction.getSize(); + + // /// raw of encoded transaction in hex + // final transactionRaw = transaaction.toHex(); + + // /// send transaction to network + // await provider.request( + // ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); + + // /// done! check the transaction in block explorer + // /// https://chipnet.imaginary.cash/tx/9e534f8a64f76b1af5ccf2522392697f2242fd215206a458cfe286bca4a3ec0a } diff --git a/example/lib/global/btc_examples_multisig_taproot_legacy_uncomp.dart b/example/lib/global/btc_examples_multisig_taproot_legacy_uncomp.dart new file mode 100644 index 0000000..b0c2350 --- /dev/null +++ b/example/lib/global/btc_examples_multisig_taproot_legacy_uncomp.dart @@ -0,0 +1,124 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; + +void main() async { + final one = ECPrivate.fromBytes(List.filled(32, 12)); + final two = ECPrivate.fromBytes(List.filled(32, 13)); + final three = ECPrivate.fromBytes(List.filled(32, 14)); + final four = ECPrivate.fromBytes(List.filled(32, 15)); + final five = ECPrivate.fromBytes(List.filled(32, 16)); + final six = ECPrivate.fromBytes(List.filled(32, 17)); + final seven = ECPrivate.fromBytes(List.filled(32, 18)); + final eight = ECPrivate.fromBytes(List.filled(32, 19)); + final Map keys = { + for (final i in [one, two, three, four, five, six, seven, eight]) i.getPublic().toHex(): i + }; + final account = MultiSignatureAddress(threshold: 8, signers: [ + MultiSignatureSigner( + publicKey: one.getPublic().toHex(mode: PublicKeyType.uncompressed), weight: 1), + MultiSignatureSigner(publicKey: two.getPublic().toHex(), weight: 1), + MultiSignatureSigner(publicKey: three.getPublic().toHex(), weight: 1), + MultiSignatureSigner( + publicKey: four.getPublic().toHex(mode: PublicKeyType.uncompressed), weight: 1), + MultiSignatureSigner(publicKey: five.getPublic().toHex(), weight: 1), + MultiSignatureSigner(publicKey: six.getPublic().toHex(), weight: 1), + MultiSignatureSigner( + publicKey: seven.getPublic().toHex(mode: PublicKeyType.uncompressed), weight: 1), + MultiSignatureSigner(publicKey: eight.getPublic().toHex(), weight: 1), + ]); + + /// connect to electrum service with websocket + /// please see `services_examples` folder for how to create electrum websocket service + final service = ElectrumSSLService.connect(Uri.parse("testnet4-electrumx.wakiyamap.dev:51002")); + + /// create provider with service + final provider = await ElectrumProvider.connect(service); + + final addrOne = one.getPublic().toP2pkAddress(mode: PublicKeyType.uncompressed); + + final addrTwo = two.getPublic().toAddress(mode: PublicKeyType.uncompressed); + + final addrThree = three.getPublic().toP2pkInP2sh(mode: PublicKeyType.uncompressed); + final addrFour = four.getPublic().toP2pkhInP2sh(mode: PublicKeyType.uncompressed); + final addrFive = four.getPublic().toSegwitAddress(); + final addrSix = account.toP2shAddress(); + final addr7 = eight.getPublic().toTaprootAddress(); + final addr8 = eight.getPublic().toP2wshAddress(); + final addr9 = eight.getPublic().toP2wshInP2sh(); + final List pubkys = [ + one.getPublic().toHex(mode: PublicKeyType.uncompressed), + two.getPublic().toHex(mode: PublicKeyType.uncompressed), + three.getPublic().toHex(mode: PublicKeyType.uncompressed), + four.getPublic().toHex(mode: PublicKeyType.uncompressed), + four.getPublic().toHex(), + four.getPublic().toHex(), + eight.getPublic().toHex(), + eight.getPublic().toHex(), + eight.getPublic().toHex(), + eight.getPublic().toHex(), + ]; + final addresses = [ + one.getPublic().toP2pkAddress(mode: PublicKeyType.uncompressed), + two.getPublic().toAddress(mode: PublicKeyType.uncompressed), + three.getPublic().toP2pkInP2sh(mode: PublicKeyType.uncompressed), + four.getPublic().toP2pkhInP2sh(mode: PublicKeyType.uncompressed), + four.getPublic().toSegwitAddress(), + four.getPublic().toP2wshInP2sh(), + addrSix, + addr7, + addr8, + addr9 + ]; + List utxos = []; + for (int i = 0; i < addresses.length; i++) { + final address = addresses[i]; + final elctrumUtxos = await provider.request(ElectrumRequestScriptHashListUnspent( + scriptHash: address.pubKeyHash(), includeTokens: false)); + if (elctrumUtxos.isEmpty) continue; + if (i == 6) { + utxos.addAll(elctrumUtxos.map((e) => UtxoWithAddress( + utxo: e.toUtxo(address.type), + ownerDetails: + UtxoAddressDetails.multiSigAddress(multiSigAddress: account, address: address)))); + continue; + } + utxos.addAll(elctrumUtxos + .map((e) => UtxoWithAddress( + utxo: e.toUtxo(address.type), + ownerDetails: UtxoAddressDetails(publicKey: pubkys[i], address: address))) + .toList()); + } + + final sumOfUtxo = utxos.sumOfUtxosValue(); + + if (sumOfUtxo == BigInt.zero) { + return; + } + final change = sumOfUtxo - (BigInt.from(1000) * BigInt.from(11) + BigInt.from(4295)); + final bchTransaction = BitcoinTransactionBuilder(outputs: [ + /// change input (sumofutxos - spend) + BitcoinOutput(address: addrOne, value: change), + BitcoinOutput(address: addrOne, value: BigInt.from(1000)), + BitcoinOutput(address: addrTwo, value: BigInt.from(1000)), + BitcoinOutput(address: addrThree, value: BigInt.from(1000)), + BitcoinOutput(address: addrFour, value: BigInt.from(1000)), + BitcoinOutput(address: addrFour, value: BigInt.from(1000)), + BitcoinOutput(address: addrFive, value: BigInt.from(1000)), + BitcoinOutput(address: addrSix, value: BigInt.from(1000)), + BitcoinOutput(address: addrSix, value: BigInt.from(1000)), + BitcoinOutput(address: addr7, value: BigInt.from(1000)), + BitcoinOutput(address: addr8, value: BigInt.from(1000)), + BitcoinOutput(address: addr9, value: BigInt.from(1000)), + ], fee: BigInt.from(4295), network: BitcoinNetwork.testnet, utxos: utxos); + final transaaction = bchTransaction.buildTransaction((trDigest, utxo, publicKey, sighash) { + final pk = ECPublic.fromHex(publicKey); + if (utxo.utxo.isP2tr) { + return keys[pk.toHex()]!.signTapRoot(trDigest, sighash: sighash); + } + return keys[pk.toHex()]!.signInput(trDigest, sigHash: sighash); + }); + + final transactionRaw = transaaction.toHex(); + await provider.request(ElectrumRequestBroadCastTransaction(transactionRaw: transactionRaw)); +} + +/// https://mempool.space/testnet4/tx/a7f08f07739de45a6a4f8871f8e6ad79e0aefbc940086df76571354ba22263fa diff --git a/example/lib/global/old_examples/bitcoin_example.dart b/example/lib/global/old_examples/bitcoin_example.dart index 3ce647d..ff62a2a 100644 --- a/example/lib/global/old_examples/bitcoin_example.dart +++ b/example/lib/global/old_examples/bitcoin_example.dart @@ -2,7 +2,6 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/explorer_service/explorer_service.dart'; /// Calculates the change value based on the sum of all provided values. /// @@ -19,8 +18,7 @@ import 'package:example/services_examples/explorer_service/explorer_service.dart /// Returns: /// - The change value. BigInt _changeValue(BigInt sum, List all) { - final sumAll = all.fold( - BigInt.zero, (previousValue, element) => previousValue + element); + final sumAll = all.fold(BigInt.zero, (previousValue, element) => previousValue + element); final remind = sum - sumAll; if (remind < BigInt.zero) { @@ -61,27 +59,25 @@ void _spendFromP2pkhTo10DifferentType() async { final examplePublicKey2 = examplePrivateKey.getPublic(); /// Define transaction outputs - final out1 = P2pkhAddress.fromAddress( - address: "msxiCJXD2WB43wK2PpTUvoqQLF7ZP98qqM", network: network); + final out1 = + P2pkhAddress.fromAddress(address: "msxiCJXD2WB43wK2PpTUvoqQLF7ZP98qqM", network: network); final out2 = P2trAddress.fromAddress( - address: "tb1plq65drqavf93wf63d8g7d8ypuzaargd5h9d35u05ktrcwxq4a6ss0gpvrt", - network: network); + address: "tb1plq65drqavf93wf63d8g7d8ypuzaargd5h9d35u05ktrcwxq4a6ss0gpvrt", network: network); final out3 = P2wpkhAddress.fromAddress( address: "tb1q3zqgu9j368wgk8u5f9vtmkdwq8geetdxry690d", network: network); - final out4 = P2pkAddress.fromPubkey(pubkey: examplePublicKey.publicKey.toHex()); - final out5 = P2shAddress.fromAddress( - address: "2N5hVdETdJMwLDxxttfqeWgMuny6K4SYGSc", network: network); - final out6 = P2shAddress.fromAddress( - address: "2NDAUpeUB1kGAQET8SojF8seXNrk3uudtCb", network: network); - final out7 = P2shAddress.fromAddress( - address: "2NE9CYdxju2iEAfR4FMdKPUcZbnKcfCiLhM", network: network); - final out8 = P2shAddress.fromAddress( - address: "2MwGRf8wNJsaYKdigqPwikPpg9JAT2faaPB", network: network); + final out4 = P2pkAddress.fromPubkey(pubkey: ECPublic.fromBip32(examplePublicKey.publicKey)); + final out5 = + P2shAddress.fromAddress(address: "2N5hVdETdJMwLDxxttfqeWgMuny6K4SYGSc", network: network); + final out6 = + P2shAddress.fromAddress(address: "2NDAUpeUB1kGAQET8SojF8seXNrk3uudtCb", network: network); + final out7 = + P2shAddress.fromAddress(address: "2NE9CYdxju2iEAfR4FMdKPUcZbnKcfCiLhM", network: network); + final out8 = + P2shAddress.fromAddress(address: "2MwGRf8wNJsaYKdigqPwikPpg9JAT2faaPB", network: network); final out9 = P2wshAddress.fromAddress( - address: "tb1qes3upam2nv3rc6s38tqgk0cqh6dlycvk6cjydyvpx9zlumh4h4lsjq26p8", - network: network); - final out10 = P2shAddress.fromAddress( - address: "2N2aRKjTQ3uzgUSLWFQAUDvKLnKCiBfCSAh", network: network); + address: "tb1qes3upam2nv3rc6s38tqgk0cqh6dlycvk6cjydyvpx9zlumh4h4lsjq26p8", network: network); + final out10 = + P2shAddress.fromAddress(address: "2N2aRKjTQ3uzgUSLWFQAUDvKLnKCiBfCSAh", network: network); /// Calculate the change value for the transaction final change = _changeValue( @@ -130,8 +126,7 @@ void _spendFromP2pkhTo10DifferentType() async { /// Create a UTXO using a BitcoinUtxo with specific details utxo: BitcoinUtxo( /// Transaction hash uniquely identifies the referenced transaction - txHash: - "b06f4ed0b49a5092a9ea206553ddc5fc469be694d0d28c95598c653e66cdeb5e", + txHash: "b06f4ed0b49a5092a9ea206553ddc5fc469be694d0d28c95598c653e66cdeb5e", /// Value represents the amount of the UTXO in satoshis. value: BigInt.from(250000), @@ -145,19 +140,16 @@ void _spendFromP2pkhTo10DifferentType() async { /// Include owner details with the public key and address associated with the UTXO ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey2.toHex(), - address: examplePublicKey2.toP2pkhAddress())), + publicKey: examplePublicKey2.toHex(), address: examplePublicKey2.toP2pkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "6ff0bdb2966f62f5e202c924e1cab1368b0258833e48986cc0a70fbca624ba93", + txHash: "6ff0bdb2966f62f5e202c924e1cab1368b0258833e48986cc0a70fbca624ba93", value: BigInt.from(812830), vout: 0, scriptType: examplePublicKey2.toP2pkhAddress().type, ), ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey2.toHex(), - address: examplePublicKey2.toP2pkhAddress())), + publicKey: examplePublicKey2.toHex(), address: examplePublicKey2.toP2pkhAddress())), ]); /// Build the transaction by invoking the buildTransaction method on the BitcoinTransactionBuilder @@ -166,7 +158,7 @@ void _spendFromP2pkhTo10DifferentType() async { /// and sign the transaction digest to construct the unlocking script. if (publicKey == examplePublicKey2.toHex()) { - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { return examplePrivateKey.signTapRoot(trDigest); } return examplePrivateKey.signInput(trDigest, sigHash: sighash); @@ -181,7 +173,7 @@ void _spendFromP2pkhTo10DifferentType() async { /// Calculate the size of the transaction in bytes. /// You can determine the transaction fee by multiplying the transaction size /// Formula: transaction fee = (transaction size in bytes * fee rate in bytes) - final size = tr.hasSegwit ? tr.getVSize() : tr.getSize(); + final size = tr.hasWitness ? tr.getVSize() : tr.getSize(); /// broadcast transaction /// https://mempool.space/testnet/tx/05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258 @@ -249,8 +241,8 @@ void _spendFrom10DifferentTypeToP2pkh() async { /// outputs /// make sure pass network to address for validate, before sending create transaction - final out1 = P2pkhAddress.fromAddress( - address: "n4bkvTyU1dVdzsrhWBqBw8fEMbHjJvtmJR", network: network); + final out1 = + P2pkhAddress.fromAddress(address: "n4bkvTyU1dVdzsrhWBqBw8fEMbHjJvtmJR", network: network); final builder = BitcoinTransactionBuilder( @@ -272,8 +264,7 @@ void _spendFrom10DifferentTypeToP2pkh() async { UtxoWithAddress( utxo: BitcoinUtxo( /// Transaction hash uniquely identifies the referenced transaction - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", /// Value represents the amount of the UTXO in satoshis. value: BtcUtils.toSatoshi("0.001"), @@ -291,8 +282,7 @@ void _spendFrom10DifferentTypeToP2pkh() async { address: childKey1PublicKey.toP2pkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 1, scriptType: childKey1PublicKey.toTaprootAddress().type, @@ -302,8 +292,7 @@ void _spendFrom10DifferentTypeToP2pkh() async { address: childKey1PublicKey.toTaprootAddress())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 2, scriptType: childKey1PublicKey.toP2wpkhAddress().type, @@ -313,53 +302,44 @@ void _spendFrom10DifferentTypeToP2pkh() async { address: childKey1PublicKey.toP2wpkhAddress())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 3, scriptType: examplePublicKey.toP2pkAddress().type, ), ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey.toHex(), - address: examplePublicKey.toP2pkAddress())), + publicKey: examplePublicKey.toHex(), address: examplePublicKey.toP2pkAddress())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 4, scriptType: examplePublicKey.toP2pkInP2sh().type, ), ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey.toHex(), - address: examplePublicKey.toP2pkInP2sh())), + publicKey: examplePublicKey.toHex(), address: examplePublicKey.toP2pkInP2sh())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 5, scriptType: examplePublicKey.toP2pkhInP2sh().type, ), ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey.toHex(), - address: examplePublicKey.toP2pkhInP2sh())), + publicKey: examplePublicKey.toHex(), address: examplePublicKey.toP2pkhInP2sh())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 6, scriptType: examplePublicKey.toP2wpkhInP2sh().type, blockHeight: 0, ), ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey.toHex(), - address: examplePublicKey.toP2wpkhInP2sh())), + publicKey: examplePublicKey.toHex(), address: examplePublicKey.toP2wpkhInP2sh())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 7, scriptType: msig.toP2shAddress().type, @@ -371,28 +351,24 @@ void _spendFrom10DifferentTypeToP2pkh() async { multiSigAddress: msig, address: msig.toP2shAddress())), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.001"), vout: 8, scriptType: msig.toP2wshAddress(network: network).type, blockHeight: 0, ), ownerDetails: UtxoAddressDetails.multiSigAddress( - multiSigAddress: msig, - address: msig.toP2wshAddress(network: network))), + multiSigAddress: msig, address: msig.toP2wshAddress(network: network))), UtxoWithAddress( utxo: BitcoinUtxo( - txHash: - "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", + txHash: "05411dce1a1c9e3f44b54413bdf71e7ab3eff1e2f94818a3568c39814c27b258", value: BtcUtils.toSatoshi("0.0015783"), vout: 9, scriptType: msig2.toP2wshInP2shAddress(network: network).type, blockHeight: 0, ), ownerDetails: UtxoAddressDetails.multiSigAddress( - multiSigAddress: msig2, - address: msig2.toP2wshInP2shAddress(network: network))), + multiSigAddress: msig2, address: msig2.toP2wshInP2shAddress(network: network))), ]); /// Build the transaction by invoking the buildTransaction method on the BitcoinTransactionBuilder @@ -400,19 +376,19 @@ void _spendFrom10DifferentTypeToP2pkh() async { /// For each input in the transaction, locate the corresponding private key /// and sign the transaction digest to construct the unlocking script. if (publicKey == childKey1PublicKey.toHex()) { - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { return childKey1PrivateKey.signTapRoot(trDigest, sighash: sighash); } return childKey1PrivateKey.signInput(trDigest, sigHash: sighash); } if (publicKey == examplePublicKey.toHex()) { - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { return childKey2PrivateKey.signTapRoot(trDigest, sighash: sighash); } return childKey2PrivateKey.signInput(trDigest, sigHash: sighash); } if (publicKey == examplePublicKey2.toHex()) { - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { return examplePrivateKey.signTapRoot(trDigest, sighash: sighash); } return examplePrivateKey.signInput(trDigest, sigHash: sighash); @@ -427,7 +403,7 @@ void _spendFrom10DifferentTypeToP2pkh() async { /// Calculate the size of the transaction in bytes. /// You can determine the transaction fee by multiplying the transaction size /// Formula: transaction fee = (transaction size in bytes * fee rate in bytes) - final size = tr.hasSegwit ? tr.getVSize() : tr.getSize(); + final size = tr.hasWitness ? tr.getVSize() : tr.getSize(); /// broadcast transaction /// https://mempool.space/testnet/tx/3e697e0993a6882689ff9b66ff73cdf53e4a3029664ec4a516da2b291e1cd8a6 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 8e66f3a..e388efa 100644 --- a/example/lib/global/old_examples/litecoin_example/litecoin_example.dart +++ b/example/lib/global/old_examples/litecoin_example/litecoin_example.dart @@ -141,7 +141,7 @@ void _spendLTCP2pkhAddress() async { /// Calculate the size of the transaction in bytes. /// You can determine the transaction fee by multiplying the transaction size /// Formula: transaction fee = (transaction size in bytes * fee rate in bytes) - final size = tr.hasSegwit ? tr.getVSize() : tr.getSize(); + final size = tr.hasWitness ? tr.getVSize() : tr.getSize(); /// broadcast transaction await _broadcastTransaction(tr.serialize()); @@ -287,7 +287,7 @@ void _spendFrom2P2shAddressAndOneMultiSigP2shAddress() async { /// Calculate the size of the transaction in bytes. /// You can determine the transaction fee by multiplying the transaction size /// Formula: transaction fee = (transaction size in bytes * fee rate in bytes) - final size = tr.hasSegwit ? tr.getVSize() : tr.getSize(); + final size = tr.hasWitness ? tr.getVSize() : tr.getSize(); /// broadcast transaction await _broadcastTransaction(tr.serialize()); @@ -399,7 +399,7 @@ void _spendFromNestedSegwitP2WPKHInP2SH() async { /// Calculate the size of the transaction in bytes. /// You can determine the transaction fee by multiplying the transaction size /// Formula: transaction fee = (transaction size in bytes * fee rate in bytes) - final size = tr.hasSegwit ? tr.getVSize() : tr.getSize(); + final size = tr.hasWitness ? tr.getVSize() : tr.getSize(); /// broadcast transaction await _broadcastTransaction(tr.serialize()); @@ -519,7 +519,7 @@ void _spendFromSegwitP2WPKHAddress() async { /// Calculate the size of the transaction in bytes. /// You can determine the transaction fee by multiplying the transaction size /// Formula: transaction fee = (transaction size in bytes * fee rate in bytes) - final size = tr.hasSegwit ? tr.getVSize() : tr.getSize(); + final size = tr.hasWitness ? tr.getVSize() : tr.getSize(); /// broadcast transaction await _broadcastTransaction(tr.serialize()); 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 3a17df5..b43c85c 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 @@ -6,9 +6,7 @@ BtcTransaction buildP2wpkTransaction({ required List utxo, }) { // We define transaction inputs by specifying the transaction ID and index. - final txin = utxo - .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) - .toList(); + final txin = utxo.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)).toList(); // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script // for the transaction inputs. In traditional non-SegWit transactions, // the unlocking script is part of the scriptSig field, which contains @@ -26,14 +24,12 @@ BtcTransaction buildP2wpkTransaction({ // It includes the recipient's Bitcoin address (encoded in a specific way) // and can also include additional conditions or requirements based on the type of script used. final List txOut = receiver - .map((e) => - TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .map((e) => TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) .toList(); // create BtcTransaction instance with inputs, outputs and segwit // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. - BtcTransaction tx = - BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + BtcTransaction tx = BtcTransaction(inputs: txin, outputs: txOut); for (int i = 0; i < txin.length; i++) { // For SegWit transactions (excluding P2TR), we use the 'getTransactionSegwitDigit' method @@ -46,8 +42,7 @@ BtcTransaction buildP2wpkTransaction({ // amount of utxo amount: utxo[i].utxo.value); // sign transaction - final signedTx = - sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.SIGHASH_ALL); + final signedTx = sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.sighashAll); // create unlock script @@ -57,8 +52,7 @@ BtcTransaction buildP2wpkTransaction({ // P2WSH (Pay-to-Witness-Script-Hash): Similarly, for P2WSH addresses, you create SegWit transactions, // and the witness data (signatures and script) is separated from the transaction data. - final p2wpkhWitness = - TxWitnessInput(stack: [signedTx, utxo[i].public().toHex()]); + final p2wpkhWitness = TxWitnessInput(stack: [signedTx, utxo[i].public().toHex()]); witnesses.add(p2wpkhWitness); } tx = tx.copyWith(witnesses: witnesses); @@ -71,9 +65,7 @@ BtcTransaction buildP2WSHTransaction({ required List utxo, }) { // We define transaction inputs by specifying the transaction ID and index. - final txin = utxo - .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) - .toList(); + final txin = utxo.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)).toList(); // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script // for the transaction inputs. In traditional non-SegWit transactions, @@ -92,14 +84,12 @@ BtcTransaction buildP2WSHTransaction({ // It includes the recipient's Bitcoin address (encoded in a specific way) // and can also include additional conditions or requirements based on the type of script used. final List txOut = receiver - .map((e) => - TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .map((e) => TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) .toList(); // create BtcTransaction instance with inputs, outputs and segwit // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. - BtcTransaction tx = - BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + BtcTransaction tx = BtcTransaction(inputs: txin, outputs: txOut); for (int i = 0; i < txin.length; i++) { // For SegWit transactions (excluding P2TR), we use the 'getTransactionSegwitDigit' method // to obtain the input digest for signing. @@ -112,8 +102,8 @@ BtcTransaction buildP2WSHTransaction({ amount: utxo[i].utxo.value); // sign transaction - final signedTx = sign(txDigit, utxo[i].public().toP2wshRedeemScript().toHex(), - BitcoinOpCodeConst.SIGHASH_ALL); + final signedTx = sign( + txDigit, utxo[i].public().toP2wshRedeemScript().toHex(), BitcoinOpCodeConst.sighashAll); // create unlock script @@ -123,8 +113,7 @@ BtcTransaction buildP2WSHTransaction({ // P2WSH (Pay-to-Witness-Script-Hash): Similarly, for P2WSH addresses, you create SegWit transactions, // and the witness data (signatures and script) is separated from the transaction data. - final p2wshWitness = - TxWitnessInput(stack: ['', signedTx, utxo[i].public().toHex()]); + final p2wshWitness = TxWitnessInput(stack: ['', signedTx, utxo[i].public().toHex()]); witnesses.add(p2wshWitness); } @@ -138,9 +127,7 @@ BtcTransaction buildP2pkhTransaction({ required List utxo, }) { // We define transaction inputs by specifying the transaction ID and index. - final txin = utxo - .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) - .toList(); + final txin = utxo.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)).toList(); // Create transaction outputs // Parameters @@ -151,12 +138,11 @@ BtcTransaction buildP2pkhTransaction({ // It includes the recipient's Bitcoin address (encoded in a specific way) // and can also include additional conditions or requirements based on the type of script used. final List txOut = receiver - .map((e) => - TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .map((e) => TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) .toList(); // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, in this case we need to set 'hasSegwit' to false. - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); + final tx = BtcTransaction(inputs: txin, outputs: txOut); for (int i = 0; i < txin.length; i++) { // For None-SegWit transactions, we use the 'getTransactionDigest' method // to obtain the input digest for signing. @@ -168,8 +154,7 @@ BtcTransaction buildP2pkhTransaction({ ); // sign transaction - final signedTx = - sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.SIGHASH_ALL); + final signedTx = sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.sighashAll); // set unlocking script for current index txin[i].scriptSig = Script(script: [signedTx, utxo[i].public().toHex()]); @@ -184,9 +169,7 @@ BtcTransaction buildP2shNoneSegwitTransaction({ required List utxo, }) { // We define transaction inputs by specifying the transaction ID and index. - final txin = utxo - .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) - .toList(); + final txin = utxo.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)).toList(); // Create transaction outputs // Parameters @@ -197,12 +180,11 @@ BtcTransaction buildP2shNoneSegwitTransaction({ // It includes the recipient's Bitcoin address (encoded in a specific way) // and can also include additional conditions or requirements based on the type of script used. final List txOut = receiver - .map((e) => - TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .map((e) => TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) .toList(); // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, in this caase we need to set 'hasSegwit' to false. - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); + final tx = BtcTransaction(inputs: txin, outputs: txOut); for (int i = 0; i < txin.length; i++) { final ownerPublic = utxo[i].public(); final scriptPubKey = utxo[i].ownerDetails.address.type == P2shAddressType.p2pkhInP2sh @@ -217,14 +199,12 @@ BtcTransaction buildP2shNoneSegwitTransaction({ script: scriptPubKey, ); // sign transaction - final signedTx = - sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.SIGHASH_ALL); + final signedTx = sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.sighashAll); // set unlocking script for current index switch (utxo[i].ownerDetails.address.type) { case P2shAddressType.p2pkhInP2sh: - txin[i].scriptSig = Script( - script: [signedTx, ownerPublic.toHex(), scriptPubKey.toHex()]); + txin[i].scriptSig = Script(script: [signedTx, ownerPublic.toHex(), scriptPubKey.toHex()]); break; case P2shAddressType.p2pkInP2sh: txin[i].scriptSig = Script(script: [signedTx, scriptPubKey.toHex()]); @@ -243,9 +223,7 @@ BtcTransaction buildP2SHSegwitTransaction({ required List utxo, }) { // We define transaction inputs by specifying the transaction ID and index. - final txin = utxo - .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) - .toList(); + final txin = utxo.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)).toList(); // Create transaction outputs // Parameters @@ -256,8 +234,7 @@ BtcTransaction buildP2SHSegwitTransaction({ // It includes the recipient's Bitcoin address (encoded in a specific way) // and can also include additional conditions or requirements based on the type of script used. final List txOut = receiver - .map((e) => - TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .map((e) => TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) .toList(); // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script // for the transaction inputs. In traditional non-SegWit transactions, @@ -268,14 +245,13 @@ BtcTransaction buildP2SHSegwitTransaction({ final List witnesses = []; // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + final tx = BtcTransaction(inputs: txin, outputs: txOut); for (int i = 0; i < txin.length; i++) { final ownerPublic = utxo[i].public(); - final scriptPubKey = - utxo[i].ownerDetails.address.type == P2shAddressType.p2wpkhInP2sh - ? ownerPublic.toP2pkhAddress().toScriptPubKey() - : ownerPublic.toP2wshRedeemScript(); + final scriptPubKey = utxo[i].ownerDetails.address.type == P2shAddressType.p2wpkhInP2sh + ? ownerPublic.toP2pkhAddress().toScriptPubKey() + : ownerPublic.toP2wshRedeemScript(); // For SegWit transactions (excluding P2TR), we use the 'getTransactionSegwitDigit' method // to obtain the input digest for signing. @@ -288,8 +264,7 @@ BtcTransaction buildP2SHSegwitTransaction({ amount: utxo[i].utxo.value); // sign transaction - final signedTx = - sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.SIGHASH_ALL); + final signedTx = sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.sighashAll); // In a SegWit P2SH (Pay-to-Script-Hash) transaction, you will find both a scriptSig field and a witness field. // This combination is used to maintain compatibility with non-SegWit Bitcoin nodes while taking advantage @@ -328,9 +303,7 @@ BtcTransaction buildP2trTransaction({ required List utxo, }) { // We define transaction inputs by specifying the transaction ID and index. - final txin = utxo - .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) - .toList(); + final txin = utxo.map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)).toList(); // Create transaction outputs // Parameters @@ -341,13 +314,11 @@ BtcTransaction buildP2trTransaction({ // It includes the recipient's Bitcoin address (encoded in a specific way) // and can also include additional conditions or requirements based on the type of script used. final List txOut = receiver - .map((e) => - TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .map((e) => TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) .toList(); // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. - BtcTransaction tx = - BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + BtcTransaction tx = BtcTransaction(inputs: txin, outputs: txOut); // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script // for the transaction inputs. In traditional non-SegWit transactions, @@ -368,20 +339,14 @@ BtcTransaction buildP2trTransaction({ // you typically need to include all the scriptPubKeys of the UTXOs // being spent and their corresponding amounts. // This information is required to ensure that the transaction is properly structured and secure - scriptPubKeys: - utxo.map((e) => e.ownerDetails.address.toScriptPubKey()).toList(), + scriptPubKeys: utxo.map((e) => e.ownerDetails.address.toScriptPubKey()).toList(), amounts: utxo.map((e) => e.utxo.value).toList(), - // The tapleaf script that we are spending (ext_flag=1) - script: null, - sighash: BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL, - // default is 0; 1 is for script spending (BIP342) - extFlags: 0, + sighash: BitcoinOpCodeConst.sighashDefault, ); // sign transaction using `signTapRoot` method of thransaction - final signedTx = - sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.SIGHASH_ALL); + final signedTx = sign(txDigit, utxo[i].public().toHex(), BitcoinOpCodeConst.sighashAll); // add witness for current index witnesses.add(TxWitnessInput(stack: [signedTx])); 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 4f16538..12b0f72 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 @@ -1,16 +1,14 @@ // ignore_for_file: unused_local_variable import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:example/services_examples/explorer_service/explorer_service.dart'; import 'spending_builders.dart'; // Define the network as the Testnet (used for testing and development purposes). const network = BitcoinNetwork.testnet; -final service = BitcoinApiService(); // Initialize an API provider for interacting with the Testnet's blockchain data. -final api = ApiProvider.fromMempool(network, service); +final api = ApiProvider.fromMempool(network); // In these tutorials, you will learn how to spend various types of UTXOs. // Each method is specific to a type of UTXO. @@ -28,16 +26,15 @@ Future spendingP2WPKH(ECPrivate sWallet, ECPrivate rWallet) async { // P2WPKH final sender = publicKey.toP2wpkhAddress(); // Read UTXOs of accounts from the BlockCypher API. - final utxo = await api.getAccountUtxo( - UtxoAddressDetails(address: sender, publicKey: publicKey.toHex())); + final utxo = + await api.getAccountUtxo(UtxoAddressDetails(address: sender, publicKey: publicKey.toHex())); // The total amount of UTXOs that we can spend. final sumOfUtxo = utxo.sumOfUtxosValue(); if (sumOfUtxo == BigInt.zero) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); + throw Exception("account does not have any unspent transaction or mybe no confirmed"); } // Receive network fees - final feeRate = await api.getNetworkFeeRate(); + final feeRate = await api.getRecommendedFeeRate(); // feeRate.medium, feeRate.high ,feeRate.low P/KB // In this section, we select the transaction outputs; the number and type of addresses are not important @@ -64,8 +61,7 @@ Future spendingP2WPKH(ECPrivate sWallet, ECPrivate rWallet) async { // Now that we've determined the transaction size, let's calculate the transaction fee // based on the transaction size and the desired fee rate. - final estimateFee = feeRate.getEstimate(transactionSize, - feeRateType: BitcoinFeeRateType.medium); + final estimateFee = feeRate.getEstimate(transactionSize, feeRateType: BitcoinFeeRateType.medium); // We subtract the fee from the total amount of UTXOs to calculate // the actual amount we can spend in this transaction. @@ -74,9 +70,8 @@ Future spendingP2WPKH(ECPrivate sWallet, ECPrivate rWallet) async { // We specify the desired amount for each address. Here, I have divided the desired total // amount by the number of outputs to ensure an equal amount for each. final outPutWithValue = outputsAdress - .map((e) => BitcoinOutput( - address: e.address, - value: canSpend ~/ BigInt.from(outputsAdress.length))) + .map((e) => + BitcoinOutput(address: e.address, value: canSpend ~/ BigInt.from(outputsAdress.length))) .toList(); // I use the 'buildP2wpkTransaction' method to create a transaction. @@ -112,15 +107,14 @@ Future spendingP2WSH(ECPrivate sWallet, ECPrivate rWallet) async { final addr = sWallet.getPublic(); // P2WSH ADDRESS final sender = addr.toP2wshAddress(); - final utxo = await api.getAccountUtxo( - UtxoAddressDetails(address: sender, publicKey: addr.toHex())); + final utxo = + await api.getAccountUtxo(UtxoAddressDetails(address: sender, publicKey: addr.toHex())); final sumOfUtxo = utxo.sumOfUtxosValue(); if (sumOfUtxo == BigInt.zero) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); + throw Exception("account does not have any unspent transaction or mybe no confirmed"); } - final feeRate = await api.getNetworkFeeRate(); + final feeRate = await api.getRecommendedFeeRate(); final prive = sWallet; final recPub = rWallet.getPublic(); @@ -133,13 +127,11 @@ Future spendingP2WSH(ECPrivate sWallet, ECPrivate rWallet) async { ]; final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: utxo, outputs: outputsAdress, network: network); - final estimateFee = feeRate.getEstimate(transactionSize, - feeRateType: BitcoinFeeRateType.medium); + final estimateFee = feeRate.getEstimate(transactionSize, feeRateType: BitcoinFeeRateType.medium); final canSpend = sumOfUtxo - estimateFee; final outPutWithValue = outputsAdress - .map((e) => BitcoinOutput( - address: e.address, - value: canSpend ~/ BigInt.from(outputsAdress.length))) + .map((e) => + BitcoinOutput(address: e.address, value: canSpend ~/ BigInt.from(outputsAdress.length))) .toList(); final transaction = buildP2WSHTransaction( receiver: outPutWithValue, @@ -161,15 +153,14 @@ Future spendingP2PKH(ECPrivate sWallet, ECPrivate rWallet) async { final addr = sWallet.getPublic(); // P2PKH final sender = addr.toP2pkhAddress(); - final utxo = await api.getAccountUtxo( - UtxoAddressDetails(address: sender, publicKey: addr.toHex())); + final utxo = + await api.getAccountUtxo(UtxoAddressDetails(address: sender, publicKey: addr.toHex())); final sumOfUtxo = utxo.sumOfUtxosValue(); if (sumOfUtxo == BigInt.zero) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); + throw Exception("account does not have any unspent transaction or mybe no confirmed"); } - final feeRate = await api.getNetworkFeeRate(); + final feeRate = await api.getRecommendedFeeRate(); final prive = sWallet; final recPub = rWallet.getPublic(); @@ -181,13 +172,11 @@ Future spendingP2PKH(ECPrivate sWallet, ECPrivate rWallet) async { ]; final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: utxo, outputs: outputsAdress, network: network); - final estimateFee = feeRate.getEstimate(transactionSize, - feeRateType: BitcoinFeeRateType.medium); + final estimateFee = feeRate.getEstimate(transactionSize, feeRateType: BitcoinFeeRateType.medium); final canSpend = sumOfUtxo - estimateFee; final outPutWithValue = outputsAdress - .map((e) => BitcoinOutput( - address: e.address, - value: canSpend ~/ BigInt.from(outputsAdress.length))) + .map((e) => + BitcoinOutput(address: e.address, value: canSpend ~/ BigInt.from(outputsAdress.length))) .toList(); final transaction = buildP2pkhTransaction( @@ -205,23 +194,21 @@ Future spendingP2PKH(ECPrivate sWallet, ECPrivate rWallet) async { // Spend P2SH(P2PKH) or P2SH(P2PK): Please note that all input addresses must be of P2SH(P2PKH) or P2SH(P2PK) type; otherwise, the transaction will fail. // This method is for standard 1-1 Multisig P2SH. // For standard n-of-m multi-signature scripts, please refer to the 'multi_sig_transactions.dart' tutorial. -Future spendingP2SHNoneSegwit( - ECPrivate sWallet, ECPrivate rWallet) async { +Future spendingP2SHNoneSegwit(ECPrivate sWallet, ECPrivate rWallet) async { // All the steps are the same as in the first tutorial; // the only difference is the transaction input type, // and we use method `buildP2shNoneSegwitTransaction` to create the transaction. final addr = sWallet.getPublic(); // P2SH(P2PK) final sender = addr.toP2pkInP2sh(); - final utxo = await api.getAccountUtxo( - UtxoAddressDetails(address: sender, publicKey: addr.toHex())); + final utxo = + await api.getAccountUtxo(UtxoAddressDetails(address: sender, publicKey: addr.toHex())); final sumOfUtxo = utxo.sumOfUtxosValue(); if (sumOfUtxo == BigInt.zero) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); + throw Exception("account does not have any unspent transaction or mybe no confirmed"); } - final feeRate = await api.getNetworkFeeRate(); + final feeRate = await api.getRecommendedFeeRate(); final prive = sWallet; final recPub = rWallet.getPublic(); @@ -233,13 +220,11 @@ Future spendingP2SHNoneSegwit( ]; final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: utxo, outputs: outputsAdress, network: network); - final estimateFee = feeRate.getEstimate(transactionSize, - feeRateType: BitcoinFeeRateType.medium); + final estimateFee = feeRate.getEstimate(transactionSize, feeRateType: BitcoinFeeRateType.medium); final canSpend = sumOfUtxo - estimateFee; final outPutWithValue = outputsAdress - .map((e) => BitcoinOutput( - address: e.address, - value: canSpend ~/ BigInt.from(outputsAdress.length))) + .map((e) => + BitcoinOutput(address: e.address, value: canSpend ~/ BigInt.from(outputsAdress.length))) .toList(); final transaction = buildP2shNoneSegwitTransaction( receiver: outPutWithValue, @@ -263,15 +248,14 @@ Future spendingP2shSegwit(ECPrivate sWallet, ECPrivate rWallet) async { final addr = sWallet.getPublic(); // P2SH(P2PWKH) final sender = addr.toP2wpkhInP2sh(); - final utxo = await api.getAccountUtxo( - UtxoAddressDetails(address: sender, publicKey: addr.toHex())); + final utxo = + await api.getAccountUtxo(UtxoAddressDetails(address: sender, publicKey: addr.toHex())); final sumOfUtxo = utxo.sumOfUtxosValue(); if (sumOfUtxo == BigInt.zero) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); + throw Exception("account does not have any unspent transaction or mybe no confirmed"); } - final feeRate = await api.getNetworkFeeRate(); + final feeRate = await api.getRecommendedFeeRate(); final prive = sWallet; final recPub = rWallet.getPublic(); @@ -284,13 +268,11 @@ Future spendingP2shSegwit(ECPrivate sWallet, ECPrivate rWallet) async { ]; final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: utxo, outputs: outputsAdress, network: network); - final estimateFee = feeRate.getEstimate(transactionSize, - feeRateType: BitcoinFeeRateType.medium); + final estimateFee = feeRate.getEstimate(transactionSize, feeRateType: BitcoinFeeRateType.medium); final canSpend = sumOfUtxo - estimateFee; final outPutWithValue = outputsAdress - .map((e) => BitcoinOutput( - address: e.address, - value: canSpend ~/ BigInt.from(outputsAdress.length))) + .map((e) => + BitcoinOutput(address: e.address, value: canSpend ~/ BigInt.from(outputsAdress.length))) .toList(); // return; @@ -315,15 +297,14 @@ Future spendingP2TR(ECPrivate sWallet, ECPrivate rWallet) async { final addr = sWallet.getPublic(); // P2TR address final sender = addr.toTaprootAddress(); - final utxo = await api.getAccountUtxo( - UtxoAddressDetails(address: sender, publicKey: addr.toHex())); + final utxo = + await api.getAccountUtxo(UtxoAddressDetails(address: sender, publicKey: addr.toHex())); final sumOfUtxo = utxo.sumOfUtxosValue(); if (sumOfUtxo == BigInt.zero) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); + throw Exception("account does not have any unspent transaction or mybe no confirmed"); } - final feeRate = await api.getNetworkFeeRate(); + final feeRate = await api.getRecommendedFeeRate(); final prive = sWallet; final recPub = rWallet.getPublic(); @@ -335,13 +316,11 @@ Future spendingP2TR(ECPrivate sWallet, ECPrivate rWallet) async { ]; final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( utxos: utxo, outputs: outputsAdress, network: network); - final estimateFee = feeRate.getEstimate(transactionSize, - feeRateType: BitcoinFeeRateType.medium); + final estimateFee = feeRate.getEstimate(transactionSize, feeRateType: BitcoinFeeRateType.medium); final canSpend = sumOfUtxo - estimateFee; final outPutWithValue = outputsAdress - .map((e) => BitcoinOutput( - address: e.address, - value: canSpend ~/ BigInt.from(outputsAdress.length))) + .map((e) => + BitcoinOutput(address: e.address, value: canSpend ~/ BigInt.from(outputsAdress.length))) .toList(); final transaction = buildP2trTransaction( 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 794deb6..d0a3bcc 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 @@ -4,7 +4,6 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/explorer_service/explorer_service.dart'; void main() async { final service = BitcoinApiService(); @@ -13,7 +12,7 @@ void main() async { // select api for read accounts UTXOs and send transaction // Mempool or BlockCypher - final api = ApiProvider.fromMempool(network, service); + final api = ApiProvider.fromMempool(network); final mnemonic = Bip39SeedGenerator(Mnemonic.fromString( "spy often critic spawn produce volcano depart fire theory fog turn retire")) @@ -59,18 +58,14 @@ void main() async { // P2WSH Multisig 4-6 // tb1qxt3c7849m0m6cv3z3s35c3zvdna3my3yz0r609qd9g0dcyyk580sgyldhe - final p2wshMultiSigAddress = - multiSignatureAddress.toP2wshAddress(network: network).toP2pkhAddress(network); + final p2wshMultiSigAddress = multiSignatureAddress.toP2wshAddress(network: network); // p2sh(p2wsh) multisig - final signerP2sh1 = - MultiSignatureSigner(publicKey: public5.toHex(), weight: 1); + final signerP2sh1 = MultiSignatureSigner(publicKey: public5.toHex(), weight: 1); - final signerP2sh2 = - MultiSignatureSigner(publicKey: public6.toHex(), weight: 1); + final signerP2sh2 = MultiSignatureSigner(publicKey: public6.toHex(), weight: 1); - final signerP2sh3 = - MultiSignatureSigner(publicKey: public1.toHex(), weight: 1); + final signerP2sh3 = MultiSignatureSigner(publicKey: public1.toHex(), weight: 1); final MultiSignatureAddress p2shMultiSignature = MultiSignatureAddress( threshold: 2, @@ -78,9 +73,7 @@ void main() async { ); // P2SH(P2WSH) miltisig 2-3 // 2N8co8bth9CNKtnWGfHW6HuUNgnNPNdpsMj - final p2shMultisigAddress = p2shMultiSignature - .toP2wshInP2shAddress(network: network) - .toP2pkhAddress(network); + final p2shMultisigAddress = p2shMultiSignature.toP2wshInP2shAddress(network: network); // P2TR final exampleAddr2 = public2.toTaprootAddress(); @@ -149,11 +142,9 @@ void main() async { utxos: utxos, outputs: [ BitcoinOutput( - address: p2shMultiSignature.toP2wshInP2shAddress(network: network), - value: BigInt.zero), + address: p2shMultiSignature.toP2wshInP2shAddress(network: network), value: BigInt.zero), BitcoinOutput( - address: multiSignatureAddress.toP2wshAddress(network: network), - value: BigInt.zero), + address: multiSignatureAddress.toP2wshAddress(network: network), value: BigInt.zero), BitcoinOutput(address: exampleAddr2, value: BigInt.zero), BitcoinOutput(address: exampleAddr4, value: BigInt.zero) ], @@ -168,7 +159,7 @@ void main() async { // That's my perspective, of course. final blockCypher = ApiProvider.fromBlocCypher(network, service); - final feeRate = await blockCypher.getNetworkFeeRate(); + final feeRate = await blockCypher.getRecommendedFeeRate(); // fee rate inKB // feeRate.medium: 32279 P/KB // feeRate.high: 43009 P/KB @@ -190,12 +181,9 @@ void main() async { address: p2shMultiSignature.toP2wshInP2shAddress(network: network), value: BigInt.from(365449)); final output2 = BitcoinOutput( - address: multiSignatureAddress.toP2wshAddress(network: network), - value: BigInt.from(365449)); - final output3 = - BitcoinOutput(address: exampleAddr2, value: BigInt.from(365448)); - final output4 = - BitcoinOutput(address: exampleAddr4, value: BigInt.from(365448)); + address: multiSignatureAddress.toP2wshAddress(network: network), value: BigInt.from(365449)); + final output3 = BitcoinOutput(address: exampleAddr2, value: BigInt.from(365448)); + final output4 = BitcoinOutput(address: exampleAddr4, value: BigInt.from(365448)); // Well, now it is clear to whom we are going to pay the amount // Now let's create the transaction @@ -233,8 +221,7 @@ void main() async { // I've added a method for signing the transaction as a parameter. // This method sends you the public key for each UTXO, // allowing you to sign the desired input with the associated private key - final transaction = - transactionBuilder.buildTransaction((trDigest, utxo, publicKey, sighash) { + final transaction = transactionBuilder.buildTransaction((trDigest, utxo, publicKey, sighash) { late ECPrivate key; // ok we have the public key of the current UTXO and we use some conditions to find private key and sign transaction @@ -265,7 +252,7 @@ void main() async { // Ok, now we have the private key, we need to check which method to use for signing // We check whether the UTX corresponds to the P2TR address or not. - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { // yes is p2tr utxo and now we use SignTaprootTransaction(Schnorr sign) // for now this transaction builder support only tweak transaction // If you want to spend a Taproot script-path spending, you must create your own transaction builder. @@ -282,7 +269,7 @@ void main() async { // we check if transaction is segwit or not // When one of the input UTXO addresses is SegWit, the transaction is considered SegWit. - final isSegwitTr = transaction.hasSegwit; + final isSegwitTr = transaction.hasWitness; // transaction id final transactionId = transaction.txId(); 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 851259f..67653da 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 @@ -1,6 +1,5 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; -import 'package:example/services_examples/explorer_service/explorer_service.dart'; // spend from 8 different address type to 10 different output void main() async { @@ -116,26 +115,16 @@ void main() async { // now we have 1,174,140 satoshi for spending let do it // we create 10 different output with different address type like (pt2r, p2sh(p2wpkh), p2sh(p2wsh), p2pkh, etc.) // We consider the spendable amount for 10 outputs and divide by 10, each output 117,414 - final output1 = - BitcoinOutput(address: exampleAddr4, value: BigInt.from(117414)); - final output2 = - BitcoinOutput(address: exampleAddr9, value: BigInt.from(117414)); - final output3 = - BitcoinOutput(address: exampleAddr10, value: BigInt.from(117414)); - final output4 = - BitcoinOutput(address: exampleAddr1, value: BigInt.from(117414)); - final output5 = - BitcoinOutput(address: exampleAddr3, value: BigInt.from(117414)); - final output6 = - BitcoinOutput(address: exampleAddr2, value: BigInt.from(117414)); - final output7 = - BitcoinOutput(address: exampleAddr7, value: BigInt.from(117414)); - final output8 = - BitcoinOutput(address: exampleAddr8, value: BigInt.from(117414)); - final output9 = - BitcoinOutput(address: exampleAddr5, value: BigInt.from(117414)); - final output10 = - BitcoinOutput(address: exampleAddr6, value: BigInt.from(117414)); + final output1 = BitcoinOutput(address: exampleAddr4, value: BigInt.from(117414)); + final output2 = BitcoinOutput(address: exampleAddr9, value: BigInt.from(117414)); + final output3 = BitcoinOutput(address: exampleAddr10, value: BigInt.from(117414)); + final output4 = BitcoinOutput(address: exampleAddr1, value: BigInt.from(117414)); + final output5 = BitcoinOutput(address: exampleAddr3, value: BigInt.from(117414)); + final output6 = BitcoinOutput(address: exampleAddr2, value: BigInt.from(117414)); + final output7 = BitcoinOutput(address: exampleAddr7, value: BigInt.from(117414)); + final output8 = BitcoinOutput(address: exampleAddr8, value: BigInt.from(117414)); + final output9 = BitcoinOutput(address: exampleAddr5, value: BigInt.from(117414)); + final output10 = BitcoinOutput(address: exampleAddr6, value: BigInt.from(117414)); // Well, now it is clear to whom we are going to pay the amount // Now let's create the transaction @@ -183,8 +172,7 @@ void main() async { // parameters // utxo infos with owner details // trDigest transaction digest of current UTXO (must be sign with correct privateKey) - final transaction = - transactionBuilder.buildTransaction((trDigest, utxo, publicKey, sighash) { + final transaction = transactionBuilder.buildTransaction((trDigest, utxo, publicKey, sighash) { late ECPrivate key; // ok we have the public key of the current UTXO and we use some conditions to find private key and sign transaction @@ -212,7 +200,7 @@ void main() async { // Ok, now we have the private key, we need to check which method to use for signing // We check whether the UTX corresponds to the P2TR address or not. - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { // yes is p2tr utxo and now we use SignTaprootTransaction(Schnorr sign) // for now this transaction builder support only tweak transaction // If you want to spend a Taproot script-path spending, you must create your own transaction builder. @@ -229,7 +217,7 @@ void main() async { // we check if transaction is segwit or not // When one of the input UTXO addresses is SegWit, the transaction is considered SegWit. - final isSegwitTr = transaction.hasSegwit; + final isSegwitTr = transaction.hasWitness; // transaction id // ignore: unused_local_variable 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 37c6e1d..b9fba57 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 @@ -1,5 +1,4 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:example/services_examples/electrum/electrum_ssl_service.dart'; /// If you are working with different networks, /// you can apply this tutorial universally. @@ -15,15 +14,14 @@ import 'package:example/services_examples/electrum/electrum_ssl_service.dart'; void main() async { /// connect to electrum service with websocket /// please see `services_examples` folder for how to create electrum websocket service - final service = - await ElectrumSSLService.connect("testnet.aranguren.org:51002"); + final service = ElectrumSSLService.connect(Uri.parse("testnet.aranguren.org:51002")); /// create provider with service - final provider = ElectrumApiProvider(service); + final provider = await ElectrumProvider.connect(service); /// spender details - final privateKey = ECPrivate.fromHex( - "76257aafc9b954351c7f6445b2d07277f681a5e83d515a1f32ebf54989c2af4f"); + final privateKey = + ECPrivate.fromHex("76257aafc9b954351c7f6445b2d07277f681a5e83d515a1f32ebf54989c2af4f"); final examplePublicKey = privateKey.getPublic(); final spender1 = examplePublicKey.toP2pkhAddress(); final spender2 = examplePublicKey.toP2wpkhAddress(); @@ -47,17 +45,16 @@ 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 - final elctrumUtxos = await provider - .request(ElectrumScriptHashListUnspent(scriptHash: i.pubKeyHash())); + /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account + final electrumUtxos = + await provider.request(ElectrumRequestScriptHashListUnspent(scriptHash: i.pubKeyHash())); /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. /// read spender utxos - final List utxos = elctrumUtxos + final List utxos = electrumUtxos .map((e) => UtxoWithAddress( utxo: e.toUtxo(i.type), - ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey.toHex(), address: i))) + ownerDetails: UtxoAddressDetails(publicKey: examplePublicKey.toHex(), address: i))) .toList(); accountsUtxos.addAll(utxos); } @@ -68,8 +65,8 @@ void main() async { return; } - final examplePublicKey2 = ECPublic.fromHex( - "02d82c9860e36f15d7b72aa59e29347f951277c21cd4d34822acdeeadbcff8a546"); + final examplePublicKey2 = + ECPublic.fromHex("02d82c9860e36f15d7b72aa59e29347f951277c21cd4d34822acdeeadbcff8a546"); /// When creating outputs with an address, I utilize the public key. Alternatively, an address class, such as /// P2pkhAddress.fromAddress(address: ".....", network: network); @@ -77,28 +74,21 @@ void main() async { /// .... final List outputs = [ BitcoinOutput( - address: examplePublicKey2.toP2wpkhAddress(), - value: BtcUtils.toSatoshi("0.00001")), + address: examplePublicKey2.toP2wpkhAddress(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( - address: examplePublicKey2.toTaprootAddress(), - value: BtcUtils.toSatoshi("0.00001")), + address: examplePublicKey2.toTaprootAddress(), value: BtcUtils.toSatoshi("0.00001")), + BitcoinOutput(address: examplePublicKey2.toP2pkhInP2sh(), value: BtcUtils.toSatoshi("0.00001")), + BitcoinOutput(address: examplePublicKey2.toP2pkInP2sh(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( - address: examplePublicKey2.toP2pkhInP2sh(), - value: BtcUtils.toSatoshi("0.00001")), - BitcoinOutput( - address: examplePublicKey2.toP2pkInP2sh(), - value: BtcUtils.toSatoshi("0.00001")), - BitcoinOutput( - address: examplePublicKey2.toP2wshAddress(), - value: BtcUtils.toSatoshi("0.00001")), + address: examplePublicKey2.toP2wshAddress(), value: BtcUtils.toSatoshi("0.00001")), ]; /// OP_RETURN const String memo = "https://github.com/mrtnetwork"; /// SUM OF OUTOUT AMOUNTS - final sumOfOutputs = outputs.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); + final sumOfOutputs = + outputs.fold(BigInt.zero, (previousValue, element) => previousValue + element.value); /// Estimate transaction size int transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( @@ -107,8 +97,7 @@ void main() async { ...outputs, /// I add more output for change value to get correct transaction size - BitcoinOutput( - address: examplePublicKey2.toP2pkhAddress(), value: BigInt.zero) + BitcoinOutput(address: examplePublicKey2.toP2pkhAddress(), value: BigInt.zero) ], /// network @@ -121,11 +110,15 @@ void main() async { enableRBF: true); /// get network fee esmtimate (fee per kilobyte) - final networkEstimate = await provider.request(ElectrumEstimateFee()); + final networkEstimate = await provider.request(ElectrumRequestEstimateFee()); + + /// the daemon does not have enough information to make an estimate + if (networkEstimate == null) { + return; + } /// Convert kilobytes to bytes, multiply by the transaction size, and the result yields the transaction fees. - final fee = - BigInt.from(transactionSize) * (networkEstimate ~/ BigInt.from(1000)); + final fee = BigInt.from(transactionSize) * (networkEstimate ~/ BigInt.from(1000)); /// change value final changeValue = sumOfUtxo - (sumOfOutputs + fee); @@ -135,8 +128,7 @@ void main() async { } //// if we have change value we back amount to account if (changeValue > BigInt.zero) { - outputs.add(BitcoinOutput( - address: examplePublicKey2.toP2pkhAddress(), value: changeValue)); + outputs.add(BitcoinOutput(address: examplePublicKey2.toP2pkhAddress(), value: changeValue)); } /// create transaction builder @@ -151,9 +143,8 @@ void main() async { enableRBF: true); /// create transaction and sign it - final transaction = - builder.buildTransaction((trDigest, utxo, publicKey, sighash) { - if (utxo.utxo.isP2tr()) { + final transaction = builder.buildTransaction((trDigest, utxo, publicKey, sighash) { + if (utxo.utxo.isP2tr) { return privateKey.signTapRoot(trDigest, sighash: sighash); } return privateKey.signInput(trDigest, sigHash: sighash); @@ -166,7 +157,7 @@ void main() async { final raw = transaction.serialize(); /// send to network - await provider.request(ElectrumBroadCastTransaction(transactionRaw: raw)); + await provider.request(ElectrumRequestBroadCastTransaction(transactionRaw: raw)); /// Once completed, we verify the status by checking the mempool or using another explorer to review the transaction details. /// https://mempool.space/testnet/tx/70cf664bba4b5ac9edc6133e9c6891ffaf8a55eaea9d2ac99aceead1c3db8899 diff --git a/example/lib/global/transfer_to_8_account_example.dart b/example/lib/global/transfer_to_8_account_example.dart index a8f6f6b..c36453f 100644 --- a/example/lib/global/transfer_to_8_account_example.dart +++ b/example/lib/global/transfer_to_8_account_example.dart @@ -1,15 +1,12 @@ import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:example/services_examples/electrum/electrum_ssl_service.dart'; - void main() async { /// connect to electrum service with websocket /// please see `services_examples` folder for how to create electrum websocket service - final service = - await ElectrumSSLService.connect("testnet.aranguren.org:51002"); + final service = ElectrumSSLService.connect(Uri.parse("testnet.aranguren.org:51002")); /// create provider with service - final provider = ElectrumApiProvider(service); + final provider = await ElectrumProvider.connect(service); /// spender details /// Define another private key from wif @@ -20,22 +17,22 @@ void main() async { final p2pkhAddress = examplePublicKey2.toP2pkhAddress(); /// receiver addresses i use public key for generate address - final examplePublicKey = ECPublic.fromHex( - "032a4f8be9ebffb46e2c6a1c240702553b9c9c8ad9638650833d07d5d22f618621"); + final examplePublicKey = + ECPublic.fromHex("032a4f8be9ebffb46e2c6a1c240702553b9c9c8ad9638650833d07d5d22f618621"); const network = BitcoinNetwork.testnet; - /// Reads all UTXOs (Unspent Transaction outputs) associated with the account - final elctrumUtxos = await provider.request(ElectrumScriptHashListUnspent( - scriptHash: examplePublicKey2.toP2pkhAddress().pubKeyHash())); + /// Reads all UTXOs (Unspent Transaction Outputs) associated with the account + final electrumUtxos = await provider.request( + ElectrumRequestScriptHashListUnspent(scriptHash: examplePublicKey2.toAddress().pubKeyHash())); /// Converts all UTXOs to a list of UtxoWithAddress, containing UTXO information along with address details. /// read spender utxos - final List utxos = elctrumUtxos + final List utxos = electrumUtxos .map((e) => UtxoWithAddress( utxo: e.toUtxo(p2pkhAddress.type), - ownerDetails: UtxoAddressDetails( - publicKey: examplePublicKey2.toHex(), address: p2pkhAddress))) + ownerDetails: + UtxoAddressDetails(publicKey: examplePublicKey2.toHex(), address: p2pkhAddress))) .toList(); /// get sum of values @@ -49,35 +46,23 @@ void main() async { /// P2trAddress.fromAddress(address: "....", network: network) /// .... final List outputs = [ + BitcoinOutput(address: examplePublicKey.toP2pkhAddress(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( - address: examplePublicKey.toP2pkhAddress(), - value: BtcUtils.toSatoshi("0.00001")), - BitcoinOutput( - address: examplePublicKey.toP2wpkhAddress(), - value: BtcUtils.toSatoshi("0.00001")), - BitcoinOutput( - address: examplePublicKey.toTaprootAddress(), - value: BtcUtils.toSatoshi("0.00001")), - BitcoinOutput( - address: examplePublicKey.toP2pkhInP2sh(), - value: BtcUtils.toSatoshi("0.00001")), - BitcoinOutput( - address: examplePublicKey.toP2pkInP2sh(), - value: BtcUtils.toSatoshi("0.00001")), + address: examplePublicKey.toP2wpkhAddress(), value: BtcUtils.toSatoshi("0.00001")), BitcoinOutput( - address: examplePublicKey.toP2wshAddress(), - value: BtcUtils.toSatoshi("0.00001")), - BitcoinOutput( - address: examplePublicKey.toP2wpkhInP2sh(), - value: BtcUtils.toSatoshi("0.00001")), + address: examplePublicKey.toTaprootAddress(), value: BtcUtils.toSatoshi("0.00001")), + BitcoinOutput(address: examplePublicKey.toP2pkhInP2sh(), value: BtcUtils.toSatoshi("0.00001")), + BitcoinOutput(address: examplePublicKey.toP2pkInP2sh(), value: BtcUtils.toSatoshi("0.00001")), + BitcoinOutput(address: examplePublicKey.toP2wshAddress(), value: BtcUtils.toSatoshi("0.00001")), + BitcoinOutput(address: examplePublicKey.toP2wpkhInP2sh(), value: BtcUtils.toSatoshi("0.00001")), ]; /// OP_RETURN const String memo = "https://github.com/mrtnetwork"; /// SUM OF OUTOUT AMOUNTS - final sumOfOutputs = outputs.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); + final sumOfOutputs = + outputs.fold(BigInt.zero, (previousValue, element) => previousValue + element.value); /// ESTIMATE TRANSACTION SIZE int estimateSize = BitcoinTransactionBuilder.estimateTransactionSize( @@ -86,8 +71,7 @@ void main() async { ...outputs, /// I add more output for change value to get correct transaction size - BitcoinOutput( - address: examplePublicKey2.toP2pkhAddress(), value: BigInt.zero) + BitcoinOutput(address: examplePublicKey2.toP2pkhAddress(), value: BigInt.zero) ], /// network @@ -100,11 +84,15 @@ void main() async { enableRBF: true); /// get network fee esmtimate (kb/s) - final networkEstimate = await provider.request(ElectrumEstimateFee()); + final networkEstimate = await provider.request(ElectrumRequestEstimateFee()); + + /// the daemon does not have enough information to make an estimate + if (networkEstimate == null) { + return; + } /// kb to bytes and mul with transaction size and now we have fee - final fee = - BigInt.from(estimateSize) * (networkEstimate ~/ BigInt.from(1000)); + final fee = BigInt.from(estimateSize) * (networkEstimate ~/ BigInt.from(1000)); /// change value final changeValue = sumOfUtxo - (sumOfOutputs + fee); @@ -112,8 +100,8 @@ void main() async { if (changeValue.isNegative) return; //// if we have change value we back amount to account if (changeValue > BigInt.zero) { - final changeOutput = BitcoinOutput( - address: examplePublicKey2.toP2pkhAddress(), value: changeValue); + final changeOutput = + BitcoinOutput(address: examplePublicKey2.toP2pkhAddress(), value: changeValue); outputs.add(changeOutput); } @@ -129,9 +117,8 @@ void main() async { enableRBF: true); /// create transaction and sign it - final transaction = - builder.buildTransaction((trDigest, utxo, publicKey, sighash) { - if (utxo.utxo.isP2tr()) { + final transaction = builder.buildTransaction((trDigest, utxo, publicKey, sighash) { + if (utxo.utxo.isP2tr) { return examplePrivateKey2.signTapRoot(trDigest, sighash: sighash); } return examplePrivateKey2.signInput(trDigest, sigHash: sighash); @@ -144,7 +131,7 @@ void main() async { final raw = transaction.serialize(); /// send to network - await provider.request(ElectrumBroadCastTransaction(transactionRaw: raw)); + await provider.request(ElectrumRequestBroadCastTransaction(transactionRaw: raw)); /// Once completed, we verify the status by checking the mempool or using another explorer to review the transaction details. /// https://mempool.space/testnet/tx/abab018f3d2b92bf30c63b4aca419cf6d6571692b3620f06311c7e5a21a88b56 diff --git a/example/lib/musig/methods.dart b/example/lib/musig/methods.dart new file mode 100644 index 0000000..2837658 --- /dev/null +++ b/example/lib/musig/methods.dart @@ -0,0 +1,87 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; + +Future getProvider( + {String url = "testnet4-electrumx.wakiyamap.dev:51002"}) async { + final service = + ElectrumSSLService.connect(Uri.parse("tcp://testnet4-electrumx.wakiyamap.dev:51002")); + return ElectrumProvider.connect(service); +} + +class PsbtUtxoRequest { + final BitcoinBaseAddress address; + final Script? p2shRedeemScript; + final Script? witnessScript; + final TapLeafMerkleProof? merkleProof; + final TaprootTree? treeScript; + final TaprootLeaf? leafScript; + final List? xOnlyOrInternalPubKey; + final List? merkleRoot; + final List? leafScripts; + final List privateKeys; + final List? muSig2ParticipantPublicKeys; + final List? ripemd160; + final List? sha256; + final List? hash160; + final List? hash256; + const PsbtUtxoRequest( + {required this.address, + this.p2shRedeemScript, + this.witnessScript, + this.leafScript, + this.merkleProof, + this.treeScript, + this.xOnlyOrInternalPubKey, + this.merkleRoot, + this.leafScripts, + this.privateKeys = const [], + this.muSig2ParticipantPublicKeys, + this.ripemd160, + this.hash160, + this.hash256, + this.sha256}); +} + +Future> getPsbtUtxo( + {required List addresses, + bool local = true, + List>? data}) async { + final provider = await getProvider(); + + final utxos = await Future.wait(addresses.map((e) async { + return await provider + .request(ElectrumRequestScriptHashListUnspent(scriptHash: e.address.pubKeyHash())); + })); + + final utxoss = List.generate(utxos.length, (i) async { + final request = addresses[i]; + final accountUtxos = utxos[i]; + final er = await Future.wait(accountUtxos + .map((e) => provider.request(ElectrumRequestGetRawTransaction(e.txId))) + .toList()); + return List.generate( + accountUtxos.length, + (index) { + return PsbtUtxo( + utxo: accountUtxos[index].toUtxo(request.address.type), + p2shRedeemScript: request.p2shRedeemScript, + p2wshWitnessScript: request.witnessScript, + tx: er[index], + scriptPubKey: request.address.toScriptPubKey(), + leafScript: request.leafScript, + leafScripts: request.leafScripts, + merkleProof: request.merkleProof, + treeScript: request.treeScript, + merkleRoot: request.merkleRoot, + privateKeys: request.privateKeys, + xOnlyOrInternalPubKey: request.xOnlyOrInternalPubKey, + muSig2ParticipantPublicKeys: request.muSig2ParticipantPublicKeys, + hash160: request.hash160, + hash256: request.hash256, + ripemd160: request.ripemd160, + sha256: request.sha256); + }, + ); + }); + final e = await Future.wait(utxoss); + return e.expand((e) => e).toList(); +} diff --git a/example/lib/musig/musig_example.dart b/example/lib/musig/musig_example.dart new file mode 100644 index 0000000..2a1aaad --- /dev/null +++ b/example/lib/musig/musig_example.dart @@ -0,0 +1,199 @@ +// ignore_for_file: unused_element + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; + +void main() async { + final key = _getPubKey(); + final key2 = _getPubKey(path: "m/86'/1'/0'/0/2"); + final key3 = _getPubKey(path: "m/86'/1'/0'/0/3"); + final signer = Musig2Signer.generate([key, key2, key3]); + + final treeScript = _generateScriptTree(); + final address = _generateAddress(); + final input = + TxInput(txId: '9bb72872594cb9b7a029f3daf0fe277dfbe5de3d52d4b55ba283cfa97b9310cc', txIndex: 1); + final amount = BtcUtils.toSatoshi("0.00500000"); + final rAmount = BtcUtils.toSatoshi("0.00010000"); + final fee = BtcUtils.toSatoshi("0.00001"); + final change = amount - (rAmount + fee); + final output = TxOutput(amount: rAmount, scriptPubKey: signer.toAddress().toScriptPubKey()); + final changeOutput = TxOutput(amount: change, scriptPubKey: address.toScriptPubKey()); + BtcTransaction transaction = BtcTransaction(inputs: [input], outputs: [output, changeOutput]); + transaction = _spendWithKeyPath( + transaction: transaction, + existsUtxosScriptPubKeys: [address], + existsUtxosAmounts: [amount], + treeScript: treeScript); +} + +BtcTransaction _spendWithKeyPath( + {required BtcTransaction transaction, + required List existsUtxosScriptPubKeys, + required List existsUtxosAmounts, + required TaprootTree treeScript}) { + final key1 = _getPrivateKey(path: "m/86'/1'/0'/0/2"); + final key2 = _getPrivateKey(path: "m/86'/1'/0'/0/3"); + final key3 = _getPrivateKey(path: "m/86'/1'/0'/0/4"); + final pk1 = key1.getPublic(); + final pk2 = key2.getPublic(); + final pk3 = key3.getPublic(); + final signer = Musig2Signer.generate([pk1, pk2, pk3], privateKeys: [key1, key2, key3]); + final digest = transaction + .getTransactionTaprootDigset( + txIndex: 0, + scriptPubKeys: existsUtxosScriptPubKeys.map((e) => e.toScriptPubKey()).toList(), + amounts: existsUtxosAmounts) + .asImmutableBytes; + + final signature = signer.fullSign(digest, treeScript: treeScript); + return transaction.copyWith(witnesses: [ + TxWitnessInput(stack: [BytesUtils.toHexString(signature)]) + ]); +} + +BtcTransaction _spendWithLeafA( + {required BtcTransaction transaction, + required List existsUtxosScriptPubKeys, + required List existsUtxosAmounts, + required TaprootTree treeScript}) { + final internalKey = _getInternalPubKey(); + final key1 = _getPrivateKey(path: "m/86'/1'/0'/0/2"); + final key3 = _getPrivateKey(path: "m/86'/1'/0'/0/4"); + final pk1 = key1.getPublic(); + final pk3 = key3.getPublic(); + final musigSigner = + MuSig2.aggPublicKeys(keys: [pk1.publicKey.compressed, pk3.publicKey.compressed]); + final tapleafScript1 = TaprootLeaf( + script: Script(script: [ + BytesUtils.toHexString(musigSigner.publicKey.toXonly()), + BitcoinOpcode.opCheckSig + ])); + final digest = transaction + .getTransactionTaprootDigset( + txIndex: 0, + tapleafScript: tapleafScript1, + scriptPubKeys: existsUtxosScriptPubKeys.map((e) => e.toScriptPubKey()).toList(), + amounts: existsUtxosAmounts) + .asImmutableBytes; + final nonce1 = + MuSig2.nonceGenerate(publicKey: pk1.toBytes(mode: PubKeyModes.compressed), msg: digest); + final nonce3 = + MuSig2.nonceGenerate(publicKey: pk3.toBytes(mode: PubKeyModes.compressed), msg: digest); + final aggNonce = MuSig2.nonceAgg([nonce1.pubnonce, nonce3.pubnonce]); + final session = MuSig2Session( + aggnonce: aggNonce, + publicKeys: [ + pk1.toBytes(mode: PubKeyModes.compressed), + pk3.toBytes(mode: PubKeyModes.compressed) + ], + tweaks: [], + msg: digest); + final sig1 = MuSig2.sign(secnonce: nonce1.secnonce, sk: key1.toBytes(), session: session); + final sig3 = MuSig2.sign(secnonce: nonce3.secnonce, sk: key3.toBytes(), session: session); + final signature = MuSig2.partialSigAgg(signatures: [sig1, sig3], session: session); + final controlBlock = TaprootControlBlock.generate( + xOnlyOrInternalPubKey: internalKey.toXOnly(), + leafScript: tapleafScript1, + scriptTree: treeScript); + + return transaction.copyWith(witnesses: [ + TxWitnessInput(stack: [ + BytesUtils.toHexString(signature), + tapleafScript1.script.toHex(), + controlBlock.toHex() + ]) + ]); +} + +BtcTransaction _spendWithLeafC( + {required BtcTransaction transaction, + required List existsUtxosScriptPubKeys, + required List existsUtxosAmounts, + required TaprootTree treeScript}) { + final internalKey = _getInternalPubKey(); + final key2 = _getPrivateKey(path: "m/86'/1'/0'/0/3"); + final key1 = _getPrivateKey(path: "m/86'/1'/0'/0/2"); + final pk1 = key1.getPublic(); + final pk2 = key2.getPublic(); + final leafC = TaprootLeaf( + script: Script(script: [ + pk1.toXOnlyHex(), + BitcoinOpcode.opCheckSig, + pk2.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + 2, + BitcoinOpcode.opNumEqual + ])); + final digest = transaction + .getTransactionTaprootDigset( + txIndex: 0, + tapleafScript: leafC, + scriptPubKeys: existsUtxosScriptPubKeys.map((e) => e.toScriptPubKey()).toList(), + amounts: existsUtxosAmounts) + .asImmutableBytes; + final sig1 = key1.signTapRoot(digest, tweak: false); + final sig2 = key2.signTapRoot(digest, tweak: false); + final controlBlock = TaprootControlBlock.generate( + xOnlyOrInternalPubKey: internalKey.toXOnly(), leafScript: leafC, scriptTree: treeScript); + + return transaction.copyWith(witnesses: [ + TxWitnessInput(stack: [sig2, sig1, leafC.script.toHex(), controlBlock.toHex()]) + ]); +} + +Bip32Slip10Secp256k1 _deriveKey( + {String key = + "tprv8ZgxMBicQKsPdgBQV2Y9EVPSjAGhyZXArhwSzHwnV3FytzPRr8KCR8EKEpLeHbANAncgbc31a6QoXjBTARQiZ2h1Z2NgSCjFYeTqKpAN5Gc", + String path = "m/86'/1'/0'/0/1"}) { + Bip32Slip10Secp256k1 secp = + Bip32Slip10Secp256k1.fromExtendedKey(key, Bip44Coins.bitcoinTestnet.conf.keyNetVer); + return secp.derivePath(path) as Bip32Slip10Secp256k1; +} + +ECPublic _getPubKey({String path = "m/86'/1'/0'/0/1"}) { + return ECPublic.fromBytes(_deriveKey(path: path).publicKey.compressed); +} + +ECPrivate _getPrivateKey({String path = "m/86'/1'/0'/0/1"}) { + return ECPrivate.fromBytes(_deriveKey(path: path).privateKey.raw); +} + +TaprootBranch _generateScriptTree() { + final pk1 = _getPubKey(path: "m/86'/1'/0'/0/2"); + final pk2 = _getPubKey(path: "m/86'/1'/0'/0/3"); + final pk3 = _getPubKey(path: "m/86'/1'/0'/0/4"); + final musigSigner = Musig2Signer.generate([pk1, pk3]); + final leaf1 = TaprootLeaf( + script: Script(script: [ + BytesUtils.toHexString(musigSigner.aggPublicKey.toXOnly()), + BitcoinOpcode.opCheckSig + ])); + final leaf2 = TaprootLeaf(script: Script(script: [pk3.toXOnlyHex(), BitcoinOpcode.opCheckSig])); + + final leaf3 = TaprootLeaf( + script: Script(script: [ + pk1.toXOnlyHex(), + BitcoinOpcode.opCheckSig, + pk2.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + 2, + BitcoinOpcode.opNumEqual + ])); + return TaprootBranch(a: leaf3, b: TaprootBranch(a: leaf1, b: leaf2)); +} + +ECPublic _getInternalPubKey() { + final pk1 = _getPubKey(path: "m/86'/1'/0'/0/2"); + final pk2 = _getPubKey(path: "m/86'/1'/0'/0/3"); + final pk3 = _getPubKey(path: "m/86'/1'/0'/0/4"); + final internalKey = Musig2Signer.generate([pk1, pk2, pk3]); + + return internalKey.aggPublicKey; +} + +P2trAddress _generateAddress() { + final internalKey = _getInternalPubKey(); + return P2trAddress.fromInternalKey( + internalKey: internalKey.toXOnly(), treeScript: _generateScriptTree()); +} diff --git a/example/lib/psbt/psbt_example.dart b/example/lib/psbt/psbt_example.dart new file mode 100644 index 0000000..473a8d0 --- /dev/null +++ b/example/lib/psbt/psbt_example.dart @@ -0,0 +1,622 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; + +import '../musig/methods.dart'; + +void main() async { + final psbt = PsbtBuilderV0.create(); + final btcAddress1 = _getBtcAddress1(); + final btcAddress2 = _getBtcAddress2(); + final btcAddress3 = _getBtcAddress3(); + final btcAddress4 = _getBtcAddress4(); + final btcAddress5 = _getBtcAddress5(); + final btcAddress6 = _getBtcAddress6(); + final btcAddress7 = _getBtcAddress7(); + final btcAddress8 = _getBtcAddress8(); + final btcAddress9 = _getBtcAddress9(); + final btcAddress10 = _getBtcAddress10(); + final btcAddress11 = _getBtcAddress11(); + final btcAddress12 = _getBtcAddress12(); + final btcAddress13 = _getBtcAddress13(); + final btcAddress14 = _getBtcAddress14(); + final internalKey12 = _getSigner().signerPublicKey.toXOnly(); + final treeScript12 = _getScript12(); + final leafScript12 = _getScript12(); + final treeScript13 = _getScript13(); + final leafScript13 = _getLeafScript13(); + + final internalKey13 = _getInternalKey8(); + final internalKey14 = _getTapScriptSigner6(); + final treeScript14 = _treeScript25(); + final btcAddress15 = + P2shAddress.fromRedeemScript(script: Script(script: [BitcoinOpcode.opTrue])); + final btcAddress16 = + P2wshAddress.fromRedeemScript(script: Script(script: [BitcoinOpcode.opTrue])); + final btcAddress17 = P2shAddress.fromRedeemScript( + script: btcAddress16.toScriptPubKey(), type: P2shAddressType.p2wshInP2sh); + + final opHash160 = _getOpHash160(); + final sha256 = _getOpSHA256(); + final hash256 = _getOpHASH256(); + final tapHash160 = _getTapScriptHash160(); + + final utxos = await getPsbtUtxo(addresses: [ + PsbtUtxoRequest( + address: tapHash160.$1, + leafScript: TaprootLeaf(script: tapHash160.$2), + treeScript: TaprootLeaf(script: tapHash160.$2), + xOnlyOrInternalPubKey: tapHash160.$3.xOnly, + // p2shRedeemScript: opHash160.$3, + hash160: [PsbtInputHash160.fromPreImage(opHash160.$4)]), + PsbtUtxoRequest( + address: opHash160.$1, + p2shRedeemScript: opHash160.$3, + hash160: [PsbtInputHash160.fromPreImage(opHash160.$4)]), + PsbtUtxoRequest( + address: opHash160.$2, + witnessScript: opHash160.$3, + hash160: [PsbtInputHash160.fromPreImage(opHash160.$4)]), + PsbtUtxoRequest( + address: sha256.$1, + p2shRedeemScript: sha256.$3, + sha256: [PsbtInputSha256.fromPreImage(sha256.$4)]), + PsbtUtxoRequest( + address: sha256.$2, + witnessScript: sha256.$3, + sha256: [PsbtInputSha256.fromPreImage(sha256.$4)]), + PsbtUtxoRequest( + address: hash256.$1, + p2shRedeemScript: hash256.$3, + hash256: [PsbtInputHash256.fromPreImage(sha256.$4)]), + PsbtUtxoRequest( + address: hash256.$2, + witnessScript: hash256.$3, + hash256: [PsbtInputHash256.fromPreImage(sha256.$4)]), + PsbtUtxoRequest( + address: btcAddress14, + treeScript: treeScript14, + privateKeys: [_getSigner31().privateKey, _getSigner32().privateKey], + xOnlyOrInternalPubKey: internalKey14.signerPublicKey.toXOnly()), + PsbtUtxoRequest( + address: btcAddress13, + treeScript: treeScript13, + leafScript: leafScript13, + privateKeys: [_getSigner30().privateKey, _getSigner31().privateKey], + xOnlyOrInternalPubKey: internalKey13.toXOnly()), + PsbtUtxoRequest( + address: btcAddress12, + leafScript: leafScript12, + treeScript: treeScript12, + xOnlyOrInternalPubKey: internalKey12, + privateKeys: [ + _getSigner2().privateKey, + _getSigner3().privateKey, + ]), + PsbtUtxoRequest( + address: btcAddress9, + witnessScript: _mSigOnlyWitness().multiSigScript, + privateKeys: [ + _getSigner2().privateKey, + _getSigner().privateKey, + _getSigner3().privateKey + ]), + PsbtUtxoRequest( + address: btcAddress10, + p2shRedeemScript: _multisig().multiSigScript, + privateKeys: [ + _getSigner2().privateKey, + _getSigner().privateKey, + _getSigner5().privateKey + ]), + PsbtUtxoRequest( + address: btcAddress11, + p2shRedeemScript: _multisig2().multiSigScript, + privateKeys: [ + _getSigner2().privateKey, + _getSigner().privateKey, + _getSigner5().privateKey + ]), + PsbtUtxoRequest( + address: btcAddress15, p2shRedeemScript: Script(script: ["OP_TRUE"])), + PsbtUtxoRequest( + address: btcAddress17, + p2shRedeemScript: btcAddress16.toScriptPubKey(), + witnessScript: Script(script: ["OP_TRUE"])), + PsbtUtxoRequest( + address: btcAddress1, + privateKeys: [_getSigner().privateKey], + p2shRedeemScript: _getSigner().signerPublicKey.toRedeemScript()), + PsbtUtxoRequest( + address: btcAddress5, + witnessScript: _getSigner5().signerPublicKey.toP2wshScript(), + privateKeys: [_getSigner5().privateKey], + p2shRedeemScript: + _getSigner5().signerPublicKey.toP2wshAddress().toScriptPubKey()), + PsbtUtxoRequest( + address: btcAddress6, + privateKeys: [_getSigner6().privateKey], + xOnlyOrInternalPubKey: _getSigner6().privateKey.getPublic().toXOnly()), + PsbtUtxoRequest( + address: btcAddress2, privateKeys: [_getSigner2().privateKey]), + PsbtUtxoRequest( + address: btcAddress3, + p2shRedeemScript: + _getSigner3().signerPublicKey.toAddress().toScriptPubKey(), + privateKeys: [_getSigner3().privateKey]), + PsbtUtxoRequest( + address: btcAddress4, + privateKeys: [_getSigner4().privateKey], + p2shRedeemScript: + _getSigner4().signerPublicKey.toSegwitAddress().toScriptPubKey()), + PsbtUtxoRequest( + address: btcAddress7, + privateKeys: [_getSigner11().privateKey], + p2shRedeemScript: _getSigner11() + .signerPublicKey + .toRedeemScript(mode: PubKeyModes.uncompressed)), + PsbtUtxoRequest( + address: btcAddress8, privateKeys: [_getSigner12().privateKey]), + ]); + psbt.addUtxos(utxos); + final totalAmount = + utxos.fold(BigInt.zero, (p, c) => p + c.utxo.value); + + final rec1Amount = BtcUtils.toSatoshi("0.00002"); + + final fee = BtcUtils.toSatoshi("0.0001"); + final changeAmount = totalAmount - (rec1Amount + fee); + assert(!changeAmount.isNegative); + + psbt.addOutput( + PsbtTransactionOutput(amount: rec1Amount, address: _getReceipt2())); + psbt.addOutput( + PsbtTransactionOutput(amount: changeAmount, address: _getReceipt())); + + psbt.signAllInput( + (param) { + if (param.scriptPubKey == btcAddress13.toScriptPubKey()) { + assert(param.address == btcAddress13); + return PsbtSignerResponse( + sighash: BitcoinOpCodeConst.sighashAll, + signers: [_getSigner30(), _getSigner31()], + tapleafHash: leafScript13.hash(), + ); + } + if (param.scriptPubKey == btcAddress14.toScriptPubKey()) { + assert(param.address == btcAddress14); + return PsbtSignerResponse( + sighash: BitcoinOpCodeConst.sighashNone, + signers: [_getSigner31(), _getSigner32()], + ); + } + if (param.scriptPubKey == btcAddress1.toScriptPubKey()) { + assert(param.address == btcAddress1); + return PsbtSignerResponse(signers: [_getSigner()]); + } + if (param.scriptPubKey == btcAddress2.toScriptPubKey()) { + assert(param.address == btcAddress2); + return PsbtSignerResponse(signers: [_getSigner2()]); + } + if (param.scriptPubKey == btcAddress3.toScriptPubKey()) { + assert(param.address == btcAddress3); + return PsbtSignerResponse(signers: [_getSigner3()]); + } + if (param.scriptPubKey == btcAddress4.toScriptPubKey()) { + assert(param.address == btcAddress4); + return PsbtSignerResponse(signers: [_getSigner4()]); + } + if (param.scriptPubKey == btcAddress5.toScriptPubKey()) { + assert(param.address == btcAddress5); + return PsbtSignerResponse(signers: [_getSigner5()]); + } + if (param.scriptPubKey == btcAddress6.toScriptPubKey()) { + assert(param.address == btcAddress6); + return PsbtSignerResponse(signers: [_getSigner6()]); + } + if (param.scriptPubKey == btcAddress7.toScriptPubKey()) { + assert(param.address == btcAddress7); + return PsbtSignerResponse(signers: [_getSigner11()]); + } + if (param.scriptPubKey == btcAddress8.toScriptPubKey()) { + assert(param.address == btcAddress8); + return PsbtSignerResponse(signers: [_getSigner12()]); + } + if (param.scriptPubKey == btcAddress12.toScriptPubKey()) { + assert(param.address == btcAddress12); + return PsbtSignerResponse( + signers: [_getSigner2(), _getSigner3()], + tapleafHash: leafScript12.hash(), + ); + } + if (param.scriptPubKey == btcAddress9.toScriptPubKey()) { + assert(param.address == btcAddress9); + return PsbtSignerResponse( + signers: [_getSigner2(), _getSigner(), _getSigner3()], + ); + } + if (param.scriptPubKey == btcAddress10.toScriptPubKey()) { + assert(param.address == btcAddress10); + return PsbtSignerResponse( + signers: [_getSigner2(), _getSigner(), _getSigner5()], + ); + } + if (param.scriptPubKey == btcAddress11.toScriptPubKey()) { + assert(param.address == btcAddress11); + return PsbtSignerResponse( + signers: [_getSigner2(), _getSigner(), _getSigner5()], + ); + } + + return null; + }, + ); + // ignore: unused_local_variable + final finalTx = psbt.finalizeAll( + onFinalizeInput: (params) { + if (params.scriptPubKey == btcAddress13.toScriptPubKey()) { + return PsbtFinalizeResponse(tapleafHash: leafScript13.hash()); + } + return null; + }, + ); +} + +PsbtDefaultSigner _getSigner() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1022"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner11() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1033"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner12() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1034"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner6() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/0"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner2() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1025"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +BitcoinBaseAddress _getBtcAddress2() { + final s = _getSigner2(); + return s.privateKey.getPublic().toSegwitAddress(); +} + +BitcoinBaseAddress _getBtcAddress1() { + final s = _getSigner(); + return s.privateKey.getPublic().toP2pkInP2sh(); +} + +BitcoinBaseAddress _getBtcAddress7() { + final s = _getSigner11(); + return s.privateKey + .getPublic() + .toP2pkInP2sh(mode: PublicKeyType.uncompressed); +} + +BitcoinBaseAddress _getBtcAddress8() { + final s = _getSigner12(); + return s.privateKey.getPublic().toAddress(mode: PublicKeyType.uncompressed); +} + +BitcoinBaseAddress _getBtcAddress6() { + final s = _getSigner6(); + return s.privateKey.getPublic().toTaprootAddress(); +} + +BitcoinBaseAddress _getReceipt() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1022"); + final pk = ECPublic.fromBytes(acc.publicKey.compressed); + return pk.toP2wpkhInP2sh(); +} + +BitcoinBaseAddress _getReceipt2() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1025"); + final pk = ECPublic.fromBytes(acc.publicKey.compressed); + return pk.toP2wshInP2sh(); +} + +PsbtDefaultSigner _getSigner3() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1025"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +BitcoinBaseAddress _getBtcAddress3() { + final s = _getSigner3(); + return s.privateKey.getPublic().toP2pkhInP2sh(); +} + +PsbtDefaultSigner _getSigner4() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1022"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner5() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1025"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +BitcoinBaseAddress _getBtcAddress5() { + final s = _getSigner5(); + return s.privateKey.getPublic().toP2wshInP2sh(); +} + +BitcoinBaseAddress _getBtcAddress4() { + final s = _getSigner4(); + return s.privateKey.getPublic().toP2wpkhInP2sh(); +} + +TaprootLeaf _getScript12() { + final pk2 = _getSigner2().privateKey.getPublic(); + final pk3 = _getSigner3().privateKey.getPublic(); + return TaprootLeaf( + script: Script(script: [ + pk2.toXOnlyHex(), + BitcoinOpcode.opCheckSig, + pk3.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + 2, + BitcoinOpcode.opNumEqual, + ])); +} + +BitcoinBaseAddress _getBtcAddress12() { + final s = _getSigner(); + + return P2trAddress.fromInternalKey( + internalKey: s.privateKey.getPublic().toXOnly(), + treeScript: _getScript12()); +} + +PsbtDefaultSigner _getTapScriptSigner() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1022"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +MultiSignatureAddress _mSigOnlyWitness() { + final s1 = _getSigner(); + final s2 = _getSigner2(); + final s3 = _getSigner3(); + final m = MultiSignatureAddress(threshold: 3, signers: [ + MultiSignatureSigner(publicKey: s1.signerPublicKey.toHex(), weight: 1), + MultiSignatureSigner(publicKey: s2.signerPublicKey.toHex(), weight: 1), + MultiSignatureSigner(publicKey: s3.signerPublicKey.toHex(), weight: 1), + ]); + + return m; +} + +MultiSignatureAddress _multisig() { + final s1 = _getSigner(); + final s2 = _getSigner2(); + final s3 = _getSigner5(); + final m = MultiSignatureAddress(threshold: 4, signers: [ + MultiSignatureSigner(publicKey: s1.signerPublicKey.toHex(), weight: 1), + MultiSignatureSigner( + publicKey: s2.signerPublicKey.toHex(mode: PubKeyModes.uncompressed), + weight: 1), + MultiSignatureSigner( + publicKey: s3.signerPublicKey.toHex(mode: PubKeyModes.uncompressed), + weight: 2), + ]); + + return m; +} + +MultiSignatureAddress _multisig2() { + final s1 = _getSigner(); + final s2 = _getSigner2(); + final s3 = _getSigner5(); + final m = MultiSignatureAddress(threshold: 2, signers: [ + MultiSignatureSigner(publicKey: s1.signerPublicKey.toHex(), weight: 1), + MultiSignatureSigner( + publicKey: s2.signerPublicKey.toHex(mode: PubKeyModes.uncompressed), + weight: 1), + MultiSignatureSigner( + publicKey: s3.signerPublicKey.toHex(mode: PubKeyModes.uncompressed), + weight: 1), + ]); + + return m; +} + +BitcoinBaseAddress _getBtcAddress9() { + return _mSigOnlyWitness().toP2wshAddress(network: BitcoinNetwork.testnet); +} + +BitcoinBaseAddress _getBtcAddress10() { + return _multisig().toP2shAddress(); +} + +BitcoinBaseAddress _getBtcAddress11() { + return _multisig2().toP2shAddress(); +} + +PsbtDefaultSigner _getSigner30() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1025"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getTapScriptSigner6() { + final acc = _deriveKey(path: "m/86'/1'/0'/1/80"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner33() { + final acc = _deriveKey(path: "m/86'/1'/0'/0'/1025"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner31() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1022"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +PsbtDefaultSigner _getSigner32() { + final acc = _deriveKey(path: "m/86'/1'/0'/0/1025"); + return PsbtDefaultSigner(ECPrivate.fromBytes(acc.privateKey.raw)); +} + +TaprootLeaf _getLeafScript13() { + final pk2 = _getSigner30().privateKey.getPublic(); + final pk4 = _getSigner31().privateKey.getPublic(); + return TaprootLeaf( + script: Script(script: [ + pk2.toXOnlyHex(), + BitcoinOpcode.opCheckSig, + pk4.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + 2, + BitcoinOpcode.opNumEqual, + ])); +} + +TaprootTree _getScript13() { + final pk2 = _getSigner30().privateKey.getPublic(); + final pk3 = _getSigner33().privateKey.getPublic(); + final pk4 = _getSigner31().privateKey.getPublic(); + final pk5 = _getSigner32().privateKey.getPublic(); + final c1 = TaprootLeaf( + script: Script(script: [pk2.toXOnlyHex(), BitcoinOpcode.opCheckSig])); + final c2 = TaprootLeaf( + script: Script(script: [pk3.toXOnlyHex(), BitcoinOpcode.opCheckSig])); + final c3 = TaprootLeaf( + script: Script(script: [pk4.toXOnlyHex(), BitcoinOpcode.opCheckSig])); + + final c4 = TaprootLeaf( + script: Script(script: [ + pk2.toXOnlyHex(), + BitcoinOpcode.opCheckSig, + pk4.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + 2, + BitcoinOpcode.opNumEqual, + ])); + final c5 = TaprootLeaf( + script: Script(script: [pk5.toXOnlyHex(), BitcoinOpcode.opCheckSig])); + + return TaprootBranch( + a: TaprootBranch( + a: c1, + b: TaprootBranch( + a: c1, b: TaprootBranch(a: c2, b: TaprootBranch(a: c3, b: c5)))), + b: TaprootBranch(a: c4, b: c1)); +} + +ECPublic _getInternalKey8() { + return _getTapScriptSigner().signerPublicKey; +} + +P2trAddress _getBtcAddress13() { + final pk1 = _getTapScriptSigner(); + return P2trAddress.fromInternalKey( + internalKey: pk1.signerPublicKey.toXOnly(), treeScript: _getScript13()); +} + +TaprootTree _treeScript25() { + final pk2 = _getSigner12().privateKey.getPublic(); + final pk3 = _getSigner11().privateKey.getPublic(); + final pk4 = _getSigner31().privateKey.getPublic(); + final pk5 = _getSigner32().privateKey.getPublic(); + final pk6 = _getSigner32().privateKey.getPublic(); + return TaprootLeaf( + script: Script(script: [ + pk2.toXOnlyHex(), + BitcoinOpcode.opCheckSig, + pk3.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + pk4.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + pk5.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + pk6.toXOnlyHex(), + BitcoinOpcode.opCheckSigAdd, + 3, + BitcoinOpcode.opNumEqual, + ])); +} + +P2trAddress _getBtcAddress14() { + final pk7 = _getTapScriptSigner6().privateKey.getPublic(); + final c4 = _treeScript25(); + return P2trAddress.fromInternalKey( + internalKey: pk7.toXOnly(), treeScript: c4); +} + +(BitcoinBaseAddress, BitcoinBaseAddress, Script, List) _getOpHash160() { + final payload = StringUtils.encode('https://github.com/mrtnetwork'); + final hash160 = QuickCrypto.hash160(payload); + final script = Script(script: [ + BitcoinOpcode.opHash160, + BytesUtils.toHexString(hash160), + BitcoinOpcode.opEqual + ]); + final p2sh = P2shAddress.fromRedeemScript(script: script); + final p2wsh = P2wshAddress.fromRedeemScript(script: script); + return (p2sh, p2wsh, script, payload); +} + +(BitcoinBaseAddress, BitcoinBaseAddress, Script, List) _getOpSHA256() { + final payload = StringUtils.encode('https://github.com/mrtnetwork'); + final hash160 = QuickCrypto.sha256Hash(payload); + final script = Script(script: [ + BitcoinOpcode.opSha256, + BytesUtils.toHexString(hash160), + BitcoinOpcode.opEqual + ]); + final p2sh = P2shAddress.fromRedeemScript(script: script); + final p2wsh = P2wshAddress.fromRedeemScript(script: script); + return (p2sh, p2wsh, script, payload); +} + +(BitcoinBaseAddress, BitcoinBaseAddress, Script, List) _getOpHASH256() { + final preimage = StringUtils.encode('https://github.com/mrtnetwork'); + final hash = QuickCrypto.sha256DoubleHash(preimage); + final script = Script(script: [ + BitcoinOpcode.opHash256, + BytesUtils.toHexString(hash), + BitcoinOpcode.opEqual + ]); + final p2sh = P2shAddress.fromRedeemScript(script: script); + final p2wsh = P2wshAddress.fromRedeemScript(script: script); + return (p2sh, p2wsh, script, preimage); +} + +(BitcoinBaseAddress, Script, TaprootControlBlock, List) + _getTapScriptHash160() { + final preImage = StringUtils.encode('https://github.com/mrtnetwork'); + final hash = QuickCrypto.hash160(preImage); + final script = Script(script: [ + BitcoinOpcode.opHash160, + BytesUtils.toHexString(hash), + BitcoinOpcode.opEqual + ]); + final key = ECPrivate.fromBytes(List.filled(32, 12)); + final block = TaprootControlBlock.generate( + xOnlyOrInternalPubKey: key.getPublic().toXOnly(), + leafScript: TaprootLeaf(script: script), + scriptTree: TaprootLeaf(script: script)); + + final p2sh = P2trAddress.fromInternalKey( + internalKey: key.getPublic().toXOnly(), + treeScript: TaprootLeaf(script: script)); + return (p2sh, script, block, preImage); +} + +Bip32Slip10Secp256k1 _deriveKey( + {String key = + "tprv8ZgxMBicQKsPdgBQV2Y9EVPSjAGhyZXArhwSzHwnV3FytzPRr8KCR8EKEpLeHbANAncgbc31a6QoXjBTARQiZ2h1Z2NgSCjFYeTqKpAN5Gc", + String path = "m/86'/1'/0'/0/1"}) { + Bip32Slip10Secp256k1 secp = Bip32Slip10Secp256k1.fromExtendedKey( + key, Bip44Coins.bitcoinTestnet.conf.keyNetVer); + return secp.derivePath(path) as Bip32Slip10Secp256k1; +} diff --git a/example/lib/psbt/psbt_musig2_example.dart b/example/lib/psbt/psbt_musig2_example.dart new file mode 100644 index 0000000..eb9a70e --- /dev/null +++ b/example/lib/psbt/psbt_musig2_example.dart @@ -0,0 +1,85 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:example/musig/methods.dart'; + +Bip32Slip10Secp256k1 _deriveKey( + {String key = + "tprv8ZgxMBicQKsPdgBQV2Y9EVPSjAGhyZXArhwSzHwnV3FytzPRr8KCR8EKEpLeHbANAncgbc31a6QoXjBTARQiZ2h1Z2NgSCjFYeTqKpAN5Gc", + String path = "m/86'/1'/0'/0/1"}) { + Bip32Slip10Secp256k1 secp = Bip32Slip10Secp256k1.fromExtendedKey( + key, Bip44Coins.bitcoinTestnet.conf.keyNetVer); + return secp.derivePath(path) as Bip32Slip10Secp256k1; +} + +ECPrivate _getPrivateKey({String path = "m/86'/1'/0'/0/1"}) { + final key = _deriveKey(path: path); + return ECPrivate.fromBytes(key.privateKey.raw); +} + +void main() async { + final key = _getPrivateKey(); + final key2 = _getPrivateKey(path: "m/86'/1'/0'/0/2"); + final key3 = _getPrivateKey(path: "m/86'/1'/0'/0/3"); + + final aggpk = Musig2Signer.generate( + [key.getPublic(), key2.getPublic(), key3.getPublic()]); + final address = + P2trAddress.fromInternalKey(internalKey: aggpk.aggPublicKey.toXOnly()); + final utxo = await getPsbtUtxo(addresses: [ + PsbtUtxoRequest( + address: address, + xOnlyOrInternalPubKey: aggpk.aggPublicKey.toXOnly(), + muSig2ParticipantPublicKeys: [ + PsbtInputMuSig2ParticipantPublicKeys( + aggregatePubKey: aggpk.aggPublicKey, pubKeys: aggpk.publicKeys) + ]) + ]); + final psbt = PsbtBuilderV0.create(); + psbt.addUtxos(utxo); + psbt.addOutput(PsbtTransactionOutput( + amount: BtcUtils.toSatoshi('0.00001'), address: address)); + for (int i = 0; i < utxo.length; i++) { + final noncePub1 = aggpk.generateNonce(key.getPublic()); + final noncePub2 = aggpk.generateNonce(key2.getPublic()); + final noncePub3 = aggpk.generateNonce(key3.getPublic()); + psbt.musig2AddPubKeyNonce( + i, + PsbtInputMuSig2PublicNonce( + publicKey: key.getPublic(), + plainPublicKey: aggpk.aggPublicKey, + publicNonce: noncePub1.pubnonce)); + psbt.musig2AddPubKeyNonce( + i, + PsbtInputMuSig2PublicNonce( + publicKey: key2.getPublic(), + plainPublicKey: aggpk.aggPublicKey, + publicNonce: noncePub2.pubnonce)); + psbt.musig2AddPubKeyNonce( + i, + PsbtInputMuSig2PublicNonce( + publicKey: key3.getPublic(), + plainPublicKey: aggpk.aggPublicKey, + publicNonce: noncePub3.pubnonce)); + psbt.signInput( + signer: (p0) => PsbtSignerResponse(signers: [ + PsbtMusig2DefaultSigner( + privateKey: key, + aggPublicKey: aggpk.aggPublicKey, + publicKeys: aggpk.publicKeys, + nonce: noncePub1), + PsbtMusig2DefaultSigner( + privateKey: key2, + aggPublicKey: aggpk.aggPublicKey, + publicKeys: aggpk.publicKeys, + nonce: noncePub2), + PsbtMusig2DefaultSigner( + privateKey: key3, + aggPublicKey: aggpk.aggPublicKey, + publicKeys: aggpk.publicKeys, + nonce: noncePub3), + ]), + index: i); + } + // ignore: unused_local_variable + final finalTx = psbt.finalizeAll(); +} diff --git a/example/lib/services_examples/electrum/electrum_ssl_service.dart b/example/lib/services_examples/electrum/electrum_ssl_service.dart deleted file mode 100644 index 362c306..0000000 --- a/example/lib/services_examples/electrum/electrum_ssl_service.dart +++ /dev/null @@ -1,95 +0,0 @@ -/// Simple example how to send request to electurm with secure socket - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:example/services_examples/electrum/request_completer.dart'; - -class ElectrumSSLService with BitcoinBaseElectrumRPCService { - ElectrumSSLService._( - this.url, - SecureSocket channel, { - this.defaultRequestTimeOut = const Duration(seconds: 30), - }) : _socket = channel { - _subscription = - _socket!.listen(_onMessge, onError: _onClose, onDone: _onDone); - } - SecureSocket? _socket; - StreamSubscription>? _subscription; - final Duration defaultRequestTimeOut; - - Map requests = {}; - bool _isDiscounnect = false; - - bool get isConnected => _isDiscounnect; - - @override - final String url; - - void add(List params) { - if (_isDiscounnect) { - throw StateError("socket has been discounected"); - } - _socket?.add(params); - } - - void _onClose(Object? error) { - _isDiscounnect = true; - - _socket = null; - _subscription?.cancel().catchError((e) {}); - _subscription = null; - } - - void _onDone() { - _onClose(null); - } - - void discounnect() { - _onClose(null); - } - - static Future connect( - String url, { - Iterable? protocols, - Duration defaultRequestTimeOut = const Duration(seconds: 30), - final Duration connectionTimeOut = const Duration(seconds: 30), - }) async { - final parts = url.split(":"); - final channel = await SecureSocket.connect( - parts[0], - int.parse(parts[1]), - onBadCertificate: (certificate) => true, - ).timeout(connectionTimeOut); - - return ElectrumSSLService._(url, channel, - defaultRequestTimeOut: defaultRequestTimeOut); - } - - void _onMessge(List event) { - final Map decode = json.decode(utf8.decode(event)); - if (decode.containsKey("id")) { - final int id = int.parse(decode["id"]!.toString()); - final request = requests.remove(id); - request?.completer.complete(decode); - } - } - - @override - Future> call(ElectrumRequestDetails params, - [Duration? timeout]) async { - final AsyncRequestCompleter compeleter = - AsyncRequestCompleter(params.params); - - try { - requests[params.id] = compeleter; - add(params.toTCPParams()); - final result = await compeleter.completer.future - .timeout(timeout ?? defaultRequestTimeOut); - return result; - } finally { - requests.remove(params.id); - } - } -} diff --git a/example/lib/services_examples/electrum/electrum_tcp_service.dart b/example/lib/services_examples/electrum/electrum_tcp_service.dart deleted file mode 100644 index 750aad0..0000000 --- a/example/lib/services_examples/electrum/electrum_tcp_service.dart +++ /dev/null @@ -1,92 +0,0 @@ -/// Simple example how to send request to electurm with tcp - -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:example/services_examples/electrum/request_completer.dart'; - -class ElectrumTCPService with BitcoinBaseElectrumRPCService { - ElectrumTCPService._( - this.url, - Socket channel, { - this.defaultRequestTimeOut = const Duration(seconds: 30), - }) : _socket = channel { - _subscription = - _socket!.listen(_onMessge, onError: _onClose, onDone: _onDone); - } - Socket? _socket; - StreamSubscription>? _subscription; - final Duration defaultRequestTimeOut; - - Map requests = {}; - bool _isDiscounnect = false; - - bool get isConnected => _isDiscounnect; - - @override - final String url; - - void add(List params) { - if (_isDiscounnect) { - throw StateError("socket has been discounected"); - } - _socket?.add(params); - } - - void _onClose(Object? error) { - _isDiscounnect = true; - - _socket = null; - _subscription?.cancel().catchError((e) {}); - _subscription = null; - } - - void _onDone() { - _onClose(null); - } - - void discounnect() { - _onClose(null); - } - - static Future connect( - String url, { - Iterable? protocols, - Duration defaultRequestTimeOut = const Duration(seconds: 30), - final Duration connectionTimeOut = const Duration(seconds: 30), - }) async { - final parts = url.split(":"); - final channel = await Socket.connect(parts[0], int.parse(parts[1])) - .timeout(connectionTimeOut); - - return ElectrumTCPService._(url, channel, - defaultRequestTimeOut: defaultRequestTimeOut); - } - - void _onMessge(List event) { - final Map decode = json.decode(utf8.decode(event)); - if (decode.containsKey("id")) { - final int id = int.parse(decode["id"]!.toString()); - final request = requests.remove(id); - request?.completer.complete(decode); - } - } - - @override - Future> call(ElectrumRequestDetails params, - [Duration? timeout]) async { - final AsyncRequestCompleter compeleter = - AsyncRequestCompleter(params.params); - - try { - requests[params.id] = compeleter; - add(params.toWebSocketParams()); - final result = await compeleter.completer.future - .timeout(timeout ?? defaultRequestTimeOut); - return result; - } finally { - requests.remove(params.id); - } - } -} diff --git a/example/lib/services_examples/electrum/electrum_websocket_service.dart b/example/lib/services_examples/electrum/electrum_websocket_service.dart deleted file mode 100644 index 6ca10a2..0000000 --- a/example/lib/services_examples/electrum/electrum_websocket_service.dart +++ /dev/null @@ -1,91 +0,0 @@ -/// Simple example how to send request to electurm with websocket -import 'dart:async'; -import 'dart:convert'; -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:example/services_examples/cross_platform_websocket/core.dart'; -import 'package:example/services_examples/electrum/request_completer.dart'; - -class ElectrumWebSocketService with BitcoinBaseElectrumRPCService { - ElectrumWebSocketService._( - this.url, - WebSocketCore channel, { - this.defaultRequestTimeOut = const Duration(seconds: 30), - }) : _socket = channel { - _subscription = channel.stream - .cast() - .listen(_onMessge, onError: _onClose, onDone: _onDone); - } - WebSocketCore? _socket; - StreamSubscription? _subscription; - final Duration defaultRequestTimeOut; - - Map requests = {}; - bool _isDiscounnect = false; - - bool get isConnected => _isDiscounnect; - - @override - final String url; - - void add(List params) { - if (_isDiscounnect) { - throw StateError("socket has been discounected"); - } - _socket?.sink(params); - } - - void _onClose(Object? error) { - _isDiscounnect = true; - - _socket = null; - _subscription?.cancel().catchError((e) {}); - _subscription = null; - } - - void _onDone() { - _onClose(null); - } - - void discounnect() { - _onClose(null); - } - - static Future connect( - String url, { - Iterable? protocols, - Duration defaultRequestTimeOut = const Duration(seconds: 30), - final Duration connectionTimeOut = const Duration(seconds: 30), - }) async { - final channel = - await WebSocketCore.connect(url, protocols: protocols?.toList()); - - return ElectrumWebSocketService._(url, channel, - defaultRequestTimeOut: defaultRequestTimeOut); - } - - void _onMessge(String event) { - final Map decode = json.decode(event); - if (decode.containsKey("id")) { - final int id = int.parse(decode["id"]!.toString()); - final request = requests.remove(id); - request?.completer.complete(decode); - } - } - - @override - Future> call(ElectrumRequestDetails params, - [Duration? timeout]) async { - final AsyncRequestCompleter compeleter = - AsyncRequestCompleter(params.params); - - try { - requests[params.id] = compeleter; - add(params.toWebSocketParams()); - final result = await compeleter.completer.future - .timeout(timeout ?? defaultRequestTimeOut); - return result; - } finally { - requests.remove(params.id); - } - } -} diff --git a/example/lib/services_examples/electrum/request_completer.dart b/example/lib/services_examples/electrum/request_completer.dart deleted file mode 100644 index e6f00dc..0000000 --- a/example/lib/services_examples/electrum/request_completer.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'dart:async'; - -class AsyncRequestCompleter { - AsyncRequestCompleter(this.params); - final Completer> completer = Completer(); - final Map params; -} diff --git a/example/pubspec.lock b/example/pubspec.lock index 318e7a4..a55e895 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -9,22 +9,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bip32: + dependency: transitive + description: + name: bip32 + sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97" + url: "https://pub.dev" + source: hosted + version: "2.0.0" bitcoin_base: dependency: "direct main" description: path: ".." relative: true source: path - version: "4.7.0" + version: "6.1.0" blockchain_utils: dependency: "direct main" description: path: "." - ref: cake-update-v2 - resolved-ref: "2767a54ed2b0a23494e4e96a3fe5b5022b834b70" + ref: cake-update-v4 + resolved-ref: "7bf4c263900a81fcddbd7797169f0e2fbd3a9f46" url: "https://github.com/cake-tech/blockchain_utils" source: git - version: "3.3.0" + version: "4.0.0" boolean_selector: dependency: transitive description: @@ -33,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + bs58check: + dependency: transitive + description: + name: bs58check + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 + url: "https://pub.dev" + source: hosted + version: "1.0.2" characters: dependency: transitive description: @@ -53,18 +69,34 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + url: "https://pub.dev" + source: hosted + version: "1.19.0" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + crypto: + dependency: transitive + description: + name: crypto + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "3.0.6" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" fake_async: dependency: transitive description: @@ -91,14 +123,22 @@ packages: description: flutter source: sdk version: "0.0.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" http: dependency: "direct main" description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -107,30 +147,46 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.2" + intl: + dependency: transitive + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" + js: + dependency: transitive + description: + name: js + sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf + url: "https://pub.dev" + source: hosted + version: "0.7.1" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -151,18 +207,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.15.0" path: dependency: transitive description: @@ -171,11 +227,27 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe" + url: "https://pub.dev" + source: hosted + version: "3.9.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -188,10 +260,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -204,10 +276,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -220,18 +292,18 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.3" typed_data: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" vector_math: dependency: transitive description: @@ -244,17 +316,18 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.3.0" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" sdks: - dart: ">=3.3.0 <4.0.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 25d16d4..a192cc3 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -40,7 +40,8 @@ dependencies: blockchain_utils: git: url: https://github.com/cake-tech/blockchain_utils - ref: cake-update-v2 + ref: cake-update-v4 + http: ^1.2.0 dev_dependencies: diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index f76cc54..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1 +0,0 @@ -void main() async {} diff --git a/lib/bitcoin_base.dart b/lib/bitcoin_base.dart index cb559d7..3785814 100644 --- a/lib/bitcoin_base.dart +++ b/lib/bitcoin_base.dart @@ -1,29 +1,43 @@ /// library bitcoin_base -/// a comprehensive and versatile Go library for all your Bitcoin transaction needs. +/// a comprehensive and versatile Dart library for all your Bitcoin transaction needs. /// offers robust support for various Bitcoin transaction types, /// including spending transactions, Bitcoin address management, /// Bitcoin Schnorr signatures, BIP-39 mnemonic phrase generation, /// hierarchical deterministic (HD) wallet derivation, and Web3 Secret Storage Definition. -library bitcoin_base; +library; -export 'package:bitcoin_base/src/bitcoin/address/address.dart'; +export 'src/bitcoin/bitcoin.dart'; -export 'package:bitcoin_base/src/bitcoin/address/util.dart'; +export 'src/exception/exception.dart'; -export 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; +export 'src/bitcoin/address/util.dart'; -export 'package:bitcoin_base/src/crypto/crypto.dart'; +export 'src/bitcoin/script/scripts.dart'; -export 'package:bitcoin_base/src/models/network.dart'; +export 'src/bitcoin/amount/amount.dart'; -export 'package:bitcoin_base/src/provider/api_provider.dart'; +export 'src/crypto/crypto.dart'; -export 'package:bitcoin_base/src/utils/utils.dart'; +export 'src/models/network.dart'; -export 'package:bitcoin_base/src/cash_token/cash_token.dart'; +export 'src/provider/api_provider.dart'; -export 'package:bitcoin_base/src/bitcoin_cash/bitcoin_cash.dart'; +export 'src/provider/models/electrum/electrum_utxo.dart'; -export 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; +export 'src/provider/service/electrum/electrum.dart'; -export 'package:bitcoin_base/src/bitcoin/script/op_code/constant.dart'; +export 'src/provider/service/electrum/electrum_version.dart'; + +export 'src/utils/utils.dart'; + +export 'src/utils/btc_utils.dart'; + +export 'src/cash_token/cash_token.dart'; + +export 'src/bitcoin_cash/bitcoin_cash.dart'; + +export 'src/transaction_builder/builder.dart'; + +export 'src/psbt/psbt.dart'; + +export 'src/bitcoin/silent_payments/silent_payments.dart'; diff --git a/lib/src/bitcoin/address/address.dart b/lib/src/bitcoin/address/address.dart index 4dd7d7e..94850fa 100644 --- a/lib/src/bitcoin/address/address.dart +++ b/lib/src/bitcoin/address/address.dart @@ -6,15 +6,21 @@ // - Utility functions for address manipulation. // - encode/decode Segregated Witness (SegWit) address implementation. // - Enhanced functionality for improved handling of addresses across diverse networks. -library bitcoin_base.address; +library; -import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; +import 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; +import 'package:bitcoin_base/src/bitcoin/taproot/taproot.dart'; +import 'package:bitcoin_base/src/crypto/keypair/ec_public.dart'; import 'package:bitcoin_base/src/exception/exception.dart'; +import 'package:bitcoin_base/src/models/network.dart'; import 'package:bitcoin_base/src/utils/enumerate.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bitcoin_base/src/utils/script.dart'; +import 'util.dart'; part 'core.dart'; part 'legacy_address.dart'; part 'utils/address_utils.dart'; part 'segwit_address.dart'; part 'network_address.dart'; +part 'derivations.dart'; diff --git a/lib/src/bitcoin/address/core.dart b/lib/src/bitcoin/address/core.dart index de0045d..ee95f9c 100644 --- a/lib/src/bitcoin/address/core.dart +++ b/lib/src/bitcoin/address/core.dart @@ -7,9 +7,9 @@ abstract class BitcoinAddressType implements Enumerate { const BitcoinAddressType._(this.value); /// Factory method to create a BitcoinAddressType enum value from a name or value. - static BitcoinAddressType fromValue(String value) { + static BitcoinAddressType fromValue(String? value) { return values.firstWhere((element) => element.value == value, - orElse: () => throw BitcoinBasePluginException('Invalid BitcoinAddressType: $value')); + orElse: () => throw DartBitcoinPluginException('Unknown address type. $value')); } static BitcoinAddressType fromAddress(BitcoinBaseAddress address) { @@ -18,30 +18,32 @@ abstract class BitcoinAddressType implements Enumerate { } else if (address is P2shAddress) { return P2shAddressType.p2wpkhInP2sh; } else if (address is P2wshAddress) { - return SegwitAddresType.p2wsh; + return SegwitAddressType.p2wsh; } else if (address is P2trAddress) { - return SegwitAddresType.p2tr; + return SegwitAddressType.p2tr; } else if (address is SilentPaymentsAddresType) { return SilentPaymentsAddresType.p2sp; } else if (address is P2wpkhAddress) { - return SegwitAddresType.p2wpkh; + return SegwitAddressType.p2wpkh; } - throw BitcoinBasePluginException('Invalid BitcoinAddressType: $address'); + throw DartBitcoinPluginException('Invalid BitcoinAddressType: $address'); } /// Check if the address type is Pay-to-Script-Hash (P2SH). bool get isP2sh; int get hashLength; bool get isSegwit; + bool get isP2tr => false; + bool get isP2sh32 => isP2sh && hashLength == 32; // Enum values as a list for iteration static const List values = [ P2pkhAddressType.p2pkh, - SegwitAddresType.p2wpkh, - SegwitAddresType.p2tr, - SegwitAddresType.p2wsh, - SegwitAddresType.mweb, + SegwitAddressType.p2wpkh, + SegwitAddressType.p2tr, + SegwitAddressType.p2wsh, + SegwitAddressType.mweb, P2shAddressType.p2wshInP2sh, P2shAddressType.p2wpkhInP2sh, P2shAddressType.p2pkhInP2sh, @@ -55,19 +57,43 @@ abstract class BitcoinAddressType implements Enumerate { P2pkhAddressType.p2pkhwt, SilentPaymentsAddresType.p2sp ]; + T cast() { + if (this is! T) { + throw DartBitcoinPluginException( + "Invalid cast: expected ${T.runtimeType}, but found $runtimeType.", + details: {'expected': '$T', 'type': value}); + } + return this as T; + } + @override String toString() => value; } abstract class BitcoinBaseAddress { - BitcoinBaseAddress({this.network}); + const BitcoinBaseAddress(); + factory BitcoinBaseAddress.fromProgram( + {required String addressProgram, required BitcoinAddressType type}) { + if (type.isP2sh) { + return P2shAddress.fromHash160(h160: addressProgram, type: type.cast()); + } + return switch (type) { + PubKeyAddressType.p2pk => P2pkAddress(publicKey: ECPublic.fromHex(addressProgram)), + P2pkhAddressType.p2pkh || + P2pkhAddressType.p2pkhwt => + P2pkhAddress.fromHash160(h160: addressProgram, type: type.cast()), + SegwitAddressType.p2wpkh => P2wpkhAddress.fromProgram(program: addressProgram), + SegwitAddressType.p2wsh => P2wshAddress.fromProgram(program: addressProgram), + SegwitAddressType.p2tr => P2trAddress.fromProgram(program: addressProgram), + _ => throw DartBitcoinPluginException("Unsuported bitcoin address type."), + }; + } 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, [ @@ -96,46 +122,42 @@ abstract class BitcoinBaseAddress { return P2wpkhAddress.fromAddress(address: address, network: network); } - throw BitcoinBasePluginException('Invalid BitcoinBaseAddress: $address'); + throw DartBitcoinPluginException('Invalid BitcoinBaseAddress: $address'); } } -class PubKeyAddressType implements BitcoinAddressType { - const PubKeyAddressType._(this.value); - static const PubKeyAddressType p2pk = PubKeyAddressType._("P2PK"); +class PubKeyAddressType extends BitcoinAddressType { + const PubKeyAddressType._(super.value) : super._(); + static const PubKeyAddressType p2pk = PubKeyAddressType._('P2PK'); @override bool get isP2sh => false; @override bool get isSegwit => false; - @override - final String value; + @override int get hashLength => 20; @override String toString() => value; } -class P2pkhAddressType implements BitcoinAddressType { - const P2pkhAddressType._(this.value); - static const P2pkhAddressType p2pkh = P2pkhAddressType._("P2PKH"); - static const P2pkhAddressType p2pkhwt = P2pkhAddressType._("P2PKHWT"); +class P2pkhAddressType extends BitcoinAddressType { + const P2pkhAddressType._(super.value) : super._(); + static const P2pkhAddressType p2pkh = P2pkhAddressType._('P2PKH'); + static const P2pkhAddressType p2pkhwt = P2pkhAddressType._('P2PKHWT'); @override bool get isP2sh => false; @override bool get isSegwit => false; - @override - final String value; - @override int get hashLength => 20; @override String toString() => value; } -class P2shAddressType implements BitcoinAddressType { - const P2shAddressType._(this.value, this.hashLength, this.withToken); +class P2shAddressType extends BitcoinAddressType { + const P2shAddressType._(super.value, this.hashLength, this.withToken) : super._(); static const P2shAddressType p2wshInP2sh = P2shAddressType._("P2SH/P2WSH", _BitcoinAddressUtils.hash160DigestLength, false); static const P2shAddressType p2wpkhInP2sh = @@ -177,33 +199,30 @@ class P2shAddressType implements BitcoinAddressType { static const P2shAddressType p2pkInP2shwt = P2shAddressType._("P2SHWT/P2PK", _BitcoinAddressUtils.hash160DigestLength, true); - @override - final String value; - @override String toString() => value; } -class SegwitAddresType implements BitcoinAddressType { - const SegwitAddresType._(this.value); - static const SegwitAddresType p2wpkh = SegwitAddresType._("P2WPKH"); - static const SegwitAddresType p2tr = SegwitAddresType._("P2TR"); - static const SegwitAddresType p2wsh = SegwitAddresType._("P2WSH"); - static const SegwitAddresType mweb = SegwitAddresType._("MWEB"); +class SegwitAddressType extends BitcoinAddressType { + const SegwitAddressType._(super.value) : super._(); + static const SegwitAddressType p2wpkh = SegwitAddressType._("P2WPKH"); + static const SegwitAddressType p2tr = SegwitAddressType._("P2TR"); + static const SegwitAddressType p2wsh = SegwitAddressType._("P2WSH"); + static const SegwitAddressType mweb = SegwitAddressType._("MWEB"); @override bool get isP2sh => false; @override bool get isSegwit => true; @override - final String value; + bool get isP2tr => this == p2tr; @override int get hashLength { switch (this) { - case SegwitAddresType.p2wpkh: + case SegwitAddressType.p2wpkh: return 20; - case SegwitAddresType.mweb: + case SegwitAddressType.mweb: return 66; default: return 32; @@ -214,17 +233,14 @@ class SegwitAddresType implements BitcoinAddressType { String toString() => value; } -class SilentPaymentsAddresType implements BitcoinAddressType { - const SilentPaymentsAddresType._(this.value); +class SilentPaymentsAddresType extends BitcoinAddressType { + const SilentPaymentsAddresType._(super.value) : super._(); 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; diff --git a/lib/src/bitcoin/address/derivations.dart b/lib/src/bitcoin/address/derivations.dart new file mode 100644 index 0000000..44cfd00 --- /dev/null +++ b/lib/src/bitcoin/address/derivations.dart @@ -0,0 +1,253 @@ +// ignore_for_file: constant_identifier_names +// ignore_for_file: non_constant_identifier_names +part of 'package:bitcoin_base/src/bitcoin/address/address.dart'; + +class BitcoinDerivationInfo { + BitcoinDerivationInfo({ + required this.derivationType, + required String derivationPath, + required this.scriptType, + this.description, + }) : derivationPath = Bip32PathParser.parse(derivationPath); + final BitcoinDerivationType derivationType; + final Bip32Path derivationPath; + final BitcoinAddressType scriptType; + final String? description; + + static BitcoinDerivationInfo fromDerivationAndAddress( + BitcoinDerivationType derivationType, + String address, + BasedUtxoNetwork network, + ) { + final derivations = BITCOIN_DERIVATIONS[derivationType]!; + final scriptType = BitcoinAddressUtils.addressTypeFromStr(address, network); + + return derivations.firstWhere( + (element) => element.scriptType == scriptType, + orElse: () => derivations.first, + ); + } + + factory BitcoinDerivationInfo.fromJSON(Map json) { + return BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.values[json['derivationType']], + derivationPath: json['derivationPath'], + scriptType: BitcoinAddressType.values.firstWhere( + (type) => type.toString() == json['scriptType'], + ), + description: json['description'], + ); + } + + Map toJSON() { + return { + 'derivationType': derivationType.index, + 'derivationPath': derivationPath.toString(), + 'scriptType': scriptType.toString(), + 'description': description, + }; + } +} + +enum BitcoinDerivationType { bip39, electrum } + +// Define constant paths +abstract class BitcoinDerivationPaths { + static const String ELECTRUM = "m/0'"; + static const String NON_STANDARD = "m/0'"; + + // Legacy + static const String BIP44 = "m/44'/0'/0'"; + static const String BCH_BIP44 = "m/44'/145'/0'"; + + // Compatibility SegWit + static const String BIP49 = "m/49'/0'/0'"; + + // Native SegWit + static const String BIP84 = "m/84'/0'/0'"; + static const String LITECOIN_BIP84 = "m/84'/2'/0'"; + + // Taproot + static const String BIP86 = "m/86'/0'/0'"; + + static const String SILENT_PAYMENTS_SCAN = "m/352'/0'/0'/1'/0"; + static const String SILENT_PAYMENTS_SPEND = "m/352'/0'/0'/0'/0"; + + static const String LITECOIN_MWEB = "m/1000'"; + + static const String SAMOURAI_BAD_BANK = "m/84'/0'/2147483644'"; + static const String SAMOURAI_WHIRLPOOL_PREMIX = "m/84'/0'/2147483645'"; + static const String SAMOURAI_WHIRLPOOL_POSTMIX = "m/84'/0'/2147483646'"; + static const String SAMOURAI_RICOCHET_LEGACY = "m/44'/0'/2147483647'"; + static const String SAMOURAI_RICOCHET_COMPATIBILITY_SEGWIT = "m/49'/0'/2147483647'"; + static const String SAMOURAI_RICOCHET_NATIVE_SEGWIT = "m/84'/0'/2147483647'"; +} + +abstract class BitcoinDerivationInfos { + static final BitcoinDerivationInfo ELECTRUM = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.electrum, + derivationPath: BitcoinDerivationPaths.ELECTRUM, + description: "Electrum", + scriptType: SegwitAddressType.p2wpkh, + ); + + static final BitcoinDerivationInfo BIP44 = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP44, + description: "Standard BIP44", + scriptType: P2pkhAddressType.p2pkh, + ); + static final BitcoinDerivationInfo BIP49 = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP49, + description: "Standard BIP49 compatibility segwit", + scriptType: P2shAddressType.p2wpkhInP2sh, + ); + static final BitcoinDerivationInfo BIP84 = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP84, + description: "Standard BIP84 native segwit", + scriptType: SegwitAddressType.p2wpkh, + ); + static final BitcoinDerivationInfo BIP86 = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP86, + description: "Standard BIP86 Taproot", + scriptType: SegwitAddressType.p2tr, + ); + + static final BitcoinDerivationInfo LITECOIN = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.LITECOIN_BIP84, + description: "Default Litecoin", + scriptType: SegwitAddressType.p2wpkh, + ); + static final BitcoinDerivationInfo LITECOIN_MWEB = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.LITECOIN_MWEB, + description: "Litecoin MWEB", + scriptType: SegwitAddressType.mweb, + ); + + static final BitcoinDerivationInfo BCH = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BCH_BIP44, + description: "Bitcoin Cash (BCH) Default", + scriptType: P2pkhAddressType.p2pkh, + ); + + static final BitcoinDerivationInfo SILENT_PAYMENTS_SCAN = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SILENT_PAYMENTS_SCAN, + description: "Silent Payments Scan", + scriptType: SilentPaymentsAddresType.p2sp, + ); + + static final BitcoinDerivationInfo SILENT_PAYMENTS_SPEND = BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SILENT_PAYMENTS_SPEND, + description: "Silent Payments Spend", + scriptType: SilentPaymentsAddresType.p2sp, + ); +} + +final Map> BITCOIN_DERIVATIONS = { + BitcoinDerivationType.electrum: [BitcoinDerivationInfos.ELECTRUM], + BitcoinDerivationType.bip39: [ + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP44, + description: "Standard BIP44", + scriptType: P2pkhAddressType.p2pkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP49, + description: "Standard BIP49 compatibility segwit", + scriptType: P2shAddressType.p2wpkhInP2sh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP84, + description: "Standard BIP84 native segwit", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP86, + description: "Standard BIP86 Taproot", + scriptType: SegwitAddressType.p2tr, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.NON_STANDARD, + description: "Non-standard legacy", + scriptType: P2pkhAddressType.p2pkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.NON_STANDARD, + description: "Non-standard compatibility segwit", + scriptType: P2shAddressType.p2wpkhInP2sh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.NON_STANDARD, + description: "Non-standard native segwit", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP44, + description: "Samourai Deposit", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.BIP49, + description: "Samourai Deposit", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SAMOURAI_BAD_BANK, + description: "Samourai Bad Bank (toxic change)", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SAMOURAI_WHIRLPOOL_PREMIX, + description: "Samourai Whirlpool Pre Mix", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SAMOURAI_WHIRLPOOL_POSTMIX, + description: "Samourai Whirlpool Post Mix", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SAMOURAI_RICOCHET_LEGACY, + description: "Samourai Ricochet legacy", + scriptType: P2pkhAddressType.p2pkh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SAMOURAI_RICOCHET_COMPATIBILITY_SEGWIT, + description: "Samourai Ricochet compatibility segwit", + scriptType: P2shAddressType.p2wpkhInP2sh, + ), + BitcoinDerivationInfo( + derivationType: BitcoinDerivationType.bip39, + derivationPath: BitcoinDerivationPaths.SAMOURAI_RICOCHET_NATIVE_SEGWIT, + description: "Samourai Ricochet native segwit", + scriptType: SegwitAddressType.p2wpkh, + ), + BitcoinDerivationInfos.LITECOIN, + BitcoinDerivationInfos.SILENT_PAYMENTS_SCAN, + BitcoinDerivationInfos.SILENT_PAYMENTS_SPEND, + ], +}; + +const String ELECTRUM_PATH = BitcoinDerivationPaths.ELECTRUM; diff --git a/lib/src/bitcoin/address/legacy_address.dart b/lib/src/bitcoin/address/legacy_address.dart index c2b9798..39c63fe 100644 --- a/lib/src/bitcoin/address/legacy_address.dart +++ b/lib/src/bitcoin/address/legacy_address.dart @@ -9,12 +9,11 @@ abstract class LegacyAddress extends BitcoinBaseAddress { 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) { + : super() { final decode = _BitcoinAddressUtils.decodeLegacyAddressWithNetworkAndType( address: address, type: type, @@ -22,31 +21,31 @@ abstract class LegacyAddress extends BitcoinBaseAddress { ); if (decode == null) { - throw BitcoinBasePluginException("Invalid ${network.conf.coinName} address"); + throw DartBitcoinPluginException('Invalid ${network.conf.coinName} address'); } _addressProgram = decode; } - LegacyAddress.fromPubkey({required ECPublic pubkey, super.network}) + LegacyAddress.fromPubkey({required ECPublic pubkey}) : _pubkey = pubkey, _addressProgram = _BitcoinAddressUtils.pubkeyToHash160(pubkey.toHex()); - LegacyAddress.fromRedeemScript({required Script script, super.network}) + LegacyAddress.fromRedeemScript({required Script script}) : _addressProgram = _BitcoinAddressUtils.scriptToHash160(script); - LegacyAddress.fromScriptSig({required Script script, super.network}) { + LegacyAddress.fromScriptSig({required Script script}) { switch (type) { case PubKeyAddressType.p2pk: _signature = script.findScriptParam(0); break; case P2pkhAddressType.p2pkh: - if (script.script.length != 2) throw ArgumentError('Input is invalid'); + if (script.script.length != 2) throw DartBitcoinPluginException('Input is invalid'); _signature = script.findScriptParam(0); if (!isCanonicalScriptSignature(BytesUtils.fromHexString(_signature!))) { - throw ArgumentError('Input has invalid signature'); + throw DartBitcoinPluginException('Input has invalid signature'); } _pubkey = ECPublic.fromHex(script.findScriptParam(1)); @@ -79,15 +78,9 @@ abstract class LegacyAddress extends BitcoinBaseAddress { } @override - String toAddress([BasedUtxoNetwork? network]) { - network ??= this.network; - - if (network == null) { - throw const BitcoinBasePluginException("Network is required"); - } - + String toAddress(BasedUtxoNetwork network) { if (!network.supportedAddress.contains(type)) { - throw BitcoinBasePluginException("network does not support ${type.value} address"); + throw DartBitcoinPluginException("network does not support ${type.value} address"); } return _BitcoinAddressUtils.legacyToAddress( @@ -101,17 +94,41 @@ abstract class LegacyAddress extends BitcoinBaseAddress { String pubKeyHash() { return _BitcoinAddressUtils.pubKeyHash(toScriptPubKey()); } + + @override + operator ==(other) { + if (identical(this, other)) return true; + if (other is! LegacyAddress) return false; + if (runtimeType != other.runtimeType) return false; + if (type != other.type) return false; + return _addressProgram == other._addressProgram; + } + + @override + int get hashCode => HashCodeGenerator.generateHashCode([_addressProgram, type]); } class P2shAddress extends LegacyAddress { - static RegExp get regex => RegExp(r'[23M][a-km-zA-HJ-NP-Z1-9]{25,34}'); + static final 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(); + factory P2shAddress.fromRedeemScript32({ + required Script script, + P2shAddressType type = P2shAddressType.p2pkInP2sh32, + }) { + if (type.hashLength != 32) { + throw DartBitcoinPluginException("Invalid P2sh 32 address type."); + } + + return P2shAddress.fromHash160( + h160: BytesUtils.toHexString(QuickCrypto.sha256DoubleHash(script.toBytes())), + ); + } + P2shAddress.fromAddress({ required super.address, required super.network, @@ -120,25 +137,61 @@ class P2shAddress extends LegacyAddress { P2shAddress.fromHash160({ required super.h160, - super.network, this.type = P2shAddressType.p2pkInP2sh, }) : super.fromHash160(type: type); + factory P2shAddress.fromDerivation({ + required Bip32Base bip32, + required BitcoinDerivationInfo derivationInfo, + required bool isChange, + required int index, + P2shAddressType type = P2shAddressType.p2wpkhInP2sh, + }) { + final fullPath = derivationInfo.derivationPath + .addElem(Bip32KeyIndex(BitcoinAddressUtils.getAccountFromChange(isChange))) + .addElem(Bip32KeyIndex(index)); + final pubkey = ECPublic.fromBip32(bip32.derive(fullPath).publicKey); + + switch (type) { + case P2shAddressType.p2pkInP2sh: + return pubkey.toP2pkInP2sh(); + case P2shAddressType.p2pkhInP2sh: + return pubkey.toP2pkhInP2sh(); + case P2shAddressType.p2wshInP2sh: + return pubkey.toP2wshInP2sh(); + case P2shAddressType.p2wpkhInP2sh: + return pubkey.toP2wpkhInP2sh(); + default: + throw UnimplementedError(); + } + } + + factory P2shAddress.fromPath({required Bip32Base bip32, required Bip32Path path}) { + return ECPublic.fromBip32(bip32.derive(path).publicKey).toP2wpkhInP2sh(); + } + factory P2shAddress.fromScriptPubkey({ required Script script, - BasedUtxoNetwork? network, type = P2shAddressType.p2pkInP2sh, }) { if (script.getAddressType() is! P2shAddressType) { - throw ArgumentError("Invalid scriptPubKey"); + throw DartBitcoinPluginException("Invalid scriptPubKey"); } - return P2shAddress.fromHash160(h160: script.findScriptParam(1), network: network, type: type); + return P2shAddress.fromHash160(h160: script.findScriptParam(1), type: type); } @override final P2shAddressType type; + @override + String toAddress(BasedUtxoNetwork network) { + if (!network.supportedAddress.contains(type)) { + throw DartBitcoinPluginException('network does not support ${type.value} address.'); + } + return super.toAddress(network); + } + /// Returns the scriptPubKey (P2SH) that corresponds to this address @override Script toScriptPubKey() { @@ -152,21 +205,31 @@ class P2shAddress extends LegacyAddress { ); } } + + @override + operator ==(other) { + if (identical(this, other)) return true; + if (other is! LegacyAddress) return false; + if (runtimeType != other.runtimeType) return false; + return _addressProgram == other._addressProgram; + } + + @override + int get hashCode => HashCodeGenerator.generateHashCode([_addressProgram]); } class P2pkhAddress extends LegacyAddress { - static RegExp get regex => RegExp(r'[1mnL][a-km-zA-HJ-NP-Z1-9]{25,34}'); + static final 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"); + throw DartBitcoinPluginException("Invalid scriptPubKey"); } - return P2pkhAddress.fromHash160(h160: script.findScriptParam(2), network: network, type: type); + return P2pkhAddress.fromHash160(h160: script.findScriptParam(2), type: type); } P2pkhAddress.fromAddress({ @@ -175,17 +238,27 @@ class P2pkhAddress extends LegacyAddress { this.type = P2pkhAddressType.p2pkh, }) : super.fromAddress(); - P2pkhAddress.fromHash160({ - required super.h160, - super.network, - this.type = P2pkhAddressType.p2pkh, - }) : super.fromHash160(type: type); + P2pkhAddress.fromHash160({required super.h160, this.type = P2pkhAddressType.p2pkh}) + : super.fromHash160(type: type); - P2pkhAddress.fromScriptSig({ - required super.script, - super.network, - this.type = P2pkhAddressType.p2pkh, - }) : super.fromScriptSig(); + P2pkhAddress.fromScriptSig({required super.script, this.type = P2pkhAddressType.p2pkh}) + : super.fromScriptSig(); + + factory P2pkhAddress.fromDerivation({ + required Bip32Base bip32, + required BitcoinDerivationInfo derivationInfo, + required bool isChange, + required int index, + }) { + final fullPath = derivationInfo.derivationPath + .addElem(Bip32KeyIndex(BitcoinAddressUtils.getAccountFromChange(isChange))) + .addElem(Bip32KeyIndex(index)); + return ECPublic.fromBip32(bip32.derive(fullPath).publicKey).toP2pkhAddress(); + } + + factory P2pkhAddress.fromPath({required Bip32Base bip32, required Bip32Path path}) { + return ECPublic.fromBip32(bip32.derive(path).publicKey).toP2pkhAddress(); + } @override Script toScriptPubKey() { @@ -209,7 +282,7 @@ class P2pkhAddress extends LegacyAddress { class P2pkAddress extends LegacyAddress { static RegExp get regex => RegExp(r'1([A-Za-z0-9]{34})'); - P2pkAddress({required ECPublic publicKey, super.network}) + P2pkAddress({required ECPublic publicKey}) : _pubkeyHex = publicKey.toHex(), super.fromPubkey(pubkey: publicKey); @@ -219,7 +292,7 @@ class P2pkAddress extends LegacyAddress { factory P2pkAddress.fromScriptPubkey({required Script script}) { if (script.getAddressType() is! PubKeyAddressType) { - throw ArgumentError("Invalid scriptPubKey"); + throw DartBitcoinPluginException("Invalid scriptPubKey"); } return P2pkAddress.fromPubkey(pubkey: ECPublic.fromHex(script.script[0])); @@ -233,13 +306,7 @@ class P2pkAddress extends LegacyAddress { } @override - String toAddress([BasedUtxoNetwork? network]) { - network ??= this.network; - - if (network == null) { - throw const BitcoinBasePluginException("Network is required"); - } - + String toAddress(BasedUtxoNetwork network) { return _BitcoinAddressUtils.legacyToAddress( network: network, addressProgram: _BitcoinAddressUtils.pubkeyToHash160(_pubkeyHex), @@ -249,4 +316,14 @@ class P2pkAddress extends LegacyAddress { @override final PubKeyAddressType type = PubKeyAddressType.p2pk; + + @override + operator ==(other) { + if (identical(this, other)) return true; + if (other is! P2pkAddress) return false; + return _pubkeyHex == other._pubkeyHex; + } + + @override + int get hashCode => HashCodeGenerator.generateHashCode([_pubkeyHex, type]); } diff --git a/lib/src/bitcoin/address/network_address.dart b/lib/src/bitcoin/address/network_address.dart index 8b8fd7f..2a8e1bb 100644 --- a/lib/src/bitcoin/address/network_address.dart +++ b/lib/src/bitcoin/address/network_address.dart @@ -2,96 +2,88 @@ part of 'package:bitcoin_base/src/bitcoin/address/address.dart'; /// An abstract class representing a forked address for a specific network. abstract class BitcoinNetworkAddress { - const BitcoinNetworkAddress(); + const BitcoinNetworkAddress._( + {required this.address, required this.network, required this.baseAddress}); /// The underlying Bitcoin base address. - abstract final BitcoinBaseAddress baseAddress; + final BitcoinBaseAddress baseAddress; /// Converts the address to a string representation for the specified network [T]. - String toAddress([T? network]) { - return network == null ? address : baseAddress.toAddress(); + String toAddress([T? updateNetwork]) { + return updateNetwork == null ? address : baseAddress.toAddress(updateNetwork); } /// The type of the Bitcoin address. BitcoinAddressType get type => baseAddress.type; /// The string representation of the address. - abstract final String address; + final String address; + + final T network; } /// A concrete implementation of [BitcoinNetworkAddress] for Bitcoin network. class BitcoinAddress extends BitcoinNetworkAddress { - const BitcoinAddress._(this.baseAddress, this.address); + const BitcoinAddress._(BitcoinBaseAddress baseAddress, String address, BitcoinNetwork network) + : super._(address: address, baseAddress: baseAddress, network: network); factory BitcoinAddress(String address, {BitcoinNetwork network = BitcoinNetwork.mainnet}) { - return BitcoinAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); + return BitcoinAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address, network); } factory BitcoinAddress.fromBaseAddress(BitcoinBaseAddress address, - {DashNetwork network = DashNetwork.mainnet}) { + {BitcoinNetwork network = BitcoinNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return BitcoinAddress._(baseAddress, baseAddress.toAddress()); + return BitcoinAddress._(baseAddress, baseAddress.toAddress(network), network); } - @override - final BitcoinBaseAddress baseAddress; - @override - final String address; } /// A concrete implementation of [BitcoinNetworkAddress] for Doge network. class DogeAddress extends BitcoinNetworkAddress { - const DogeAddress._(this.baseAddress, this.address); + const DogeAddress._(BitcoinBaseAddress baseAddress, String address, DogecoinNetwork network) + : super._(address: address, baseAddress: baseAddress, network: network); factory DogeAddress(String address, {DogecoinNetwork network = DogecoinNetwork.mainnet}) { - return DogeAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); + return DogeAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address, network); } factory DogeAddress.fromBaseAddress(BitcoinBaseAddress address, {DogecoinNetwork network = DogecoinNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return DogeAddress._(baseAddress, baseAddress.toAddress()); + return DogeAddress._(baseAddress, baseAddress.toAddress(network), network); } - @override - final BitcoinBaseAddress baseAddress; - - @override - final String address; } /// A concrete implementation of [BitcoinNetworkAddress] for Pepecoin network. class PepeAddress extends BitcoinNetworkAddress { - const PepeAddress._(this.baseAddress, this.address); + const PepeAddress._(BitcoinBaseAddress baseAddress, String address, PepeNetwork network) + : super._(address: address, network: network, baseAddress: baseAddress); factory PepeAddress(String address, {PepeNetwork network = PepeNetwork.mainnet}) { - return PepeAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); + return PepeAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address, network); } factory PepeAddress.fromBaseAddress(BitcoinBaseAddress address, {PepeNetwork network = PepeNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return PepeAddress._(baseAddress, baseAddress.toAddress()); + return PepeAddress._(baseAddress, baseAddress.toAddress(network), network); } - @override - final BitcoinBaseAddress baseAddress; - - @override - final String address; } /// A concrete implementation of [BitcoinNetworkAddress] for Litecoin network. class LitecoinAddress extends BitcoinNetworkAddress { - LitecoinAddress._(this.baseAddress, this.address); + const LitecoinAddress._(BitcoinBaseAddress baseAddress, String address, LitecoinNetwork network) + : super._(address: address, baseAddress: baseAddress, network: network); factory LitecoinAddress(String address, {LitecoinNetwork network = LitecoinNetwork.mainnet}) { - return LitecoinAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); + return LitecoinAddress._( + _BitcoinAddressUtils.decodeAddress(address, network), address, network); } factory LitecoinAddress.fromBaseAddress(BitcoinBaseAddress address, {LitecoinNetwork network = LitecoinNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return LitecoinAddress._(baseAddress, baseAddress.toAddress()); + return LitecoinAddress._(baseAddress, baseAddress.toAddress(network), network); } - @override - final BitcoinBaseAddress baseAddress; - @override - final String address; } /// A concrete implementation of [BitcoinNetworkAddress] for Bitcoin cash network. class BitcoinCashAddress extends BitcoinNetworkAddress { - const BitcoinCashAddress._(this.baseAddress, this.address); + const BitcoinCashAddress._( + BitcoinBaseAddress baseAddress, String address, BitcoinCashNetwork network) + : super._(address: address, baseAddress: baseAddress, network: network); factory BitcoinCashAddress(String address, {BitcoinCashNetwork network = BitcoinCashNetwork.mainnet, bool validateNetworkPrefix = false}) { @@ -101,59 +93,67 @@ class BitcoinCashAddress extends BitcoinNetworkAddress { validateNetworkHRP: validateNetworkPrefix, ); if (decodeAddress == null) { - throw BitcoinBasePluginException("Invalid ${network.value} address."); + throw DartBitcoinPluginException('Invalid ${network.value} address.'); } - return BitcoinCashAddress._(decodeAddress, address); + return BitcoinCashAddress._(decodeAddress, address, network); } factory BitcoinCashAddress.fromBaseAddress(BitcoinBaseAddress address, {BitcoinCashNetwork network = BitcoinCashNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return BitcoinCashAddress._(baseAddress, baseAddress.toAddress()); + return BitcoinCashAddress._(baseAddress, baseAddress.toAddress(network), network); } - @override - final BitcoinBaseAddress baseAddress; - @override - final String address; @override - String toAddress([BitcoinCashNetwork? network, String? prefix]) { + String toAddress([BitcoinCashNetwork? updateNetwork, String? prefix]) { if (prefix != null) { return BchAddrConverter.convert(address, prefix, null); } - return super.toAddress(network); + return super.toAddress(updateNetwork); } } /// A concrete implementation of [BitcoinNetworkAddress] for Dash network. class DashAddress extends BitcoinNetworkAddress { - const DashAddress._(this.baseAddress, this.address); + const DashAddress._(BitcoinBaseAddress baseAddress, String address, DashNetwork network) + : super._(address: address, baseAddress: baseAddress, network: network); factory DashAddress(String address, {DashNetwork network = DashNetwork.mainnet}) { - return DashAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); + return DashAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address, network); } factory DashAddress.fromBaseAddress(BitcoinBaseAddress address, {DashNetwork network = DashNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return DashAddress._(baseAddress, baseAddress.toAddress()); + return DashAddress._(baseAddress, baseAddress.toAddress(network), network); } - @override - final BitcoinBaseAddress baseAddress; - @override - final String address; } /// A concrete implementation of [BitcoinNetworkAddress] for bitcoinSV network. -class BitcoinSVAddress extends BitcoinNetworkAddress { - const BitcoinSVAddress._(this.baseAddress, this.address); +class BitcoinSVAddress extends BitcoinNetworkAddress { + const BitcoinSVAddress._(BitcoinBaseAddress baseAddress, String address, BitcoinSVNetwork network) + : super._(address: address, baseAddress: baseAddress, network: network); factory BitcoinSVAddress(String address, {BitcoinSVNetwork network = BitcoinSVNetwork.mainnet}) { - return BitcoinSVAddress._(_BitcoinAddressUtils.decodeAddress(address, network), address); + return BitcoinSVAddress._( + _BitcoinAddressUtils.decodeAddress(address, network), address, network); } factory BitcoinSVAddress.fromBaseAddress(BitcoinBaseAddress address, {BitcoinSVNetwork network = BitcoinSVNetwork.mainnet}) { final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); - return BitcoinSVAddress._(baseAddress, baseAddress.toAddress()); + return BitcoinSVAddress._(baseAddress, baseAddress.toAddress(network), network); + } +} + +/// A concrete implementation of [BitcoinNetworkAddress] for Electra protocol network. +class ElectraProtocolAddress extends BitcoinNetworkAddress { + const ElectraProtocolAddress._( + BitcoinBaseAddress baseAddress, String address, ElectraProtocolNetwork network) + : super._(address: address, baseAddress: baseAddress, network: network); + factory ElectraProtocolAddress(String address, + {ElectraProtocolNetwork network = ElectraProtocolNetwork.mainnet}) { + return ElectraProtocolAddress._( + _BitcoinAddressUtils.decodeAddress(address, network), address, network); + } + factory ElectraProtocolAddress.fromBaseAddress(BitcoinBaseAddress address, + {ElectraProtocolNetwork network = ElectraProtocolNetwork.mainnet}) { + final baseAddress = _BitcoinAddressUtils.validateAddress(address, network); + return ElectraProtocolAddress._(baseAddress, baseAddress.toAddress(network), network); } - @override - final BitcoinBaseAddress baseAddress; - @override - final String address; } diff --git a/lib/src/bitcoin/address/segwit_address.dart b/lib/src/bitcoin/address/segwit_address.dart index 740e1ea..b7ea9f6 100644 --- a/lib/src/bitcoin/address/segwit_address.dart +++ b/lib/src/bitcoin/address/segwit_address.dart @@ -5,7 +5,7 @@ abstract class SegwitAddress extends BitcoinBaseAddress { required String address, required BasedUtxoNetwork network, required this.segwitVersion, - }) : super(network: network) { + }) : super() { addressProgram = _BitcoinAddressUtils.toSegwitProgramWithVersionAndNetwork( address: address, version: segwitVersion, @@ -15,8 +15,7 @@ abstract class SegwitAddress extends BitcoinBaseAddress { SegwitAddress.fromProgram({ required String program, - required SegwitAddresType addressType, - super.network, + required SegwitAddressType addressType, required this.segwitVersion, this.pubkey, }) : addressProgram = _BitcoinAddressUtils.validateAddressProgram(program, addressType), @@ -24,7 +23,6 @@ abstract class SegwitAddress extends BitcoinBaseAddress { SegwitAddress.fromRedeemScript({ required Script script, - super.network, required this.segwitVersion, }) : addressProgram = _BitcoinAddressUtils.segwitScriptToSHA256(script); @@ -34,15 +32,9 @@ abstract class SegwitAddress extends BitcoinBaseAddress { ECPublic? pubkey; @override - String toAddress([BasedUtxoNetwork? network]) { - network ??= this.network; - - if (network == null) { - throw const BitcoinBasePluginException("Network is required"); - } - + String toAddress(BasedUtxoNetwork network) { if (!network.supportedAddress.contains(type)) { - throw BitcoinBasePluginException("network does not support ${type.value} address"); + throw DartBitcoinPluginException("network does not support ${type.value} address"); } return _BitcoinAddressUtils.segwitToAddress( @@ -56,29 +48,58 @@ abstract class SegwitAddress extends BitcoinBaseAddress { String pubKeyHash() { return _BitcoinAddressUtils.pubKeyHash(toScriptPubKey()); } + + @override + operator ==(other) { + if (identical(this, other)) return true; + if (other is! SegwitAddress) return false; + if (runtimeType != other.runtimeType) return false; + if (type != other.type) return false; + return addressProgram == addressProgram && segwitVersion == other.segwitVersion; + } + + @override + int get hashCode => HashCodeGenerator.generateHashCode([addressProgram, segwitVersion, type]); } class P2wpkhAddress extends SegwitAddress { - static RegExp get regex => RegExp(r'(bc|tb|ltc)1q[ac-hj-np-z02-9]{25,39}'); + static final 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}) + P2wpkhAddress.fromProgram({required super.program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV0, - addressType: SegwitAddresType.p2wpkh, + addressType: SegwitAddressType.p2wpkh, ); - P2wpkhAddress.fromRedeemScript({required super.script, super.network}) + P2wpkhAddress.fromRedeemScript({required super.script}) : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV0); - factory P2wpkhAddress.fromScriptPubkey({required Script script, BasedUtxoNetwork? network}) { - if (script.getAddressType() != SegwitAddresType.p2wpkh) { - throw ArgumentError("Invalid scriptPubKey"); + factory P2wpkhAddress.fromDerivation({ + required Bip32Base bip32, + required BitcoinDerivationInfo derivationInfo, + required bool isChange, + required int index, + }) { + final fullPath = derivationInfo.derivationPath + .addElem(Bip32KeyIndex(BitcoinAddressUtils.getAccountFromChange(isChange))) + .addElem(Bip32KeyIndex(index)); + + return ECPublic.fromBip32(bip32.derive(fullPath).publicKey).toP2wpkhAddress(); + } + + factory P2wpkhAddress.fromPath({required Bip32Base bip32, required Bip32Path path}) { + return ECPublic.fromBip32(bip32.derive(path).publicKey).toP2wpkhAddress(); + } + + factory P2wpkhAddress.fromScriptPubkey({required Script script}) { + if (script.getAddressType() != SegwitAddressType.p2wpkh) { + throw DartBitcoinPluginException("Invalid scriptPubKey"); } - return P2wpkhAddress.fromProgram(program: script.findScriptParam(1), network: network); + return P2wpkhAddress.fromProgram(program: script.findScriptParam(1)); } /// returns the scriptPubKey of a P2WPKH witness script @@ -89,31 +110,67 @@ class P2wpkhAddress extends SegwitAddress { /// returns the type of address @override - SegwitAddresType get type => SegwitAddresType.p2wpkh; + SegwitAddressType get type => SegwitAddressType.p2wpkh; } class P2trAddress extends SegwitAddress { - static RegExp get regex => + static final 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.fromInternalKey({ + required List internalKey, + TaprootTree? treeScript, + List? merkleRoot, + bool tweak = true, + }) : super.fromProgram( + program: tweak + ? BytesUtils.toHexString( + TaprootUtils.tweakPublicKey( + internalKey, + treeScript: treeScript, + merkleRoot: merkleRoot, + ).toXonly(), + ) + : BytesUtils.toHexString(internalKey), + pubkey: ECPublic.fromBytes(internalKey), + segwitVersion: _BitcoinAddressUtils.segwitV1, + addressType: SegwitAddressType.p2tr, + ); + P2trAddress.fromAddress({required super.address, required super.network}) : super.fromAddress(segwitVersion: _BitcoinAddressUtils.segwitV1); - P2trAddress.fromProgram({required super.program, super.network, super.pubkey}) + P2trAddress.fromProgram({required super.program, super.pubkey}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV1, - addressType: SegwitAddresType.p2tr, + addressType: SegwitAddressType.p2tr, ); - P2trAddress.fromRedeemScript({required super.script, super.network}) + P2trAddress.fromRedeemScript({required super.script}) : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV1); - factory P2trAddress.fromScriptPubkey({required Script script, BasedUtxoNetwork? network}) { - if (script.getAddressType() != SegwitAddresType.p2tr) { - throw ArgumentError("Invalid scriptPubKey"); + factory P2trAddress.fromDerivation({ + required Bip32Base bip32, + required BitcoinDerivationInfo derivationInfo, + required bool isChange, + required int index, + }) { + final fullPath = derivationInfo.derivationPath + .addElem(Bip32KeyIndex(BitcoinAddressUtils.getAccountFromChange(isChange))) + .addElem(Bip32KeyIndex(index)); + return ECPublic.fromBip32(bip32.derive(fullPath).publicKey).toP2trAddress(); + } + + factory P2trAddress.fromPath({required Bip32Base bip32, required Bip32Path path}) { + return ECPublic.fromBip32(bip32.derive(path).publicKey).toP2trAddress(); + } + + factory P2trAddress.fromScriptPubkey({required Script script}) { + if (script.getAddressType() != SegwitAddressType.p2tr) { + throw DartBitcoinPluginException("Invalid scriptPubKey"); } - return P2trAddress.fromProgram(program: script.findScriptParam(1), network: network); + return P2trAddress.fromProgram(program: script.findScriptParam(1)); } /// returns the scriptPubKey of a P2TR witness script @@ -124,30 +181,42 @@ class P2trAddress extends SegwitAddress { /// returns the type of address @override - SegwitAddresType get type => SegwitAddresType.p2tr; + SegwitAddressType get type => SegwitAddressType.p2tr; } class P2wshAddress extends SegwitAddress { - static RegExp get regex => RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{40,80}'); + static final 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}) + P2wshAddress.fromProgram({required super.program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV0, - addressType: SegwitAddresType.p2wsh, + addressType: SegwitAddressType.p2wsh, ); - P2wshAddress.fromRedeemScript({required super.script, super.network}) + P2wshAddress.fromRedeemScript({required super.script}) : super.fromRedeemScript(segwitVersion: _BitcoinAddressUtils.segwitV0); - factory P2wshAddress.fromScriptPubkey({required Script script, BasedUtxoNetwork? network}) { - if (script.getAddressType() != SegwitAddresType.p2wsh) { - throw ArgumentError("Invalid scriptPubKey"); + factory P2wshAddress.fromDerivation({ + required Bip32Base bip32, + required BitcoinDerivationInfo derivationInfo, + required bool isChange, + required int index, + }) { + final fullPath = derivationInfo.derivationPath + .addElem(Bip32KeyIndex(BitcoinAddressUtils.getAccountFromChange(isChange))) + .addElem(Bip32KeyIndex(index)); + return ECPublic.fromBip32(bip32.derive(fullPath).publicKey).toP2wshAddress(); + } + + factory P2wshAddress.fromScriptPubkey({required Script script}) { + if (script.getAddressType() != SegwitAddressType.p2wsh) { + throw DartBitcoinPluginException("Invalid scriptPubKey"); } - return P2wshAddress.fromProgram(program: script.findScriptParam(1), network: network); + return P2wshAddress.fromProgram(program: script.findScriptParam(1)); } /// Returns the scriptPubKey of a P2WPKH witness script @@ -158,13 +227,13 @@ class P2wshAddress extends SegwitAddress { /// Returns the type of address @override - SegwitAddresType get type => SegwitAddresType.p2wsh; + SegwitAddressType get type => SegwitAddressType.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}) { + factory MwebAddress.fromAddress({required String address}) { final decoded = Bech32DecoderBase.decodeBech32( address, Bech32Const.separator, @@ -173,14 +242,15 @@ class MwebAddress extends SegwitAddress { final hrp = decoded.item1; final data = decoded.item2; if (hrp != 'ltcmweb') { - throw ArgumentException('Invalid format (HRP not valid, expected ltcmweb, got $hrp)'); + throw DartBitcoinPluginException( + 'Invalid format (HRP not valid, expected ltcmweb, got $hrp)'); } if (data[0] != _BitcoinAddressUtils.segwitV0) { - throw const ArgumentException("Invalid segwit version"); + throw DartBitcoinPluginException("Invalid segwit version"); } final convData = Bech32BaseUtils.convertFromBase32(data.sublist(1)); if (convData.length != 66) { - throw ArgumentException( + throw DartBitcoinPluginException( 'Invalid format (witness program length not valid: ${convData.length})'); } @@ -190,14 +260,14 @@ class MwebAddress extends SegwitAddress { MwebAddress.fromProgram({required super.program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV0, - addressType: SegwitAddresType.mweb, + addressType: SegwitAddressType.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"); + factory MwebAddress.fromScriptPubkey({required Script script, type = SegwitAddressType.mweb}) { + if (script.getAddressType() != SegwitAddressType.mweb) { + throw DartBitcoinPluginException("Invalid scriptPubKey"); } return MwebAddress.fromProgram(program: BytesUtils.toHexString(script.script as List)); } @@ -210,5 +280,5 @@ class MwebAddress extends SegwitAddress { /// returns the type of address @override - SegwitAddresType get type => SegwitAddresType.mweb; + SegwitAddressType get type => SegwitAddressType.mweb; } diff --git a/lib/src/bitcoin/address/util.dart b/lib/src/bitcoin/address/util.dart index 7ec257d..a8db943 100644 --- a/lib/src/bitcoin/address/util.dart +++ b/lib/src/bitcoin/address/util.dart @@ -1,3 +1,5 @@ +import 'package:bitcoin_base/src/bitcoin/script/scripts.dart'; +import 'package:bitcoin_base/src/bitcoin/silent_payments/silent_payments.dart'; import 'package:bitcoin_base/src/utils/utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:bitcoin_base/src/bitcoin/address/address.dart'; @@ -17,15 +19,74 @@ class BitcoinAddressUtils { {required String address, required BasedUtxoNetwork network}) { final addressType = RegexUtils.addressTypeFromStr(address, network); - if (addressType.type == SegwitAddresType.mweb) { + if (addressType.type == SegwitAddressType.mweb) { return BytesUtils.fromHexString( - MwebAddress.fromAddress(address: address, network: network).addressProgram, + MwebAddress.fromAddress(address: address).addressProgram, ); } return addressType.toScriptPubKey().toBytes(); } + static String addressFromOutputScript(Script script, BasedUtxoNetwork network) { + try { + switch (script.getAddressType()) { + case P2pkhAddressType.p2pkh: + return P2pkhAddress.fromScriptPubkey(script: script).toAddress(network); + case P2shAddressType.p2pkInP2sh: + case P2shAddressType.p2pkhInP2sh: + case P2shAddressType.p2wpkhInP2sh: + case P2shAddressType.p2wshInP2sh: + return P2shAddress.fromScriptPubkey(script: script).toAddress(network); + case SegwitAddressType.p2wpkh: + return P2wpkhAddress.fromScriptPubkey(script: script).toAddress(network); + case SegwitAddressType.p2wsh: + return P2wshAddress.fromScriptPubkey(script: script).toAddress(network); + case SegwitAddressType.p2tr: + return P2trAddress.fromScriptPubkey(script: script).toAddress(network); + default: + } + } catch (_) {} + + return ''; + } + + static BitcoinAddressType addressTypeFromStr(String address, BasedUtxoNetwork network) { + try { + return P2pkhAddress.fromAddress(address: address, network: network).type; + } catch (_) {} + + try { + return P2shAddress.fromAddress(address: address, network: network).type; + } catch (_) {} + + try { + return P2wpkhAddress.fromAddress(address: address, network: network).type; + } catch (_) {} + + try { + return P2shAddress.fromAddress(address: address, network: network).type; + } catch (_) {} + + try { + return P2wshAddress.fromAddress(address: address, network: network).type; + } catch (_) {} + + try { + return P2trAddress.fromAddress(address: address, network: network).type; + } catch (_) {} + + try { + return MwebAddress.fromAddress(address: address).type; + } catch (_) {} + + try { + return SilentPaymentAddress.fromAddress(address).type; + } catch (_) {} + + throw Exception('Invalid address'); + } + static String scriptHash(String address, {required BasedUtxoNetwork network}) { final outputScript = addressToOutputScript(address: address, network: network); final parts = BytesUtils.toHexString(QuickCrypto.sha256Hash(outputScript)).split(''); @@ -41,4 +102,77 @@ class BitcoinAddressUtils { return res; } + + static BitcoinAddressType getScriptType(BitcoinBaseAddress type) { + if (type is P2pkhAddress) { + return P2pkhAddressType.p2pkh; + } else if (type is P2shAddress) { + return P2shAddressType.p2wpkhInP2sh; + } else if (type is P2wshAddress) { + return SegwitAddressType.p2wsh; + } else if (type is P2trAddress) { + return SegwitAddressType.p2tr; + } else if (type is MwebAddress) { + return SegwitAddressType.mweb; + } else if (type is SilentPaymentsAddresType) { + return SilentPaymentsAddresType.p2sp; + } else { + return SegwitAddressType.p2wpkh; + } + } + + static int getAccountFromChange(bool isChange) { + return isChange ? 1 : 0; + } + + static BitcoinDerivationInfo getDerivationFromType( + BitcoinAddressType scriptType, { + required BasedUtxoNetwork network, + bool? isElectrum = false, + }) { + switch (scriptType) { + case P2pkhAddressType.p2pkh: + switch (network) { + case BitcoinCashNetwork.mainnet: + return BitcoinDerivationInfos.BCH; + default: + return BitcoinDerivationInfos.BIP44; + } + case P2shAddressType.p2pkInP2sh: + case P2shAddressType.p2pkhInP2sh: + case P2shAddressType.p2wpkhInP2sh: + case P2shAddressType.p2wshInP2sh: + return BitcoinDerivationInfos.BIP49; + case SegwitAddressType.p2wpkh: + switch (network) { + case LitecoinNetwork.mainnet: + return BitcoinDerivationInfos.LITECOIN; + default: + if (isElectrum == true) { + return BitcoinDerivationInfos.ELECTRUM; + } else { + return BitcoinDerivationInfos.BIP84; + } + } + case SegwitAddressType.p2tr: + return BitcoinDerivationInfos.BIP86; + case SegwitAddressType.mweb: + return BitcoinDerivationInfos.BIP86; + case SegwitAddressType.p2wsh: + return BitcoinDerivationInfos.BIP84; + case SilentPaymentsAddresType.p2sp: + return BitcoinDerivationInfos.SILENT_PAYMENTS_SPEND; + default: + throw Exception("Derivation not available for $scriptType"); + } + } + + static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) { + switch (network) { + case LitecoinNetwork.mainnet: + return Bip44Conf.litecoinMainNet.altKeyNetVer; + default: + return null; + } + } } diff --git a/lib/src/bitcoin/address/utils/address_utils.dart b/lib/src/bitcoin/address/utils/address_utils.dart index 9a27306..881634e 100644 --- a/lib/src/bitcoin/address/utils/address_utils.dart +++ b/lib/src/bitcoin/address/utils/address_utils.dart @@ -24,7 +24,7 @@ class _BitcoinAddressUtils { 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 scriptBytes = decode.sublist(1, decode.length - Base58Const.checksumByteLen); /// Ensure the script bytes have the expected length. if (scriptBytes.length != hash160DigestLength) { @@ -32,12 +32,12 @@ 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); + final version = [decode[0]]; + final data = decode.sublist(0, decode.length - Base58Const.checksumByteLen); + final checksum = decode.sublist(decode.length - Base58Const.checksumByteLen); /// Verify the checksum. - List hash = QuickCrypto.sha256DoubleHash(data).sublist(0, Base58Const.checksumByteLen); + final hash = QuickCrypto.sha256DoubleHash(data).sublist(0, Base58Const.checksumByteLen); if (!BytesUtils.bytesEqual(checksum, hash)) { return null; } @@ -82,7 +82,7 @@ class _BitcoinAddressUtils { final convert = SegwitBech32Decoder.decode(network.p2wpkhHrp, address); final witnessVersion = convert.item1; if (witnessVersion != version) { - throw const BitcoinBasePluginException("Invalid segwit version"); + throw const DartBitcoinPluginException('Invalid segwit version'); } return BytesUtils.toHexString(convert.item2); } @@ -95,6 +95,10 @@ 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) { + return toP2wpkhAddress(address, network); + } + static SegwitAddress? toP2wpkhAddress(String address, BasedUtxoNetwork network) { try { final convert = SegwitBech32Decoder.decode(network.p2wpkhHrp, address); @@ -128,8 +132,8 @@ class _BitcoinAddressUtils { if (network.supportedAddress.contains(address.type)) { return address; } - throw BitcoinBasePluginException( - "${network.value} does not support ${address.type.value} address"); + throw DartBitcoinPluginException( + '${network.value} does not support ${address.type.value} address'); } /// Decodes a Bitcoin address and returns a corresponding BitcoinBaseAddress instance @@ -143,17 +147,12 @@ class _BitcoinAddressUtils { /// Throws a [MessageException] if the address is invalid or not supported by the network. static BitcoinBaseAddress decodeAddress(String address, BasedUtxoNetwork network) { BitcoinBaseAddress? baseAddress; - if (network.supportedAddress.contains(SegwitAddresType.p2wpkh)) { - baseAddress = toP2wpkhAddress(address, network); + if (network.supportedAddress.contains(SegwitAddressType.p2wpkh)) { + baseAddress = toSegwitAddress(address, network); } baseAddress ??= toLegacy(address, network); if (baseAddress == null) { - try { - throw const BitcoinBasePluginException("test2 Bitcoin address"); - } catch (e, s) { - print(s); - } - throw const BitcoinBasePluginException("test Bitcoin address"); + throw const DartBitcoinPluginException('Invalid Bitcoin address'); } return validateAddress(baseAddress, network); } @@ -170,12 +169,12 @@ class _BitcoinAddressUtils { try { final toBytes = BytesUtils.fromHexString(hash160); if (toBytes.length == addressType.hashLength) { - return hash160; + return StringUtils.strip0x(hash160.toLowerCase()); } // ignore: empty_catches } catch (e) {} - throw const BitcoinBasePluginException( - "Invalid Bitcoin address program length (program length should be 32 or 20 bytes)"); + throw const DartBitcoinPluginException( + 'Invalid Bitcoin address program length (program length should be 32 or 20 bytes)'); } /// Decodes a Bitcoin Cash (BCH) address and returns a corresponding LegacyAddress instance @@ -190,8 +189,8 @@ class _BitcoinAddressUtils { static LegacyAddress? decodeBchAddress(String address, BitcoinCashNetwork network, {bool validateNetworkHRP = false}) { try { - final String hrp = - validateNetworkHRP ? network.networkHRP : address.substring(0, address.indexOf(":")); + final hrp = + validateNetworkHRP ? network.networkHRP : address.substring(0, address.indexOf(':')); final decode = BchBech32Decoder.decode(hrp, address); final scriptBytes = decode.item2; final version = decode.item1; @@ -260,8 +259,8 @@ class _BitcoinAddressUtils { required BitcoinAddressType type, required BasedUtxoNetwork network}) { if (!network.supportedAddress.contains(type)) { - throw BitcoinBasePluginException( - "${network.value} does not support ${type.value} address type"); + throw DartBitcoinPluginException( + '${network.value} does not support ${type.value} address type'); } if (network is BitcoinCashNetwork) { final decode = _BitcoinAddressUtils.decodeBchAddress(address, network); @@ -276,7 +275,7 @@ class _BitcoinAddressUtils { if (decode == null) return null; final version = decode.item2; final addrBytes = decode.item1; - final String scriptHex = BytesUtils.toHexString(addrBytes); + final scriptHex = BytesUtils.toHexString(addrBytes); switch (type) { case P2pkhAddressType.p2pkh: @@ -334,8 +333,8 @@ class _BitcoinAddressUtils { {required BitcoinCashNetwork network, required String addressProgram, required BitcoinAddressType type}) { - List programBytes = BytesUtils.fromHexString(addressProgram); - final List netVersion = + final programBytes = BytesUtils.fromHexString(addressProgram); + final netVersion = _getBchNetVersion(network: network, type: type, secriptLength: programBytes.length); return BchBech32Encoder.encode(network.networkHRP, netVersion, programBytes); @@ -352,7 +351,7 @@ class _BitcoinAddressUtils { {required BitcoinCashNetwork network, required BitcoinAddressType type, int secriptLength = hash160DigestLength}) { - bool isToken = type.value.contains("WT"); + final isToken = type.value.contains('WT'); if (!type.isP2sh) { if (!isToken) return network.p2pkhNetVer; return network.p2pkhWtNetVer; @@ -387,7 +386,7 @@ class _BitcoinAddressUtils { if (network is BitcoinCashNetwork) { return legacyToBchAddress(addressProgram: addressProgram, network: network, type: type); } - List programBytes = BytesUtils.fromHexString(addressProgram); + var programBytes = BytesUtils.fromHexString(addressProgram); switch (type) { case P2shAddressType.p2wpkhInP2sh: case P2shAddressType.p2wshInP2sh: @@ -412,7 +411,7 @@ class _BitcoinAddressUtils { /// Returns the RIPEMD-160 hash of the public key as a hexadecimal string. static String pubkeyToHash160(String publicKey) { final bytes = BytesUtils.fromHexString(publicKey); - List ripemd160Hash = QuickCrypto.hash160(bytes); + final ripemd160Hash = QuickCrypto.hash160(bytes); return BytesUtils.toHexString(ripemd160Hash); } diff --git a/lib/src/bitcoin/amount/amount.dart b/lib/src/bitcoin/amount/amount.dart new file mode 100644 index 0000000..df7f858 --- /dev/null +++ b/lib/src/bitcoin/amount/amount.dart @@ -0,0 +1,5 @@ +library bitcoin_base.amount; + +import 'package:intl/intl.dart'; + +part 'utils.dart'; diff --git a/lib/src/bitcoin/amount/utils.dart b/lib/src/bitcoin/amount/utils.dart new file mode 100644 index 0000000..8b1f8bc --- /dev/null +++ b/lib/src/bitcoin/amount/utils.dart @@ -0,0 +1,33 @@ +part of 'package:bitcoin_base/src/bitcoin/amount/amount.dart'; + +class BitcoinAmountUtils { + static const bitcoinAmountLength = 8; + static const bitcoinAmountDivider = 100000000; + static final bitcoinAmountFormat = NumberFormat() + ..maximumFractionDigits = bitcoinAmountLength + ..minimumFractionDigits = 1; + + static double cryptoAmountToDouble({required num amount, required num divider}) => + amount / divider; + + static String bitcoinAmountToString({required int amount}) => + bitcoinAmountFormat.format(cryptoAmountToDouble( + amount: amount, + divider: bitcoinAmountDivider, + )); + + static double bitcoinAmountToDouble({required int amount}) => + cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider); + + static int stringDoubleToBitcoinAmount(String amount) { + int result = 0; + + try { + result = (double.parse(amount) * bitcoinAmountDivider).round(); + } catch (e) { + result = 0; + } + + return result; + } +} diff --git a/lib/src/bitcoin/bitcoin.dart b/lib/src/bitcoin/bitcoin.dart new file mode 100644 index 0000000..c3bd859 --- /dev/null +++ b/lib/src/bitcoin/bitcoin.dart @@ -0,0 +1,3 @@ +export 'address/address.dart'; +export 'script/scripts.dart'; +export 'taproot/taproot.dart'; diff --git a/lib/src/bitcoin/script/control_block.dart b/lib/src/bitcoin/script/control_block.dart deleted file mode 100644 index d6a07b2..0000000 --- a/lib/src/bitcoin/script/control_block.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:bitcoin_base/src/bitcoin/script/op_code/constant.dart'; -import 'package:bitcoin_base/src/crypto/keypair/ec_public.dart'; -import 'package:blockchain_utils/utils/binary/utils.dart'; - -import 'script.dart'; - -class ControlBlock { - ControlBlock({required this.public, this.scriptToSpend, List? scripts}) - : scripts = BytesUtils.tryToBytes(scripts, unmodifiable: true); - final ECPublic public; - final Script? scriptToSpend; - final List? scripts; - - List toBytes() { - final List version = [BitcoinOpCodeConst.LEAF_VERSION_TAPSCRIPT]; - - final List pubKey = BytesUtils.fromHexString(public.toXOnlyHex()); - final List marklePath = scripts ?? []; - return [...version, ...pubKey, ...marklePath]; - } - - String toHex() { - return BytesUtils.toHexString(toBytes()); - } -} diff --git a/lib/src/bitcoin/script/input.dart b/lib/src/bitcoin/script/input.dart index 149d545..e0e565e 100644 --- a/lib/src/bitcoin/script/input.dart +++ b/lib/src/bitcoin/script/input.dart @@ -1,5 +1,6 @@ +import 'dart:typed_data'; + import 'package:bitcoin_base/src/bitcoin/script/op_code/constant.dart'; -import 'package:bitcoin_base/src/exception/exception.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'script.dart'; @@ -10,16 +11,21 @@ import 'script.dart'; /// [scriptSig] the script that satisfies the locking conditions /// [sequence] the input sequence (for timelocks, RBF, etc.) class TxInput { - TxInput( - {required this.txId, - required this.txIndex, - Script? scriptSig, - List? sequence}) - : sequence = List.unmodifiable( - sequence ?? BitcoinOpCodeConst.DEFAULT_TX_SEQUENCE), + TxInput({ + required this.txId, + required this.txIndex, + Script? scriptSig, + List? sequence, + }) : sequence = List.unmodifiable( + sequence ?? BitcoinOpCodeConst.defaultTxSequence, + ), scriptSig = scriptSig ?? Script(script: []); - TxInput copyWith( - {String? txId, int? txIndex, Script? scriptSig, List? sequence}) { + TxInput copyWith({ + String? txId, + int? txIndex, + Script? scriptSig, + List? sequence, + }) { return TxInput( txId: txId ?? this.txId, txIndex: txIndex ?? this.txIndex, @@ -33,19 +39,21 @@ class TxInput { List sequence; /// creates a copy of the object + TxInput clone() { + return copy(); + } + TxInput copy() { - return TxInput( - txId: txId, txIndex: txIndex, scriptSig: scriptSig, sequence: sequence); + return TxInput(txId: txId, txIndex: txIndex, scriptSig: scriptSig, sequence: sequence); } /// serializes TxInput to bytes List toBytes() { final txidBytes = BytesUtils.fromHexString(txId).reversed.toList(); - final txoutBytes = List.filled(4, 0); - writeUint32LE(txIndex, txoutBytes); + final txoutBytes = IntUtils.toBytes(txIndex, length: 4, byteOrder: Endian.little); + // writeUint32LE(txIndex, txoutBytes); final scriptSigBytes = scriptSig.toBytes(); - final scriptSigLengthVarint = IntUtils.encodeVarint(scriptSigBytes.length); final data = List.from([ ...txidBytes, @@ -57,37 +65,72 @@ class TxInput { return data; } - static Tuple fromRaw( - {required String raw, int cursor = 0, bool hasSegwit = false}) { - final txInputRaw = BytesUtils.fromHexString(raw); - List inpHash = - txInputRaw.sublist(cursor, cursor + 32).reversed.toList(); - if (inpHash.isEmpty) { - throw const BitcoinBasePluginException( - "Input transaction hash not found. Probably malformed raw transaction"); - } - List outputN = - txInputRaw.sublist(cursor + 32, cursor + 36).reversed.toList(); - cursor += 36; - final vi = IntUtils.decodeVarint(txInputRaw.sublist(cursor, cursor + 9)); + static Tuple deserialize({ + required List bytes, + String? raw, + int cursor = 0, + bool hasSegwit = false, + }) { + return fromRaw(bytes: bytes, cursor: cursor, hasSegwit: hasSegwit); + } + + static Tuple fromRaw({ + List? bytes, + String? raw, + int cursor = 0, + bool hasSegwit = false, + }) { + bytes ??= BytesUtils.fromHexString(raw!); + final inpHash = bytes.sublist(cursor, cursor + 32).reversed.toList(); + cursor += 32; + final outputN = IntUtils.fromBytes(bytes.sublist(cursor, cursor + 4), byteOrder: Endian.little); + cursor += 4; + final vi = IntUtils.decodeVarint(bytes.sublist(cursor)); cursor += vi.item2; - List unlockingScript = txInputRaw.sublist(cursor, cursor + vi.item1); + final unlockingScript = bytes.sublist(cursor, cursor + vi.item1); cursor += vi.item1; - List sequenceNumberData = txInputRaw.sublist(cursor, cursor + 4); + final sequenceNumberData = bytes.sublist(cursor, cursor + 4); cursor += 4; return Tuple( TxInput( txId: BytesUtils.toHexString(inpHash), - txIndex: int.parse(BytesUtils.toHexString(outputN), radix: 16), - scriptSig: Script.fromRaw( - hexData: BytesUtils.toHexString(unlockingScript), - hasSegwit: hasSegwit), + txIndex: outputN, + scriptSig: Script.deserialize(bytes: unlockingScript, hasSegwit: hasSegwit), sequence: sequenceNumberData), cursor); } + List txIdBytes() { + return BytesUtils.fromHexString(txId).reversed.toList(); + } + + int sequenceAsNumber() { + return IntUtils.fromBytes(sequence, byteOrder: Endian.little); + } + + Map toJson() { + return { + 'txid': txId, + 'txIndex': txIndex, + 'scriptSig': scriptSig.toJson(), + 'sequence': BytesUtils.toHexString(sequence), + }; + } + @override String toString() { - return "TxInput{txId: $txId, txIndex: $txIndex, scriptSig: $scriptSig, sequence: ${BytesUtils.toHexString(sequence)}}"; + return 'TxInput{txId: $txId, txIndex: $txIndex, scriptSig: $scriptSig, sequence: ${BytesUtils.toHexString(sequence)}}'; } + + @override + operator ==(other) { + if (identical(this, other)) return true; + if (other is! TxInput) return false; + return txIndex == other.txIndex && + txId == other.txId && + BytesUtils.bytesEqual(sequence, other.sequence); + } + + @override + int get hashCode => HashCodeGenerator.generateHashCode([txIndex, txId, sequence]); } diff --git a/lib/src/bitcoin/script/op_code/constant.dart b/lib/src/bitcoin/script/op_code/constant.dart index bd62271..205b0c2 100644 --- a/lib/src/bitcoin/script/op_code/constant.dart +++ b/lib/src/bitcoin/script/op_code/constant.dart @@ -1,8 +1,133 @@ -/// ignore_for_file: constant_identifier_names, equal_keys_in_map, non_constant_identifier_names, camel_case_types +import 'package:blockchain_utils/helper/extensions/extensions.dart'; + +enum BitcoinOpcode { + op0("OP_0", 0x00), + opFalse("OP_FALSE", 0x00), + opPushData1("OP_PUSHDATA1", 0x4c), + opPushData2("OP_PUSHDATA2", 0x4d), + opPushData4("OP_PUSHDATA4", 0x4e), + op1Negate("OP_1NEGATE", 0x4f), + op1("OP_1", 0x51), + opTrue("OP_TRUE", 0x51), + op2("OP_2", 0x52), + op3("OP_3", 0x53), + op4("OP_4", 0x54), + op5("OP_5", 0x55), + op6("OP_6", 0x56), + op7("OP_7", 0x57), + op8("OP_8", 0x58), + op9("OP_9", 0x59), + op10("OP_10", 0x5a), + op11("OP_11", 0x5b), + op12("OP_12", 0x5c), + op13("OP_13", 0x5d), + op14("OP_14", 0x5e), + op15("OP_15", 0x5f), + op16("OP_16", 0x60), + + // Flow control + opNop("OP_NOP", 0x61), + opIf("OP_IF", 0x63), + opNotIf("OP_NOTIF", 0x64), + opElse("OP_ELSE", 0x67), + opEndIf("OP_ENDIF", 0x68), + opVerify("OP_VERIFY", 0x69), + opReturn("OP_RETURN", 0x6a), + + // Stack operations + opToAltStack("OP_TOALTSTACK", 0x6b), + opFromAltStack("OP_FROMALTSTACK", 0x6c), + opIfDup("OP_IFDUP", 0x73), + opDepth("OP_DEPTH", 0x74), + opDrop("OP_DROP", 0x75), + opDup("OP_DUP", 0x76), + opNip("OP_NIP", 0x77), + opOver("OP_OVER", 0x78), + opPick("OP_PICK", 0x79), + opRoll("OP_ROLL", 0x7a), + opRot("OP_ROT", 0x7b), + opSwap("OP_SWAP", 0x7c), + opTuck("OP_TUCK", 0x7d), + op2Drop("OP_2DROP", 0x6d), + op2Dup("OP_2DUP", 0x6e), + op3Dup("OP_3DUP", 0x6f), + op2Over("OP_2OVER", 0x70), + op2Rot("OP_2ROT", 0x71), + op2Swap("OP_2SWAP", 0x72), + opSize("OP_SIZE", 0x82), + opEqual("OP_EQUAL", 0x87), + opEqualVerify("OP_EQUALVERIFY", 0x88), + + // Arithmetic + op1Add("OP_1ADD", 0x8b), + op1Sub("OP_1SUB", 0x8c), + opNegate("OP_NEGATE", 0x8f), + opAbs("OP_ABS", 0x90), + opNot("OP_NOT", 0x91), + op0NotEqual("OP_0NOTEQUAL", 0x92), + opAdd("OP_ADD", 0x93), + opSub("OP_SUB", 0x94), + opBoolAnd("OP_BOOLAND", 0x9a), + opBoolOr("OP_BOOLOR", 0x9b), + opNumEqual("OP_NUMEQUAL", 0x9c), + opNumEqualVerify("OP_NUMEQUALVERIFY", 0x9d), + opNumNotEqual("OP_NUMNOTEQUAL", 0x9e), + opLessThan("OP_LESSTHAN", 0x9f), + opGreaterThan("OP_GREATERTHAN", 0xa0), + opLessThanOrEqual("OP_LESSTHANOREQUAL", 0xa1), + opGreaterThanOrEqual("OP_GREATERTHANOREQUAL", 0xa2), + opMin("OP_MIN", 0xa3), + opMax("OP_MAX", 0xa4), + opWithin("OP_WITHIN", 0xa5), + + // Crypto + opRipemd160("OP_RIPEMD160", 0xa6), + opSha1("OP_SHA1", 0xa7), + opSha256("OP_SHA256", 0xa8), + opHash160("OP_HASH160", 0xa9), + opHash256("OP_HASH256", 0xaa), + opCodeSeparator("OP_CODESEPARATOR", 0xab), + opCheckSig("OP_CHECKSIG", 0xac), + opCheckSigVerify("OP_CHECKSIGVERIFY", 0xad), + opCheckMultiSig("OP_CHECKMULTISIG", 0xae), + opCheckMultiSigVerify("OP_CHECKMULTISIGVERIFY", 0xaf), + opCheckSigAdd("OP_CHECKSIGADD", 0xba), + opCheckLockTimeVerify("OP_CHECKLOCKTIMEVERIFY", 0xb1), + opCheckSequenceVerify("OP_CHECKSEQUENCEVERIFY", 0xb2); + + final String name; + final int value; + + const BitcoinOpcode(this.name, this.value); + + static BitcoinOpcode? findByName(String name) { + return values.firstWhereNullable((e) => e.name == name); + } + + static BitcoinOpcode? findByValue(int value) { + return values.firstWhereNullable((e) => e.value == value); + } + + bool get isOpPushData => + this == BitcoinOpcode.opPushData1 || + this == BitcoinOpcode.opPushData2 || + this == BitcoinOpcode.opPushData4; +} + +/// ignore_for_file: constant_identifier_names, equal_keys_in_map, non_constant_identifier_names /// Constants and identifiers used in the Bitcoin-related code. -// ignore_for_file: constant_identifier_names, non_constant_identifier_names, equal_keys_in_map, camel_case_types +// ignore_for_file: constant_identifier_names, non_constant_identifier_names, equal_keys_in_map class BitcoinOpCodeConst { + static const int opPushData1 = 0x4c; + static const int opPushData2 = 0x4d; + static const int opPushData4 = 0x4e; + static bool isOpPushData(int byte) { + return byte == BitcoinOpCodeConst.opPushData1 || + byte == BitcoinOpCodeConst.opPushData2 || + byte == BitcoinOpCodeConst.opPushData4; + } + static const OP_0 = "OP_0"; static const OP_FALSE = "OP_FALSE"; static const OP_PUSHDATA1 = "OP_PUSHDATA1"; @@ -121,259 +246,50 @@ class BitcoinOpCodeConst { static const OP_NOP3 = "OP_NOP3"; static const OP_CHECKSEQUENCEVERIFY = "OP_CHECKSEQUENCEVERIFY"; - static const Map> OP_CODES = { - 'OP_0': [0x00], - 'OP_FALSE': [0x00], - 'OP_PUSHDATA1': [0x4c], - 'OP_PUSHDATA2': [0x4d], - 'OP_PUSHDATA4': [0x4e], - 'OP_1NEGATE': [0x4f], - 'OP_1': [0x51], - 'OP_TRUE': [0x51], - 'OP_2': [0x52], - 'OP_3': [0x53], - 'OP_4': [0x54], - 'OP_5': [0x55], - 'OP_6': [0x56], - 'OP_7': [0x57], - 'OP_8': [0x58], - 'OP_9': [0x59], - 'OP_10': [0x5a], - 'OP_11': [0x5b], - 'OP_12': [0x5c], - 'OP_13': [0x5d], - 'OP_14': [0x5e], - 'OP_15': [0x5f], - 'OP_16': [0x60], - - /// flow control - 'OP_NOP': [0x61], - 'OP_IF': [0x63], - 'OP_NOTIF': [0x64], - 'OP_ELSE': [0x67], - 'OP_ENDIF': [0x68], - 'OP_VERIFY': [0x69], - 'OP_RETURN': [0x6a], - - /// stack - 'OP_TOALTSTACK': [0x6b], - 'OP_FROMALTSTACK': [0x6c], - 'OP_IFDUP': [0x73], - 'OP_DEPTH': [0x74], - 'OP_DROP': [0x75], - 'OP_DUP': [0x76], - 'OP_NIP': [0x77], - 'OP_OVER': [0x78], - 'OP_PICK': [0x79], - 'OP_ROLL': [0x7a], - 'OP_ROT': [0x7b], - 'OP_SWAP': [0x7c], - 'OP_TUCK': [0x7d], - 'OP_2DROP': [0x6d], - 'OP_2DUP': [0x6e], - 'OP_3DUP': [0x6f], - 'OP_2OVER': [0x70], - 'OP_2ROT': [0x71], - 'OP_2SWAP': [0x72], - - /// splice - /// 'OP_CAT': [0x7e], - /// 'OP_SUBSTR': [0x7f], - /// 'OP_LEFT': [0x80], - /// 'OP_RIGHT': [0x81], - 'OP_SIZE': [0x82], - - /// bitwise logic - /// 'OP_INVERT': [0x83], - /// 'OP_AND': [0x84], - /// 'OP_OR': [0x85], - /// 'OP_XOR': [0x86], - 'OP_EQUAL': [0x87], - 'OP_EQUALVERIFY': [0x88], - - /// arithmetic - 'OP_1ADD': [0x8b], - 'OP_1SUB': [0x8c], - - /// 'OP_2MUL': [0x8d], - /// 'OP_2DIV': [0x8e], - 'OP_NEGATE': [0x8f], - 'OP_ABS': [0x90], - 'OP_NOT': [0x91], - 'OP_0NOTEQUAL': [0x92], - 'OP_ADD': [0x93], - 'OP_SUB': [0x94], - - /// 'OP_MUL': [0x95], - /// 'OP_DIV': [0x96], - /// 'OP_MOD': [0x97], - /// 'OP_LSHIFT': [0x98], - /// 'OP_RSHIFT': [0x99], - 'OP_BOOLAND': [0x9a], - 'OP_BOOLOR': [0x9b], - 'OP_NUMEQUAL': [0x9c], - 'OP_NUMEQUALVERIFY': [0x9d], - 'OP_NUMNOTEQUAL': [0x9e], - 'OP_LESSTHAN': [0x9f], - 'OP_GREATERTHAN': [0xa0], - 'OP_LESSTHANOREQUAL': [0xa1], - 'OP_GREATERTHANOREQUAL': [0xa2], - 'OP_MIN': [0xa3], - 'OP_MAX': [0xa4], - 'OP_WITHIN': [0xa5], - - /// crypto - 'OP_RIPEMD160': [0xa6], - 'OP_SHA1': [0xa7], - 'OP_SHA256': [0xa8], - 'OP_HASH160': [0xa9], - 'OP_HASH256': [0xaa], - 'OP_CODESEPARATOR': [0xab], - 'OP_CHECKSIG': [0xac], - 'OP_CHECKSIGVERIFY': [0xad], - 'OP_CHECKMULTISIG': [0xae], - 'OP_CHECKMULTISIGVERIFY': [0xaf], + static const int sighashSingle = 0x03; + static const int sighashAnyoneCanPay = 0x80; + static const int sighashAll = 0x01; + static const int sighashForked = 0x40; + static const int sighashTest = 0x00000041; + static const int sighashNone = 0x02; + static const int sighashDefault = 0x00; - /// locktime - 'OP_NOP2': [0xb1], - 'OP_CHECKLOCKTIMEVERIFY': [0xb1], - 'OP_NOP3': [0xb2], - 'OP_CHECKSEQUENCEVERIFY': [0xb2], - }; - - static final Map CODE_OPS = { - /// constants - 0: 'OP_0', - 76: 'OP_PUSHDATA1', - 77: 'OP_PUSHDATA2', - 78: 'OP_PUSHDATA4', - 79: 'OP_1NEGATE', - 81: 'OP_1', - 82: 'OP_2', - 83: 'OP_3', - 84: 'OP_4', - 85: 'OP_5', - 86: 'OP_6', - 87: 'OP_7', - 88: 'OP_8', - 89: 'OP_9', - 90: 'OP_10', - 91: 'OP_11', - 92: 'OP_12', - 93: 'OP_13', - 94: 'OP_14', - 95: 'OP_15', - 96: 'OP_16', - - /// flow control - 97: 'OP_NOP', - 99: 'OP_IF', - 100: 'OP_NOTIF', - 103: 'OP_ELSE', - 104: 'OP_ENDIF', - 105: 'OP_VERIFY', - 106: 'OP_RETURN', - - /// stack - 107: 'OP_TOALTSTACK', - 108: 'OP_FROMALTSTACK', - 115: 'OP_IFDUP', - 116: 'OP_DEPTH', - 117: 'OP_DROP', - 118: 'OP_DUP', - 119: 'OP_NIP', - 120: 'OP_OVER', - 121: 'OP_PICK', - 122: 'OP_ROLL', - 123: 'OP_ROT', - 124: 'OP_SWAP', - 125: 'OP_TUCK', - 109: 'OP_2DROP', - 110: 'OP_2DUP', - 111: 'OP_3DUP', - 112: 'OP_2OVER', - 113: 'OP_2ROT', - 114: 'OP_2SWAP', - - /// splice - 130: 'OP_SIZE', - - /// bitwise logic - 135: 'OP_EQUAL', - 136: 'OP_EQUALVERIFY', - - /// arithmetic - 139: 'OP_1ADD', - 140: 'OP_1SUB', - 143: 'OP_NEGATE', - 144: 'OP_ABS', - 145: 'OP_NOT', - 146: 'OP_0NOTEQUAL', - 147: 'OP_ADD', - 148: 'OP_SUB', - 154: 'OP_BOOLAND', - 155: 'OP_BOOLOR', - 156: 'OP_NUMEQUAL', - 157: 'OP_NUMEQUALVERIFY', - 158: 'OP_NUMNOTEQUAL', - 159: 'OP_LESSTHAN', - 160: 'OP_GREATERTHAN', - 161: 'OP_LESSTHANOREQUAL', - 162: 'OP_GREATERTHANOREQUAL', - 163: 'OP_MIN', - 164: 'OP_MAX', - 165: 'OP_WITHIN', - - /// crypto - 166: 'OP_RIPEMD160', - 167: 'OP_SHA1', - 168: 'OP_SHA256', - 169: 'OP_HASH160', - 170: 'OP_HASH256', - 171: 'OP_CODESEPARATOR', - 172: 'OP_CHECKSIG', - 173: 'OP_CHECKSIGVERIFY', - 174: 'OP_CHECKMULTISIG', - 175: 'OP_CHECKMULTISIGVERIFY', - - /// locktime - 177: 'OP_NOP2', - 178: 'OP_NOP3', - 177: 'OP_CHECKLOCKTIMEVERIFY', - 178: 'OP_CHECKSEQUENCEVERIFY', - }; - - /// SIGHASH types - static const int SIGHASH_SINGLE = 0x03; - static const int SIGHASH_ANYONECANPAY = 0x80; - static const int SIGHASH_ALL = 0x01; - static const int SIGHASH_FORKED = 0x40; - static const int SIGHASH_Test = 0x00000041; - static const int SIGHASH_NONE = 0x02; - static const int TAPROOT_SIGHASH_ALL = 0x00; + static const int sighashAllAnyOneCanPay = 0x81; + static const int sighashNoneAnyOneCanPay = 0x82; + static const int sighashSingleAnyOneCanPay = 0x83; /// Transaction lock types - static const int TYPE_ABSOLUTE_TIMELOCK = 0x101; - static const int TYPE_RELATIVE_TIMELOCK = 0x201; - static const int TYPE_REPLACE_BY_FEE = 0x301; + static const int typeAbsoluteTimelock = 0x101; + static const int typeRelativeTimelock = 0x201; + static const int typeReplaceByFee = 0x301; /// Default values and sequences - static const List DEFAULT_TX_LOCKTIME = [0x00, 0x00, 0x00, 0x00]; - static const List EMPTY_TX_SEQUENCE = [0x00, 0x00, 0x00, 0x00]; - static const List DEFAULT_TX_SEQUENCE = [0xff, 0xff, 0xff, 0xff]; - static const List ABSOLUTE_TIMELOCK_SEQUENCE = [0xfe, 0xff, 0xff, 0xff]; - static const List REPLACE_BY_FEE_SEQUENCE = [0x01, 0x00, 0x00, 0x00]; + static const List defaultTxLocktime = [0x00, 0x00, 0x00, 0x00]; + static const List defaultTxSequence = [0xff, 0xff, 0xff, 0xff]; + static const List emptyTxSequence = [0x00, 0x00, 0x00, 0x00]; - /// Script version and Bitcoin-related identifiers - static const int LEAF_VERSION_TAPSCRIPT = 0xc0; - static const List DEFAULT_TX_VERSION = [0x02, 0x00, 0x00, 0x00]; - static const int SATOSHIS_PER_BITCOIN = 100000000; - static const int NEGATIVE_SATOSHI = -1; + static const List absoluteTimelockSequence = [0xfe, 0xff, 0xff, 0xff]; + static const List replaceByFeeSequence = [0x01, 0x00, 0x00, 0x00]; - /// Bitcoin address types - static const String P2PKH_ADDRESS = "p2pkh"; - static const String P2SH_ADDRESS = "p2sh"; - static const String P2WPKH_ADDRESS_V0 = "p2wpkhv0"; - static const String P2WSH_ADDRESS_V0 = "p2wshv0"; - static const String P2TR_ADDRESS_V1 = "p2trv1"; + /// Script version and Bitcoin-related identifiers + static const int leafVersionTapscript = 0xc0; + static const List defaultTxVersion = [0x02, 0x00, 0x00, 0x00]; + static const int satoshisPerBitcoin = 100000000; + static BigInt negativeSatoshi = BigInt.from(-1); + + static const int sequenceLengthInBytes = 4; + static const int locktimeLengthInBytes = 4; + static const int versionLengthInBytes = 4; + static const int outputIndexBytesLength = 4; + static const int sighashByteLength = 4; + static const String opReturn = "OP_RETURN"; + static const String opTrue = "OP_TRUE"; + static const String opCheckSig = "OP_CHECKSIG"; + static const String opCheckMultiSig = "OP_CHECKMULTISIG"; + static const String opCheckMultiSigVerify = "OP_CHECKMULTISIGVERIFY"; + static const String opCheckSigAdd = "OP_CHECKSIGADD"; + + static const int minInputLocktime = 500000000; + static const int defaultTxVersionNumber = 2; + static const int sighashBytesLength = 1; } diff --git a/lib/src/bitcoin/script/op_code/constant_lib.dart b/lib/src/bitcoin/script/op_code/constant_lib.dart deleted file mode 100644 index 4a7c224..0000000 --- a/lib/src/bitcoin/script/op_code/constant_lib.dart +++ /dev/null @@ -1,4 +0,0 @@ -library bitcoin_constants; - -export 'constant.dart'; -export 'tools.dart'; diff --git a/lib/src/bitcoin/script/op_code/tools.dart b/lib/src/bitcoin/script/op_code/tools.dart deleted file mode 100644 index 9bb44e5..0000000 --- a/lib/src/bitcoin/script/op_code/tools.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'package:bitcoin_base/src/exception/exception.dart'; -import 'package:blockchain_utils/utils/utils.dart'; - -List opPushData(String hexData) { - final List dataBytes = BytesUtils.fromHexString(hexData); - if (dataBytes.length < 0x4c) { - return List.from([dataBytes.length]) + dataBytes; - } else if (dataBytes.length < mask8) { - return List.from([0x4c]) + - List.from([dataBytes.length]) + - dataBytes; - } else if (dataBytes.length < mask16) { - var lengthBytes = List.filled(2, 0); - - writeUint16LE(dataBytes.length, lengthBytes); - return List.from([0x4d, ...lengthBytes, ...dataBytes]); - } else if (dataBytes.length < mask32) { - var lengthBytes = List.filled(4, 0); - writeUint32LE(lengthBytes.length, lengthBytes); - return List.from([0x4e, ...lengthBytes, ...dataBytes]); - } else { - throw const BitcoinBasePluginException( - "Data too large. Cannot push into script"); - } -} - -List pushInteger(int integer) { - if (integer < 0) { - throw const BitcoinBasePluginException( - 'Integer is currently required to be positive.'); - } - - /// Calculate the number of bytes required to represent the integer - int numberOfBytes = (integer.bitLength + 7) ~/ 8; - - /// Convert to little-endian bytes - List integerBytes = List.filled(numberOfBytes, 0); - for (int i = 0; i < numberOfBytes; i++) { - integerBytes[i] = (integer >> (i * 8)) & mask8; - } - - /// If the last bit is set, add a sign byte to signify a positive integer - if ((integer & (1 << (numberOfBytes * 8 - 1))) != 0) { - integerBytes = List.from([...integerBytes, 0x00]); - } - - return List.from(opPushData(BytesUtils.toHexString(integerBytes))); -} diff --git a/lib/src/bitcoin/script/output.dart b/lib/src/bitcoin/script/output.dart index 09b11e2..572475e 100644 --- a/lib/src/bitcoin/script/output.dart +++ b/lib/src/bitcoin/script/output.dart @@ -1,27 +1,69 @@ import 'dart:typed_data'; import 'package:bitcoin_base/src/cash_token/cash_token.dart'; import 'package:bitcoin_base/src/bitcoin/script/script.dart'; +import 'package:bitcoin_base/src/exception/exception.dart'; +import 'package:blockchain_utils/helper/extensions/extensions.dart'; import 'package:blockchain_utils/utils/utils.dart'; +import 'package:bitcoin_base/src/bitcoin/script/op_code/constant.dart'; /// Represents a transaction output. /// /// [amount] the value we want to send to this output in satoshis /// [scriptPubKey] the script that will lock this amount class TxOutput { - const TxOutput({ + factory TxOutput.negativeOne() { + return TxOutput._( + amount: BitcoinOpCodeConst.negativeSatoshi, + scriptPubKey: Script(script: []), + ); + } + // BitcoinOpCodeConst.negativeSatoshi + const TxOutput._({ required this.amount, required this.scriptPubKey, this.cashToken, this.isSilentPayment = false, this.isChange = false, }); + factory TxOutput({ + required BigInt amount, + required Script scriptPubKey, + CashToken? cashToken, + bool isSilentPayment = false, + bool isChange = false, + }) { + try { + return TxOutput._( + amount: amount.asUint64, + scriptPubKey: scriptPubKey, + cashToken: cashToken, + isSilentPayment: isSilentPayment, + isChange: isChange, + ); + } catch (_) { + throw DartBitcoinPluginException( + "Invalid output amount: must be a non-negative 64-bit integer."); + } + } final CashToken? cashToken; final BigInt amount; final Script scriptPubKey; final bool isSilentPayment; final bool isChange; + Map toJson() { + return { + 'cashToken': cashToken?.toJson(), + 'amount': amount.toString(), + 'scriptPubKey': scriptPubKey.script + }; + } + /// creates a copy of the object + TxOutput clone() { + return copy(); + } + TxOutput copy() { return TxOutput( amount: amount, @@ -33,38 +75,73 @@ class TxOutput { } 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, + ); + final 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); + static Tuple deserialize({ + required int cursor, + List? bytes, + String? raw, + bool hasSegwit = false, + }) { + return fromRaw(bytes: bytes, cursor: cursor, hasSegwit: hasSegwit); + } + + static Tuple fromRaw({ + required int cursor, + List? bytes, + String? raw, + bool hasSegwit = false, + }) { + bytes ??= BytesUtils.fromHexString(raw!); + final value = BigintUtils.fromBytes( + bytes.sublist(cursor, cursor + 8), + byteOrder: Endian.little, + ).toSigned(64); cursor += 8; - final vi = IntUtils.decodeVarint(txBytes.sublist(cursor, cursor + 9)); + final vi = IntUtils.decodeVarint(bytes.sublist(cursor)); cursor += vi.item2; - final token = CashToken.fromRaw(txBytes.sublist(cursor)); - List lockScript = txBytes.sublist(cursor + token.item2, cursor + vi.item1); + final token = CashToken.deserialize(bytes.sublist(cursor)); + + final lockScript = bytes.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, - )), + scriptPubKey: Script.deserialize(bytes: lockScript)), cursor); } @override String toString() { - return "TxOutput{cashToken: ${cashToken?.toString()}}, amount: $amount, script: ${scriptPubKey.toString()}}"; + return 'TxOutput{cashToken: ${cashToken?.toString()}, amount: $amount, script: ${scriptPubKey.toString()}}'; + } + + @override + operator ==(other) { + if (identical(this, other)) return true; + if (other is! TxOutput) return false; + return amount == other.amount && + scriptPubKey == other.scriptPubKey && + cashToken == other.cashToken; } + + @override + int get hashCode => HashCodeGenerator.generateHashCode([amount, scriptPubKey, cashToken]); } diff --git a/lib/src/bitcoin/script/script.dart b/lib/src/bitcoin/script/script.dart index 47dc269..75aadaf 100644 --- a/lib/src/bitcoin/script/script.dart +++ b/lib/src/bitcoin/script/script.dart @@ -1,5 +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/exception/exception.dart'; import 'package:bitcoin_base/src/models/network.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; @@ -7,54 +8,69 @@ import 'package:blockchain_utils/blockchain_utils.dart'; /// /// [script] the list with all the script OP_CODES and data class Script { - Script({required List script}) - : assert(() { - for (final i in script) { - if (i is! String && i is! int) return false; - } - return true; - }(), - "A valid script is a composition of opcodes, hexadecimal strings, and integers arranged in a structured list."), - script = List.unmodifiable(script); + static final Script empty = Script(); + Script._({required List script}) : script = script.immutable; + final List script; - static Script fromRaw({List? byteData, String? hexData, bool hasSegwit = false}) { - List commands = []; + factory Script.deserialize({ + List? bytes, + String? hexData, + bool hasSegwit = false, + }) { + return Script.fromRaw(bytes: bytes, hexData: hexData, hasSegwit: hasSegwit); + } + + factory Script.fromRaw({ + List? bytes, + String? hexData, + bool hasSegwit = false, + }) { + final List commands = []; int index = 0; - final scriptBytes = byteData ?? (hexData != null ? BytesUtils.fromHexString(hexData) : null); - if (scriptBytes == null) { - throw ArgumentError("Invalid script"); + bytes ??= BytesUtils.fromHexString(hexData!); + if (bytes == null) { + throw DartBitcoinPluginException("Invalid script"); } - while (index < scriptBytes.length) { - int byte = scriptBytes[index]; - if (BitcoinOpCodeConst.CODE_OPS.containsKey(byte)) { - commands.add(BitcoinOpCodeConst.CODE_OPS[byte]!); - index = index + 1; - } else if (!hasSegwit && byte == 0x4c) { - int bytesToRead = scriptBytes[index + 1]; + while (index < bytes.length) { + final byte = bytes[index]; + final opcode = BitcoinOpcode.findByValue(byte); + if (opcode != null) { + if (!opcode.isOpPushData) { + commands.add(opcode.name); + } + + /// skip op index = index + 1; - 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))); - 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))); - index = index + bytesToRead; + if (opcode == BitcoinOpcode.opPushData1) { + // get len + final bytesToRead = bytes[index]; + // skip len + index = index + 1; + commands.add(BytesUtils.toHexString(bytes.sublist(index, index + bytesToRead))); + + /// add length + index = index + bytesToRead; + } else if (opcode == BitcoinOpcode.opPushData2) { + /// get len + final bytesToRead = readUint16LE(bytes, index); + index = index + 2; + commands.add(BytesUtils.toHexString(bytes.sublist(index, index + bytesToRead))); + index = index + bytesToRead; + } else if (opcode == BitcoinOpcode.opPushData4) { + final bytesToRead = readUint32LE(bytes, index); + + index = index + 4; + commands.add(BytesUtils.toHexString(bytes.sublist(index, index + bytesToRead))); + index = index + bytesToRead; + } } else { - 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))); + final viAndSize = IntUtils.decodeVarint(bytes.sublist(index)); + final dataSize = viAndSize.item1; + final size = viAndSize.item2; + final lastIndex = + (index + size + dataSize) > bytes.length ? bytes.length : (index + size + dataSize); + commands.add(BytesUtils.toHexString(bytes.sublist(index + size, lastIndex))); index = index + dataSize + size; } } @@ -75,7 +91,7 @@ class Script { script.length == 66 && (script[0] == 2 || script[0] == 3) && (script[33] == 2 || script[33] == 3)) { - return SegwitAddresType.mweb; + return SegwitAddressType.mweb; } final first = findScriptParam(0); @@ -85,18 +101,18 @@ class Script { } if (first == "OP_0") { - final lockingScriptBytes = opPushData(sec); + final lockingScriptBytes = BitcoinScriptUtils.opPushData(null, sec); if (lockingScriptBytes.length == 21) { - return SegwitAddresType.p2wpkh; + return SegwitAddressType.p2wpkh; } else if (lockingScriptBytes.length == 33) { - return SegwitAddresType.p2wsh; + return SegwitAddressType.p2wsh; } } else if (first == "OP_1") { - final lockingScriptBytes = opPushData(sec); + final lockingScriptBytes = BitcoinScriptUtils.opPushData(null, sec); if (lockingScriptBytes.length == 33) { - return SegwitAddresType.p2tr; + return SegwitAddressType.p2tr; } } @@ -105,12 +121,14 @@ class Script { final fifth = findScriptParam(4); if (first == "OP_DUP") { if (sec == "OP_HASH160" && - opPushData(third).length == 21 && + BitcoinScriptUtils.opPushData(null, third).length == 21 && fourth == "OP_EQUALVERIFY" && fifth == "OP_CHECKSIG") { return P2pkhAddressType.p2pkh; } - } else if (first == "OP_HASH160" && opPushData(sec).length == 21 && third == "OP_EQUAL") { + } else if (first == "OP_HASH160" && + BitcoinScriptUtils.opPushData(null, sec).length == 21 && + third == "OP_EQUAL") { return P2shAddressType.p2pkhInP2sh; } else if (sec == "OP_CHECKSIG") { if (first.length == 66) { @@ -124,7 +142,7 @@ class Script { String toAddress() { final addressType = getAddressType(); if (addressType == null) { - throw ArgumentError("Invalid script"); + throw DartBitcoinPluginException("Invalid script"); } switch (addressType) { @@ -132,45 +150,107 @@ class Script { return P2pkhAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); case P2shAddressType.p2pkhInP2sh: return P2shAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); - case SegwitAddresType.p2wpkh: + case SegwitAddressType.p2wpkh: return P2wpkhAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); - case SegwitAddresType.p2wsh: + case SegwitAddressType.p2wsh: return P2wshAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); - case SegwitAddresType.p2tr: + case SegwitAddressType.p2tr: return P2trAddress.fromScriptPubkey(script: this).toAddress(BitcoinNetwork.mainnet); } - throw ArgumentError("Invalid script"); + throw DartBitcoinPluginException("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)) { - scriptBytes.add(BitcoinOpCodeConst.OP_CODES[token]!); + final bytes = DynamicByteTracker(); + for (final token in script) { + final opcode = BitcoinOpcode.findByName(token.toString()); + if (opcode != null) { + bytes.add([opcode.value]); + } else { + if (token is int) { + bytes.add(BitcoinScriptUtils.pushInteger(token)); + } else { + final tokenBytes = BytesUtils.tryFromHexString(token); + if (tokenBytes == null) { + throw DartBitcoinPluginException( + "A valid script is a composition of opcodes, hexadecimal strings, and integers arranged in a structured list."); + } + bytes.add(BitcoinScriptUtils.opPushData(tokenBytes)); + } + } + } + + return bytes.toBytes(); + } + + factory Script.fromJson(Map json) { + return Script(script: json["script"]); + } + + factory Script({List script = const []}) { + for (final i in script) { + if (i is! String && i is! int && i is! BitcoinOpcode) { + throw DartBitcoinPluginException( + "A valid script is a composition of opcodes, hexadecimal strings, and integers arranged in a structured list."); + } + } + List scripts = []; + for (final token in script) { + if (token is BitcoinOpcode) { + if (token.isOpPushData) continue; + scripts.add(token.name); + continue; + } + final opcode = BitcoinOpcode.findByName(token.toString()); + if (opcode != null) { + scripts.add(opcode.name); } else if (token is int && token >= 0 && token <= 16) { - scriptBytes.add(BitcoinOpCodeConst.OP_CODES['OP_$token']!); + scripts.add('OP_$token'); } else { if (token is int) { - scriptBytes.add(pushInteger(token)); + final opcode = BitcoinOpcode.findByValue(token); + if (opcode?.isOpPushData ?? false) continue; + scripts.add(token); } else { - scriptBytes.add(opPushData(token)); + final tokenBytes = BytesUtils.tryFromHexString(token); + if (tokenBytes == null) { + throw DartBitcoinPluginException( + "A valid script is a composition of opcodes, hexadecimal strings, and integers arranged in a structured list."); + } + scripts.add(StringUtils.strip0x((token as String).toLowerCase())); } } } - return scriptBytes.toBytes(); + return Script._(script: scripts); } String toHex() { return BytesUtils.toHexString(toBytes()); } + Map toJson() { + return {"script": script}; + } + @override String toString() { - return "Script{script: ${script.join(", ")}}"; + return script.toString(); + } + + @override + operator ==(other) { + if (identical(this, other)) return true; + if (other is Script) { + return BytesUtils.bytesEqual(toBytes(), other.toBytes()); + } + return false; } + + @override + int get hashCode => HashCodeGenerator.generateBytesHashCode(toBytes()); } diff --git a/lib/src/bitcoin/script/scripts.dart b/lib/src/bitcoin/script/scripts.dart index e74ba6c..3777a0d 100644 --- a/lib/src/bitcoin/script/scripts.dart +++ b/lib/src/bitcoin/script/scripts.dart @@ -1,9 +1,9 @@ -export 'control_block.dart'; +export 'utils.dart'; export 'input.dart'; export 'output.dart'; export 'script.dart'; export 'sequence.dart'; export 'transaction.dart'; export 'witness.dart'; -export 'op_code/constant_lib.dart'; +export 'op_code/constant.dart'; export 'outpoint.dart'; diff --git a/lib/src/bitcoin/script/sequence.dart b/lib/src/bitcoin/script/sequence.dart index f202eff..9090d64 100644 --- a/lib/src/bitcoin/script/sequence.dart +++ b/lib/src/bitcoin/script/sequence.dart @@ -6,14 +6,14 @@ import 'package:blockchain_utils/utils/utils.dart'; /// Helps setting up appropriate sequence. Used to provide the sequence to transaction inputs and to scripts. /// /// [value] The value of the block height or the 512 seconds increments -/// [seqType] Specifies the type of sequence (TYPE_RELATIVE_TIMELOCK | TYPE_ABSOLUTE_TIMELOCK | TYPE_REPLACE_BY_FEE -/// [isTypeBlock] If type is TYPE_RELATIVE_TIMELOCK then this specifies its type (block height or 512 secs increments) +/// [seqType] Specifies the type of sequence (.typeRelativeTimelock | .typeAbsoluteTimelock | .typeReplaceByFee +/// [isTypeBlock] If type is .typeRelativeTimelock then this specifies its type (block height or 512 secs increments) class Sequence { Sequence( {required this.seqType, required this.value, this.isTypeBlock = true}) { - if (seqType == BitcoinOpCodeConst.TYPE_RELATIVE_TIMELOCK && + if (seqType == BitcoinOpCodeConst.typeRelativeTimelock && (value < 1 || value > mask16)) { - throw const BitcoinBasePluginException( + throw const DartBitcoinPluginException( 'Sequence should be between 1 and 65535'); } } @@ -23,14 +23,14 @@ class Sequence { /// Serializes the relative sequence as required in a transaction List forInputSequence() { - if (seqType == BitcoinOpCodeConst.TYPE_ABSOLUTE_TIMELOCK) { - return List.from(BitcoinOpCodeConst.ABSOLUTE_TIMELOCK_SEQUENCE); + if (seqType == BitcoinOpCodeConst.typeAbsoluteTimelock) { + return List.from(BitcoinOpCodeConst.absoluteTimelockSequence); } - if (seqType == BitcoinOpCodeConst.TYPE_REPLACE_BY_FEE) { - return List.from(BitcoinOpCodeConst.REPLACE_BY_FEE_SEQUENCE); + if (seqType == BitcoinOpCodeConst.typeReplaceByFee) { + return List.from(BitcoinOpCodeConst.replaceByFeeSequence); } - if (seqType == BitcoinOpCodeConst.TYPE_RELATIVE_TIMELOCK) { + if (seqType == BitcoinOpCodeConst.typeRelativeTimelock) { int seq = 0; if (!isTypeBlock) { seq |= 1 << 22; @@ -39,17 +39,17 @@ class Sequence { return IntUtils.toBytes(seq, length: 4, byteOrder: Endian.little); } - throw const BitcoinBasePluginException("Invalid seqType"); + throw const DartBitcoinPluginException('Invalid seqType'); } /// Returns the appropriate integer for a script; e.g. for relative timelocks int forScript() { - if (seqType == BitcoinOpCodeConst.TYPE_REPLACE_BY_FEE) { - throw const BitcoinBasePluginException( - "RBF is not to be included in a script."); + if (seqType == BitcoinOpCodeConst.typeReplaceByFee) { + throw const DartBitcoinPluginException( + 'RBF is not to be included in a script.'); } - int scriptIntiger = value; - if (seqType == BitcoinOpCodeConst.TYPE_RELATIVE_TIMELOCK && !isTypeBlock) { + var scriptIntiger = value; + if (seqType == BitcoinOpCodeConst.typeRelativeTimelock && !isTypeBlock) { scriptIntiger |= 1 << 22; } return scriptIntiger; @@ -57,6 +57,6 @@ class Sequence { @override String toString() { - return "Sequence{seqType: $seqType, value: $value, isTypeBlock: $isTypeBlock}"; + return 'Sequence{seqType: $seqType, value: $value, isTypeBlock: $isTypeBlock}'; } } diff --git a/lib/src/bitcoin/script/transaction.dart b/lib/src/bitcoin/script/transaction.dart index 4e53e71..5fa0dfa 100644 --- a/lib/src/bitcoin/script/transaction.dart +++ b/lib/src/bitcoin/script/transaction.dart @@ -1,8 +1,9 @@ import 'dart:typed_data'; +import 'package:bitcoin_base/src/bitcoin/taproot/taproot.dart'; import 'package:bitcoin_base/src/cash_token/cash_token.dart'; import 'package:bitcoin_base/src/bitcoin/script/op_code/constant.dart'; -import 'package:bitcoin_base/src/crypto/crypto.dart'; import 'package:bitcoin_base/src/exception/exception.dart'; +import 'package:blockchain_utils/helper/helper.dart'; import 'package:blockchain_utils/utils/utils.dart'; import 'package:blockchain_utils/crypto/quick_crypto.dart'; import 'package:collection/collection.dart'; @@ -20,21 +21,52 @@ 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, + BtcTransaction._( + {required List inputs, + required List outputs, + List witnesses = const [], + this.hasSegwit = false, + this.canReplaceByFee = false, + this.mwebBytes, + required List locktime, + required List version, + this.hasSilentPayment = false}) + : locktime = locktime.asImmutableBytes, + version = version.asImmutableBytes, + inputs = inputs.immutable, + outputs = outputs.immutable, + witnesses = witnesses.immutable; + + factory BtcTransaction({ + List inputs = const [], + List outputs = const [], 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); + List locktime = BitcoinOpCodeConst.defaultTxLocktime, + List version = BitcoinOpCodeConst.defaultTxVersion, + bool hasSegwit = false, + bool canReplaceByFee = false, + List? mwebBytes, + bool hasSilentPayment = false, + }) { + if (locktime.length != BitcoinOpCodeConst.locktimeLengthInBytes) { + throw DartBitcoinPluginException( + "Invalid locktime length: expected ${BitcoinOpCodeConst.locktimeLengthInBytes}, but got ${locktime.length}."); + } + if (version.length != BitcoinOpCodeConst.versionLengthInBytes) { + throw DartBitcoinPluginException( + "Invalid version length: expected ${BitcoinOpCodeConst.versionLengthInBytes}, but got ${version.length}."); + } + return BtcTransaction._( + inputs: inputs, + outputs: outputs, + witnesses: witnesses, + version: version, + locktime: locktime, + hasSegwit: hasSegwit, + canReplaceByFee: canReplaceByFee, + mwebBytes: mwebBytes, + hasSilentPayment: hasSilentPayment); + } final List inputs; final List outputs; final List locktime; @@ -50,22 +82,27 @@ class BtcTransaction { List? outputs, List? witnesses, bool? hasSegwit, - List? lock, + List? locktime, List? version, + List? mwebBytes, + bool? hasSilentPayment, }) { return BtcTransaction( - inputs: inputs ?? this.inputs, - 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, - ); + inputs: inputs ?? this.inputs, + outputs: outputs ?? this.outputs, + witnesses: witnesses ?? this.witnesses, + hasSegwit: hasSegwit ?? this.hasSegwit, + mwebBytes: mwebBytes ?? this.mwebBytes, + locktime: locktime ?? this.locktime, + version: version ?? this.version, + hasSilentPayment: hasSilentPayment ?? this.hasSilentPayment); } /// creates a copy of the object (classmethod) + static BtcTransaction clone(BtcTransaction tx) { + return copy(tx); + } + static BtcTransaction copy(BtcTransaction tx) { return BtcTransaction( hasSegwit: tx.hasSegwit, @@ -73,92 +110,104 @@ class BtcTransaction { outputs: tx.outputs.map((e) => e.copy()).toList(), witnesses: tx.witnesses.map((e) => e.copy()).toList(), mwebBytes: tx.mwebBytes, - lock: tx.locktime, + locktime: tx.locktime, version: tx.version); } /// Instantiates a Transaction from serialized raw hexadacimal data (classmethod) - static BtcTransaction fromRaw(String raw) { + static BtcTransaction fromRaw(String raw, {bool allowWitness = true}) { final rawtx = BytesUtils.fromHexString(raw); - final List version = rawtx.sublist(0, 4); - 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 > 0) { - hasSegwit = true; - } - if (flag[0] & 8 > 0) { - hasMweb = true; + return deserialize(rawtx, allowWitness: allowWitness); + } + + static BtcTransaction deserialize(List txBytes, {bool allowWitness = true}) { + try { + final version = txBytes.sublist(0, 4); + int cursor = 4; + List? flag; + bool hasWitness = false; + bool hasMweb = false; + if (txBytes[4] == 0) { + flag = List.from(txBytes.sublist(5, 6)); + if (flag[0] & 1 > 0) { + hasWitness = true; + } + if (flag[0] & 8 > 0) { + hasMweb = true; + } + cursor += 2; } - cursor += 2; - } - final vi = IntUtils.decodeVarint(rawtx.sublist(cursor, cursor + 9)); - cursor += vi.item2; + final vi = IntUtils.decodeVarint(txBytes.sublist(cursor)); + 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); + bool canReplaceByFee = false; + final inputs = []; + for (int index = 0; index < vi.item1; index++) { + final inp = TxInput.deserialize(bytes: txBytes, hasSegwit: hasWitness, cursor: cursor); - final input = inp.item1; - inputs.add(input); - cursor = inp.item2; + final input = inp.item1; + inputs.add(input); + cursor = inp.item2; - if (canReplaceByFee == false) { - canReplaceByFee = - const ListEquality().equals(input.sequence, BitcoinOpCodeConst.REPLACE_BY_FEE_SEQUENCE); + if (canReplaceByFee == false) { + canReplaceByFee = + const ListEquality().equals(input.sequence, BitcoinOpCodeConst.replaceByFeeSequence); + } } - } - - 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); - 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 = []; - for (int n = 0; n < wVi.item1; n++) { - 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); + final outputs = []; + final viOut = IntUtils.decodeVarint(txBytes.sublist(cursor)); + cursor += viOut.item2; + for (int index = 0; index < viOut.item1; index++) { + final inp = TxOutput.deserialize(bytes: txBytes, cursor: cursor); + outputs.add(inp.item1); + cursor = inp.item2; + } + final witnesses = []; + if (hasWitness) { + if (cursor + 4 < txBytes.length) { + for (int n = 0; n < inputs.length; n++) { + final wVi = IntUtils.decodeVarint(txBytes.sublist(cursor)); + cursor += wVi.item2; + final witnessesTmp = []; + for (int n = 0; n < wVi.item1; n++) { + List witness = []; + final wtVi = IntUtils.decodeVarint(txBytes.sublist(cursor)); + if (wtVi.item1 != 0) { + witness = txBytes.sublist(cursor + wtVi.item2, cursor + wtVi.item1 + wtVi.item2); + } + cursor += wtVi.item1 + wtVi.item2; + witnessesTmp.add(BytesUtils.toHexString(witness)); + } + + witnesses.add(TxWitnessInput(stack: witnessesTmp)); } - cursor += wtVi.item1 + wtVi.item2; - witnessesTmp.add(BytesUtils.toHexString(witness)); } - - witnesses.add(TxWitnessInput(stack: witnessesTmp)); } + List? mwebBytes; + if (hasMweb) { + mwebBytes = txBytes.sublist(cursor, txBytes.length - 4); + } + List locktime = BitcoinOpCodeConst.defaultTxLocktime; + if ((txBytes.length - cursor) >= 4) { + locktime = txBytes.sublist(cursor, cursor + 4); + cursor += 4; + } + assert(txBytes.length == cursor, "Transaction deserialization failed. Unexpected bytes."); + + return BtcTransaction( + inputs: inputs, + outputs: outputs, + witnesses: witnesses, + hasSegwit: hasWitness, + canReplaceByFee: canReplaceByFee, + mwebBytes: mwebBytes, + version: version, + locktime: locktime, + ); + } catch (e) { + throw DartBitcoinPluginException("Transaction deserialization failed.", + details: {"error": e.toString()}); } - 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, - canReplaceByFee: canReplaceByFee, - mwebBytes: mwebBytes, - version: version, - lock: lock, - ); } /// returns the transaction input's digest that is to be signed according. @@ -169,58 +218,59 @@ class BtcTransaction { List getTransactionDigest( {required int txInIndex, required Script script, - int sighash = BitcoinOpCodeConst.SIGHASH_ALL}) { - BtcTransaction tx = copy(this); + int sighash = BitcoinOpCodeConst.sighashAll}) { + BtcTransaction tx = clone(this); for (final i in tx.inputs) { i.scriptSig = Script(script: []); } tx.inputs[txInIndex].scriptSig = script; - if ((sighash & 0x1f) == BitcoinOpCodeConst.SIGHASH_NONE) { - // tx.outputs.clear(); + if ((sighash & 0x1f) == BitcoinOpCodeConst.sighashNone) { 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 = BitcoinOpCodeConst.emptyTxSequence; } } - } else if ((sighash & 0x1f) == BitcoinOpCodeConst.SIGHASH_SINGLE) { + } else if ((sighash & 0x1f) == BitcoinOpCodeConst.sighashSingle) { if (txInIndex >= tx.outputs.length) { - throw const BitcoinBasePluginException( - "Transaction index is greater than the available outputs"); + throw DartBitcoinPluginException( + "SIGHASH_SINGLE error: Input index $txInIndex is greater than or equal to the number of outputs (${tx.outputs.length}). This input cannot be signed."); } - List outputs = []; + final List outputs = []; for (int i = 0; i < txInIndex; i++) { - outputs.add(TxOutput( - amount: BigInt.from(BitcoinOpCodeConst.NEGATIVE_SATOSHI), - scriptPubKey: Script(script: []))); + outputs.add(TxOutput.negativeOne()); } 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 = BitcoinOpCodeConst.emptyTxSequence; } } } - if ((sighash & BitcoinOpCodeConst.SIGHASH_ANYONECANPAY) != 0) { + if ((sighash & BitcoinOpCodeConst.sighashAnyoneCanPay) != 0) { tx = tx.copyWith(inputs: [tx.inputs[txInIndex]]); } - List txForSign = tx.toBytes(segwit: false); + List txForSign = tx.toBytes(allowWitness: false); - txForSign = List.from( - [...txForSign, ...IntUtils.toBytes(sighash, length: 4, byteOrder: Endian.little)]); + txForSign = [ + ...txForSign, + ...IntUtils.toBytes(sighash, length: 4, byteOrder: Endian.little), + ]; return QuickCrypto.sha256DoubleHash(txForSign); } /// Serializes Transaction to bytes - List toBytes({bool segwit = false}) { - DynamicByteTracker data = DynamicByteTracker(); + List toBytes({bool allowWitness = true}) { + final data = DynamicByteTracker(); data.add(version); - var flag = 0; - if (segwit) flag |= 1; - if (mwebBytes != null) flag |= 8; - if (flag > 0) { - data.add([0x00, flag]); + if (allowWitness && witnesses.isNotEmpty) { + var flag = 0; + if (allowWitness) flag |= 1; + if (mwebBytes != null && mwebBytes!.isNotEmpty) flag |= 8; + if (flag > 0) { + data.add([0x00, flag]); + } } final txInCountBytes = IntUtils.encodeVarint(inputs.length); final txOutCountBytes = IntUtils.encodeVarint(outputs.length); @@ -229,18 +279,17 @@ class BtcTransaction { for (final txIn in inputs) { data.add(txIn.toBytes()); } + data.add(txOutCountBytes); for (final txOut in outputs) { data.add(txOut.toBytes()); } - if (segwit) { + if (allowWitness && witnesses.isNotEmpty) { for (final wit in witnesses) { - final witnessesCountBytes = List.from([wit.stack.length]); - data.add(witnessesCountBytes); data.add(wit.toBytes()); } } - if (mwebBytes != null) { + if (mwebBytes != null && mwebBytes!.isNotEmpty) { data.add(mwebBytes!); } data.add(locktime); @@ -253,29 +302,30 @@ class BtcTransaction { /// [script] The scriptCode (template) that corresponds to the segwit, transaction output type that we want to spend. /// [amount] The amount of the UTXO to spend is included in the signature for segwit (in satoshis). /// [sighash] The type of the signature hash to be created. - List getTransactionSegwitDigit( - {required int txInIndex, - required Script script, - int sighash = BitcoinOpCodeConst.SIGHASH_ALL, - required BigInt amount, - CashToken? token}) { + List getTransactionSegwitDigit({ + required int txInIndex, + required Script script, + int sighash = BitcoinOpCodeConst.sighashAll, + required BigInt amount, + CashToken? token, + }) { final tx = copy(this); List hashPrevouts = List.filled(32, 0); List hashSequence = List.filled(32, 0); List hashOutputs = List.filled(32, 0); - int basicSigHashType = sighash & 0x1F; - bool anyoneCanPay = (sighash & 0xF0) == BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; - bool signAll = (basicSigHashType != BitcoinOpCodeConst.SIGHASH_SINGLE) && - (basicSigHashType != BitcoinOpCodeConst.SIGHASH_NONE); + final basicSigHashType = sighash & 0x1F; + final anyoneCanPay = (sighash & 0xF0) == BitcoinOpCodeConst.sighashAnyoneCanPay; + final signAll = (basicSigHashType != BitcoinOpCodeConst.sighashSingle) && + (basicSigHashType != BitcoinOpCodeConst.sighashNone); if (!anyoneCanPay) { hashPrevouts = []; for (final txin in tx.inputs) { - List txidBytes = List.from(BytesUtils.fromHexString(txin.txId).reversed.toList()); - hashPrevouts = List.from([ + final txidBytes = List.from(BytesUtils.fromHexString(txin.txId).reversed.toList()); + hashPrevouts = [ ...hashPrevouts, ...txidBytes, ...IntUtils.toBytes(txin.txIndex, length: 4, byteOrder: Endian.little) - ]); + ]; } hashPrevouts = QuickCrypto.sha256DoubleHash(hashPrevouts); } @@ -283,42 +333,41 @@ class BtcTransaction { if (!anyoneCanPay && signAll) { hashSequence = []; for (final i in tx.inputs) { - hashSequence = List.from([...hashSequence, ...i.sequence]); + hashSequence = [...hashSequence, ...i.sequence]; } hashSequence = QuickCrypto.sha256DoubleHash(hashSequence); } if (signAll) { hashOutputs = []; for (final i in tx.outputs) { - hashOutputs = List.from([...hashOutputs, ...i.toBytes()]); + hashOutputs = [...hashOutputs, ...i.toBytes()]; } hashOutputs = QuickCrypto.sha256DoubleHash(hashOutputs); - } else if (basicSigHashType == BitcoinOpCodeConst.SIGHASH_SINGLE && - txInIndex < tx.outputs.length) { + } + if (basicSigHashType == BitcoinOpCodeConst.sighashSingle && txInIndex < tx.outputs.length) { final out = tx.outputs[txInIndex]; - 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]); + final packedAmount = BigintUtils.toBytes(out.amount, length: 8, order: Endian.little); + final scriptBytes = IntUtils.prependVarint(out.scriptPubKey.toBytes()); + hashOutputs = [...packedAmount, ...scriptBytes]; hashOutputs = QuickCrypto.sha256DoubleHash(hashOutputs); } - DynamicByteTracker txForSigning = DynamicByteTracker(); + final txForSigning = DynamicByteTracker(); txForSigning.add(version); txForSigning.add(hashPrevouts); 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)])); + final txidBytes = BytesUtils.fromHexString(txIn.txId).reversed.toList(); + txForSigning.add( + [...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); + final varintBytes = IntUtils.prependVarint(script.toBytes()); + txForSigning.add(varintBytes); + final packedAmount = BigintUtils.toBytes(amount, length: 8, order: Endian.little); txForSigning.add(packedAmount); txForSigning.add(txIn.sequence); txForSigning.add(hashOutputs); @@ -333,129 +382,117 @@ class BtcTransaction { /// [txIndex] The index of the input that we wish to sign /// [scriptPubKeys] he scriptPubkeys that correspond to all the inputs/UTXOs /// [amounts] The amounts that correspond to all the inputs/UTXOs - /// [extFlags] Extension mechanism, default is 0; 1 is for script spending (BIP342) - /// [script] The script that we are spending (ext_flag=1) - /// [leafVar] The script version, LEAF_VERSION_TAPSCRIPT for the default tapscript /// [sighash] The type of the signature hash to be created - List getTransactionTaprootDigset( - {required int txIndex, - required List