diff --git a/analysis_options.yaml b/analysis_options.yaml index d429a46..cf19463 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -2,3 +2,10 @@ 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: + - unnecessary_const + - prefer_const_declarations + - prefer_final_locals # Warns when a local variable could be final + - prefer_final_in_for_each # Warns when a forEach variable could be final 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..1bd9fc1 --- /dev/null +++ b/example/lib/global/btc_examples_multisig_taproot_legacy_uncomp.dart @@ -0,0 +1,131 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:example/services_examples/electrum/electrum_ssl_service.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(compressed: false), weight: 1), + MultiSignatureSigner(publicKey: two.getPublic().toHex(), weight: 1), + MultiSignatureSigner(publicKey: three.getPublic().toHex(), weight: 1), + MultiSignatureSigner( + publicKey: four.getPublic().toHex(compressed: false), weight: 1), + MultiSignatureSigner(publicKey: five.getPublic().toHex(), weight: 1), + MultiSignatureSigner(publicKey: six.getPublic().toHex(), weight: 1), + MultiSignatureSigner( + publicKey: seven.getPublic().toHex(compressed: false), 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 = await ElectrumSSLService.connect( + "testnet4-electrumx.wakiyamap.dev:51002"); + + /// create provider with service + final provider = ElectrumApiProvider(service); + + final addrOne = one.getPublic().toP2pkAddress(compressed: false); + + final addrTwo = two.getPublic().toAddress(compressed: false); + + final addrThree = three.getPublic().toP2pkInP2sh(compressed: false); + final addrFour = four.getPublic().toP2pkhInP2sh(compressed: false); + 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(compressed: false), + two.getPublic().toHex(compressed: false), + three.getPublic().toHex(compressed: false), + four.getPublic().toHex(compressed: false), + four.getPublic().toHex(), + four.getPublic().toHex(), + eight.getPublic().toHex(), + eight.getPublic().toHex(), + eight.getPublic().toHex(), + eight.getPublic().toHex(), + ]; + final addresses = [ + one.getPublic().toP2pkAddress(compressed: false), + two.getPublic().toAddress(compressed: false), + three.getPublic().toP2pkInP2sh(compressed: false), + four.getPublic().toP2pkhInP2sh(compressed: false), + 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(ElectrumScriptHashListUnspent( + 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(ElectrumBroadCastTransaction(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 0ef7b04..98c3412 100644 --- a/example/lib/global/old_examples/bitcoin_example.dart +++ b/example/lib/global/old_examples/bitcoin_example.dart @@ -166,7 +166,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); @@ -400,19 +400,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); diff --git a/example/lib/global/old_examples/spending_with_transaction_builder/multi_sig_transactions.dart b/example/lib/global/old_examples/spending_with_transaction_builder/multi_sig_transactions.dart index 6b4542e..8166e85 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 @@ -265,7 +265,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. diff --git a/example/lib/global/old_examples/spending_with_transaction_builder/transaction_builder_example.dart b/example/lib/global/old_examples/spending_with_transaction_builder/transaction_builder_example.dart index 5da3143..a303df7 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 @@ -212,7 +212,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. 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 295d906..61d02cc 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 @@ -158,7 +158,7 @@ void main() async { /// create transaction and sign it final transaction = builder.buildTransaction((trDigest, utxo, publicKey, sighash) { - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { return privateKey.signTapRoot(trDigest, sighash: sighash); } return privateKey.signInput(trDigest, sigHash: sighash); diff --git a/example/lib/global/transfer_to_8_account_example.dart b/example/lib/global/transfer_to_8_account_example.dart index 6830feb..235c673 100644 --- a/example/lib/global/transfer_to_8_account_example.dart +++ b/example/lib/global/transfer_to_8_account_example.dart @@ -136,7 +136,7 @@ void main() async { /// create transaction and sign it final transaction = builder.buildTransaction((trDigest, utxo, publicKey, sighash) { - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { return examplePrivateKey2.signTapRoot(trDigest, sighash: sighash); } return examplePrivateKey2.signInput(trDigest, sigHash: sighash); diff --git a/example/pubspec.lock b/example/pubspec.lock index b2187bd..7873d10 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -19,10 +19,9 @@ packages: blockchain_utils: dependency: "direct main" description: - name: blockchain_utils - sha256: eebd06ff3709be5c660be72f0b0ee8f524fbb9e20752436f19f07f3be8ee6d1f - url: "https://pub.dev" - source: hosted + path: "../../blockchain_utils" + relative: true + source: path version: "3.5.0" boolean_selector: dependency: transitive diff --git a/example/pubspec.yaml b/example/pubspec.yaml index bce4f5a..c2fdaea 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -37,9 +37,9 @@ dependencies: cupertino_icons: ^1.0.2 bitcoin_base: path: ../ - # blockchain_utils: - # path: ../../blockchain_utils - blockchain_utils: ^3.5.0 + blockchain_utils: + path: ../../../blockchain_utils + # blockchain_utils: ^3.5.0 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 ef45ef4..9edd8ac 100644 --- a/lib/bitcoin_base.dart +++ b/lib/bitcoin_base.dart @@ -4,7 +4,7 @@ /// 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'; diff --git a/lib/src/bitcoin/address/address.dart b/lib/src/bitcoin/address/address.dart index 8f1d988..f878515 100644 --- a/lib/src/bitcoin/address/address.dart +++ b/lib/src/bitcoin/address/address.dart @@ -6,7 +6,7 @@ // - 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/exception/exception.dart'; diff --git a/lib/src/bitcoin/address/core.dart b/lib/src/bitcoin/address/core.dart index 51a04e8..a3fcb6d 100644 --- a/lib/src/bitcoin/address/core.dart +++ b/lib/src/bitcoin/address/core.dart @@ -36,6 +36,14 @@ abstract class BitcoinAddressType implements Enumerate { P2shAddressType.p2pkInP2shwt, P2pkhAddressType.p2pkhwt ]; + T cast() { + if (this is! T) { + throw DartBitcoinPluginException("BitcoinAddressType casting failed.", + details: {"excepted": "$T", "type": value}); + } + return this as T; + } + @override String toString() { return "BitcoinAddressType.$value"; @@ -50,15 +58,14 @@ abstract class BitcoinBaseAddress { String get addressProgram; } -class PubKeyAddressType implements BitcoinAddressType { - const PubKeyAddressType._(this.value); +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 @@ -67,8 +74,8 @@ class PubKeyAddressType implements BitcoinAddressType { } } -class P2pkhAddressType implements BitcoinAddressType { - const P2pkhAddressType._(this.value); +class P2pkhAddressType extends BitcoinAddressType { + const P2pkhAddressType._(super.value) : super._(); static const P2pkhAddressType p2pkh = P2pkhAddressType._("P2PKH"); static const P2pkhAddressType p2pkhwt = P2pkhAddressType._("P2PKHWT"); @@ -77,9 +84,6 @@ class P2pkhAddressType implements BitcoinAddressType { @override bool get isSegwit => false; - @override - final String value; - @override int get hashLength => 20; @override @@ -88,8 +92,9 @@ class P2pkhAddressType implements BitcoinAddressType { } } -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 = P2shAddressType._( @@ -131,17 +136,14 @@ class P2shAddressType implements BitcoinAddressType { static const P2shAddressType p2pkInP2shwt = P2shAddressType._( "P2SHWT/P2PK", _BitcoinAddressUtils.hash160DigestLength, true); - @override - final String value; - @override String toString() { return "P2shAddressType.$value"; } } -class SegwitAddresType implements BitcoinAddressType { - const SegwitAddresType._(this.value); +class SegwitAddresType extends BitcoinAddressType { + const SegwitAddresType._(super.value) : super._(); static const SegwitAddresType p2wpkh = SegwitAddresType._("P2WPKH"); static const SegwitAddresType p2tr = SegwitAddresType._("P2TR"); static const SegwitAddresType p2wsh = SegwitAddresType._("P2WSH"); @@ -150,9 +152,6 @@ class SegwitAddresType implements BitcoinAddressType { @override bool get isSegwit => true; - @override - final String value; - @override int get hashLength { switch (this) { diff --git a/lib/src/bitcoin/address/legacy_address.dart b/lib/src/bitcoin/address/legacy_address.dart index 30b1445..e8b3d54 100644 --- a/lib/src/bitcoin/address/legacy_address.dart +++ b/lib/src/bitcoin/address/legacy_address.dart @@ -45,14 +45,14 @@ abstract class LegacyAddress implements BitcoinBaseAddress { class P2shAddress extends LegacyAddress { P2shAddress.fromScript( - {required Script script, this.type = P2shAddressType.p2pkInP2sh}) - : super.fromScript(script: script); + {required super.script, this.type = P2shAddressType.p2pkInP2sh}) + : super.fromScript(); P2shAddress.fromAddress( - {required String address, - required BasedUtxoNetwork network, + {required super.address, + required super.network, this.type = P2shAddressType.p2pkInP2sh}) - : super.fromAddress(address: address, network: network); + : super.fromAddress(); P2shAddress.fromHash160( {required String addrHash, this.type = P2shAddressType.p2pkInP2sh}) : super.fromHash160(addrHash, type); @@ -81,13 +81,13 @@ class P2shAddress extends LegacyAddress { class P2pkhAddress extends LegacyAddress { P2pkhAddress.fromScript( - {required Script script, this.type = P2pkhAddressType.p2pkh}) - : super.fromScript(script: script); + {required super.script, this.type = P2pkhAddressType.p2pkh}) + : super.fromScript(); P2pkhAddress.fromAddress( - {required String address, - required BasedUtxoNetwork network, + {required super.address, + required super.network, this.type = P2pkhAddressType.p2pkh}) - : super.fromAddress(address: address, network: network); + : super.fromAddress(); P2pkhAddress.fromHash160( {required String addrHash, this.type = P2pkhAddressType.p2pkh}) : super.fromHash160(addrHash, type); diff --git a/lib/src/bitcoin/address/segwit_address.dart b/lib/src/bitcoin/address/segwit_address.dart index 039395d..4731973 100644 --- a/lib/src/bitcoin/address/segwit_address.dart +++ b/lib/src/bitcoin/address/segwit_address.dart @@ -46,21 +46,15 @@ abstract class SegwitAddress implements BitcoinBaseAddress { } class P2wpkhAddress extends SegwitAddress { - P2wpkhAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) - : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV0, - address: address, - network: network); + P2wpkhAddress.fromAddress({required super.address, required super.network}) + : super.fromAddress(segwitVersion: _BitcoinAddressUtils.segwitV0); - P2wpkhAddress.fromProgram({required String program}) + P2wpkhAddress.fromProgram({required super.program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV0, - program: program, addresType: SegwitAddresType.p2wpkh); - P2wpkhAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + P2wpkhAddress.fromScript({required super.script}) + : super.fromScript(segwitVersion: _BitcoinAddressUtils.segwitV0); /// returns the scriptPubKey of a P2WPKH witness script @override @@ -74,20 +68,14 @@ class P2wpkhAddress extends SegwitAddress { } class P2trAddress extends SegwitAddress { - P2trAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) - : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV1, - address: address, - network: network); - P2trAddress.fromProgram({required String program}) + P2trAddress.fromAddress({required super.address, required super.network}) + : super.fromAddress(segwitVersion: _BitcoinAddressUtils.segwitV1); + P2trAddress.fromProgram({required super.program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV1, - program: program, addresType: SegwitAddresType.p2tr); - P2trAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV1, script: script); + P2trAddress.fromScript({required super.script}) + : super.fromScript(segwitVersion: _BitcoinAddressUtils.segwitV1); /// returns the scriptPubKey of a P2TR witness script @override @@ -101,20 +89,14 @@ class P2trAddress extends SegwitAddress { } class P2wshAddress extends SegwitAddress { - P2wshAddress.fromAddress( - {required String address, required BasedUtxoNetwork network}) - : super.fromAddress( - segwitVersion: _BitcoinAddressUtils.segwitV0, - address: address, - network: network); - P2wshAddress.fromProgram({required String program}) + P2wshAddress.fromAddress({required super.address, required super.network}) + : super.fromAddress(segwitVersion: _BitcoinAddressUtils.segwitV0); + P2wshAddress.fromProgram({required super.program}) : super.fromProgram( segwitVersion: _BitcoinAddressUtils.segwitV0, - program: program, addresType: SegwitAddresType.p2wsh); - P2wshAddress.fromScript({required Script script}) - : super.fromScript( - segwitVersion: _BitcoinAddressUtils.segwitV0, script: script); + P2wshAddress.fromScript({required super.script}) + : super.fromScript(segwitVersion: _BitcoinAddressUtils.segwitV0); /// Returns the scriptPubKey of a P2WPKH witness script @override diff --git a/lib/src/bitcoin/address/utils/address_utils.dart b/lib/src/bitcoin/address/utils/address_utils.dart index af88a82..5b1f431 100644 --- a/lib/src/bitcoin/address/utils/address_utils.dart +++ b/lib/src/bitcoin/address/utils/address_utils.dart @@ -35,13 +35,13 @@ class _BitcoinAddressUtils { /// Extract version, data, and checksum. final List version = [decode[0]]; - List data = + final List data = decode.sublist(0, decode.length - Base58Const.checksumByteLen); - List checksum = + final List checksum = decode.sublist(decode.length - Base58Const.checksumByteLen); /// Verify the checksum. - List hash = QuickCrypto.sha256DoubleHash(data) + final List hash = QuickCrypto.sha256DoubleHash(data) .sublist(0, Base58Const.checksumByteLen); if (!BytesUtils.bytesEqual(checksum, hash)) { return null; @@ -351,7 +351,7 @@ class _BitcoinAddressUtils { {required BitcoinCashNetwork network, required String addressProgram, required BitcoinAddressType type}) { - List programBytes = BytesUtils.fromHexString(addressProgram); + final List programBytes = BytesUtils.fromHexString(addressProgram); final List netVersion = _getBchNetVersion( network: network, type: type, secriptLength: programBytes.length); @@ -370,7 +370,7 @@ class _BitcoinAddressUtils { {required BitcoinCashNetwork network, required BitcoinAddressType type, int secriptLength = hash160DigestLength}) { - bool isToken = type.value.contains("WT"); + final bool isToken = type.value.contains("WT"); if (!type.isP2sh) { if (!isToken) return network.p2pkhNetVer; return network.p2pkhWtNetVer; @@ -431,7 +431,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 List ripemd160Hash = QuickCrypto.hash160(bytes); return BytesUtils.toHexString(ripemd160Hash); } diff --git a/lib/src/bitcoin/script/input.dart b/lib/src/bitcoin/script/input.dart index c9d0aee..f1b56da 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'; @@ -42,50 +43,54 @@ class TxInput { 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, ...txoutBytes, ...scriptSigLengthVarint, ...scriptSigBytes, - ...sequence, + ...sequence ]); 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 DartBitcoinPluginException( - "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, int cursor = 0, bool hasSegwit = false}) { + final List inpHash = + bytes.sublist(cursor, cursor + 32).reversed.toList(); + cursor += 32; + final int 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 List unlockingScript = bytes.sublist(cursor, cursor + vi.item1); cursor += vi.item1; - List sequenceNumberData = txInputRaw.sublist(cursor, cursor + 4); + final List 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), sequance: sequenceNumberData), cursor); } + Map toJson() { + return { + "txid": txId, + "txIndex": txIndex, + "scriptSig": scriptSig.script, + "sequance": BytesUtils.toHexString(sequence), + }; + } + @override String toString() { return "TxInput{txId: $txId, txIndex: $txIndex, scriptSig: $scriptSig, sequence: ${BytesUtils.toHexString(sequence)}}"; diff --git a/lib/src/bitcoin/script/op_code/constant.dart b/lib/src/bitcoin/script/op_code/constant.dart index cb6b952..4825e66 100644 --- a/lib/src/bitcoin/script/op_code/constant.dart +++ b/lib/src/bitcoin/script/op_code/constant.dart @@ -2,6 +2,15 @@ /// Constants and identifiers used in the Bitcoin-related code. // 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 Map> OP_CODES = { 'OP_0': [0x00], 'OP_FALSE': [0x00], diff --git a/lib/src/bitcoin/script/op_code/constant_lib.dart b/lib/src/bitcoin/script/op_code/constant_lib.dart index 4a7c224..e6c4f56 100644 --- a/lib/src/bitcoin/script/op_code/constant_lib.dart +++ b/lib/src/bitcoin/script/op_code/constant_lib.dart @@ -1,4 +1,4 @@ -library bitcoin_constants; +library; 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 index 8d17869..a68492a 100644 --- a/lib/src/bitcoin/script/op_code/tools.dart +++ b/lib/src/bitcoin/script/op_code/tools.dart @@ -1,23 +1,23 @@ +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'; List opPushData(String hexData) { final List dataBytes = BytesUtils.fromHexString(hexData); - if (dataBytes.length < 0x4c) { - return List.from([dataBytes.length]) + dataBytes; + if (dataBytes.length < BitcoinOpCodeConst.opPushData1) { + return [dataBytes.length, ...dataBytes]; } else if (dataBytes.length < mask8) { - return List.from([0x4c]) + - List.from([dataBytes.length]) + - dataBytes; + return [BitcoinOpCodeConst.opPushData1, dataBytes.length, ...dataBytes]; } else if (dataBytes.length < mask16) { - var lengthBytes = List.filled(2, 0); - - writeUint16LE(dataBytes.length, lengthBytes); - return List.from([0x4d, ...lengthBytes, ...dataBytes]); + final lengthBytes = + IntUtils.toBytes(dataBytes.length, length: 2, byteOrder: Endian.little); + return [BitcoinOpCodeConst.opPushData2, ...lengthBytes, ...dataBytes]; } else if (dataBytes.length < mask32) { - var lengthBytes = List.filled(4, 0); - writeUint32LE(lengthBytes.length, lengthBytes); - return List.from([0x4e, ...lengthBytes, ...dataBytes]); + final lengthBytes = + IntUtils.toBytes(dataBytes.length, length: 4, byteOrder: Endian.little); + return [BitcoinOpCodeConst.opPushData4, ...lengthBytes, ...dataBytes]; } else { throw const DartBitcoinPluginException( "Data too large. Cannot push into script"); @@ -31,7 +31,7 @@ List pushInteger(int integer) { } /// Calculate the number of bytes required to represent the integer - int numberOfBytes = (integer.bitLength + 7) ~/ 8; + final int numberOfBytes = (integer.bitLength + 7) ~/ 8; /// Convert to little-endian bytes List integerBytes = List.filled(numberOfBytes, 0); diff --git a/lib/src/bitcoin/script/output.dart b/lib/src/bitcoin/script/output.dart index 8a25497..61bca22 100644 --- a/lib/src/bitcoin/script/output.dart +++ b/lib/src/bitcoin/script/output.dart @@ -14,6 +14,14 @@ class TxOutput { final BigInt amount; final Script scriptPubKey; + Map toJson() { + return { + "cashToken": cashToken?.toJson(), + "amount": amount.toString(), + "scriptPubKey": scriptPubKey.script + }; + } + /// creates a copy of the object TxOutput copy() { return TxOutput( @@ -25,7 +33,7 @@ class TxOutput { List toBytes() { final amountBytes = BigintUtils.toBytes(amount, length: 8, order: Endian.little); - List scriptBytes = [ + final List scriptBytes = [ ...cashToken?.toBytes() ?? [], ...scriptPubKey.toBytes() ]; @@ -37,27 +45,26 @@ class TxOutput { 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), + static Tuple deserialize( + {required List bytes, required int cursor, bool hasSegwit = false}) { + 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.fromRaw(bytes.sublist(cursor)); + + final List 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, hasSegwit: hasSegwit)), cursor); } diff --git a/lib/src/bitcoin/script/script.dart b/lib/src/bitcoin/script/script.dart index 5228776..96c57c7 100644 --- a/lib/src/bitcoin/script/script.dart +++ b/lib/src/bitcoin/script/script.dart @@ -16,44 +16,54 @@ class Script { script = List.unmodifiable(script); final List script; - static Script fromRaw({required String hexData, bool hasSegwit = false}) { - List commands = []; + static Script deserialize( + {required List bytes, bool hasSegwit = false}) { + final List commands = []; int index = 0; - final scriptBytes = BytesUtils.fromHexString(hexData); - while (index < scriptBytes.length) { - int byte = scriptBytes[index]; + // final scriptBytes = BytesUtils.fromHexString(hexData); + while (index < bytes.length) { + final int byte = bytes[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]; + if (!BitcoinOpCodeConst.isOpPushData(byte)) { + commands.add(BitcoinOpCodeConst.CODE_OPS[byte]!); + } + + /// 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); + if (byte == BitcoinOpCodeConst.opPushData1) { + // get len + final int bytesToRead = bytes[index]; + // skip len + index = index + 1; + commands.add(BytesUtils.toHexString( + bytes.sublist(index, index + bytesToRead))); - 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); + /// add length + index = index + bytesToRead; + } else if (byte == BitcoinOpCodeConst.opPushData2) { + /// get len + final int bytesToRead = readUint16LE(bytes, index); + index = index + 2; + commands.add(BytesUtils.toHexString( + bytes.sublist(index, index + bytesToRead))); + index = index + bytesToRead; + } else if (byte == BitcoinOpCodeConst.opPushData4) { + final int bytesToRead = readUint32LE(bytes, index); - index = index + 5; - commands.add(BytesUtils.toHexString( - scriptBytes.sublist(index, index + bytesToRead))); - index = index + bytesToRead; + index = index + 4; + commands.add(BytesUtils.toHexString( + bytes.sublist(index, index + bytesToRead))); + index = index + bytesToRead; + } } else { - final viAndSize = IntUtils.decodeVarint(scriptBytes.sublist(index)); - int dataSize = viAndSize.item1; - int size = viAndSize.item2; - final lastIndex = (index + size + dataSize) > scriptBytes.length - ? scriptBytes.length + final viAndSize = IntUtils.decodeVarint(bytes.sublist(index)); + final int dataSize = viAndSize.item1; + final int size = viAndSize.item2; + final lastIndex = (index + size + dataSize) > bytes.length + ? bytes.length : (index + size + dataSize); - commands.add(BytesUtils.toHexString( - scriptBytes.sublist(index + size, lastIndex))); + commands.add( + BytesUtils.toHexString(bytes.sublist(index + size, lastIndex))); index = index + dataSize + size; } } @@ -62,8 +72,8 @@ class Script { List toBytes() { if (script.isEmpty) return []; - DynamicByteTracker scriptBytes = DynamicByteTracker(); - for (var token in script) { + final DynamicByteTracker scriptBytes = DynamicByteTracker(); + for (final token in script) { if (BitcoinOpCodeConst.OP_CODES.containsKey(token)) { scriptBytes.add(BitcoinOpCodeConst.OP_CODES[token]!); } else if (token is int && token >= 0 && token <= 16) { diff --git a/lib/src/bitcoin/script/transaction.dart b/lib/src/bitcoin/script/transaction.dart index 28e8333..a7f06a2 100644 --- a/lib/src/bitcoin/script/transaction.dart +++ b/lib/src/bitcoin/script/transaction.dart @@ -3,6 +3,7 @@ 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 'input.dart'; @@ -26,13 +27,11 @@ class BtcTransaction { this.hasSegwit = false, List? lock, List? version}) - : 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); + : locktime = lock?.immutable ?? BitcoinOpCodeConst.DEFAULT_TX_LOCKTIME, + version = version?.immutable ?? BitcoinOpCodeConst.DEFAULT_TX_VERSION, + inputs = inputs.immutable, + outputs = outputs.immutable, + witnesses = witnesses.immutable; final List inputs; final List outputs; final List locktime; @@ -49,13 +48,12 @@ class BtcTransaction { List? version, }) { return BtcTransaction( - inputs: inputs ?? this.inputs, - outputs: outputs ?? this.outputs, - witnesses: witnesses ?? this.witnesses, - hasSegwit: hasSegwit ?? this.hasSegwit, - lock: lock ?? List.from(locktime), - version: version ?? List.from(this.version), - ); + inputs: inputs ?? this.inputs, + outputs: outputs ?? this.outputs, + witnesses: witnesses ?? this.witnesses, + hasSegwit: hasSegwit ?? this.hasSegwit, + lock: lock ?? locktime, + version: version ?? this.version); } /// creates a copy of the object (classmethod) @@ -85,33 +83,30 @@ class BtcTransaction { } final vi = IntUtils.decodeVarint(rawtx.sublist(cursor)); cursor += vi.item2; - - List inputs = []; + final List inputs = []; for (int index = 0; index < vi.item1; index++) { - final inp = - TxInput.fromRaw(raw: raw, hasSegwit: hasSegwit, cursor: cursor); - + final inp = TxInput.deserialize( + bytes: rawtx, hasSegwit: hasSegwit, cursor: cursor); inputs.add(inp.item1); cursor = inp.item2; } - - List outputs = []; + final List outputs = []; final viOut = IntUtils.decodeVarint(rawtx.sublist(cursor)); cursor += viOut.item2; for (int index = 0; index < viOut.item1; index++) { - final inp = - TxOutput.fromRaw(raw: raw, hasSegwit: hasSegwit, cursor: cursor); + final inp = TxOutput.deserialize( + bytes: rawtx, hasSegwit: hasSegwit, cursor: cursor); outputs.add(inp.item1); cursor = inp.item2; } - List witnesses = []; + final List witnesses = []; if (hasSegwit) { if (cursor + 4 < rawtx.length) { // in this case the tx contains wintness data. for (int n = 0; n < inputs.length; n++) { final wVi = IntUtils.decodeVarint(rawtx.sublist(cursor)); cursor += wVi.item2; - List witnessesTmp = []; + final List witnessesTmp = []; for (int n = 0; n < wVi.item1; n++) { List witness = []; final wtVi = IntUtils.decodeVarint(rawtx.sublist(cursor)); @@ -155,12 +150,10 @@ class BtcTransaction { } tx.inputs[txInIndex].scriptSig = script; if ((sighash & 0x1f) == BitcoinOpCodeConst.SIGHASH_NONE) { - // tx.outputs.clear(); 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.EMPTY_TX_SEQUENCE; } } } else if ((sighash & 0x1f) == BitcoinOpCodeConst.SIGHASH_SINGLE) { @@ -169,7 +162,7 @@ class BtcTransaction { "Transaction index is greater than the available outputs"); } - List outputs = []; + final List outputs = []; for (int i = 0; i < txInIndex; i++) { outputs.add(TxOutput( amount: BigInt.from(BitcoinOpCodeConst.NEGATIVE_SATOSHI), @@ -178,8 +171,7 @@ class BtcTransaction { tx = tx.copyWith(outputs: [...outputs, tx.outputs[txInIndex]]); for (int i = 0; i < tx.inputs.length; i++) { if (i != txInIndex) { - tx.inputs[i].sequence = - List.unmodifiable(BitcoinOpCodeConst.EMPTY_TX_SEQUENCE); + tx.inputs[i].sequence = BitcoinOpCodeConst.EMPTY_TX_SEQUENCE; } } } @@ -188,16 +180,16 @@ class BtcTransaction { } List txForSign = tx.toBytes(segwit: false); - txForSign = List.from([ + 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(); + final DynamicByteTracker data = DynamicByteTracker(); data.add(version); if (segwit) { data.add([0x00, 0x01]); @@ -240,21 +232,22 @@ class BtcTransaction { List hashPrevouts = List.filled(32, 0); List hashSequence = List.filled(32, 0); List hashOutputs = List.filled(32, 0); - int basicSigHashType = sighash & 0x1F; - bool anyoneCanPay = + final int basicSigHashType = sighash & 0x1F; + final bool anyoneCanPay = (sighash & 0xF0) == BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; - bool signAll = (basicSigHashType != BitcoinOpCodeConst.SIGHASH_SINGLE) && - (basicSigHashType != BitcoinOpCodeConst.SIGHASH_NONE); + final bool signAll = + (basicSigHashType != BitcoinOpCodeConst.SIGHASH_SINGLE) && + (basicSigHashType != BitcoinOpCodeConst.SIGHASH_NONE); if (!anyoneCanPay) { hashPrevouts = []; for (final txin in tx.inputs) { - List txidBytes = List.from( + final List txidBytes = List.from( BytesUtils.fromHexString(txin.txId).reversed.toList()); - hashPrevouts = List.from([ + hashPrevouts = [ ...hashPrevouts, ...txidBytes, ...IntUtils.toBytes(txin.txIndex, length: 4, byteOrder: Endian.little) - ]); + ]; } hashPrevouts = QuickCrypto.sha256DoubleHash(hashPrevouts); } @@ -262,55 +255,52 @@ 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) { final out = tx.outputs[txInIndex]; - List packedAmount = + final 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 scriptBytes = IntUtils.prependVarint(out.scriptPubKey.toBytes()); + hashOutputs = [...packedAmount, ...scriptBytes]; hashOutputs = QuickCrypto.sha256DoubleHash(hashOutputs); } - DynamicByteTracker txForSigning = DynamicByteTracker(); + final DynamicByteTracker 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([ + final List 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 List packedAmount = + BigintUtils.toBytes(amount, length: 8, order: Endian.little); txForSigning.add(packedAmount); txForSigning.add(txIn.sequence); txForSigning.add(hashOutputs); txForSigning.add(locktime); txForSigning .add(IntUtils.toBytes(sighash, length: 4, byteOrder: Endian.little)); - return QuickCrypto.sha256DoubleHash(txForSigning.toBytes()); } @@ -332,11 +322,13 @@ class BtcTransaction { int leafVar = BitcoinOpCodeConst.LEAF_VERSION_TAPSCRIPT, int sighash = BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL}) { final newTx = copy(this); - bool sighashNone = (sighash & 0x03) == BitcoinOpCodeConst.SIGHASH_NONE; - bool sighashSingle = (sighash & 0x03) == BitcoinOpCodeConst.SIGHASH_SINGLE; - bool anyoneCanPay = + final bool sighashNone = + (sighash & 0x03) == BitcoinOpCodeConst.SIGHASH_NONE; + final bool sighashSingle = + (sighash & 0x03) == BitcoinOpCodeConst.SIGHASH_SINGLE; + final bool anyoneCanPay = (sighash & 0x80) == BitcoinOpCodeConst.SIGHASH_ANYONECANPAY; - DynamicByteTracker txForSign = DynamicByteTracker(); + final DynamicByteTracker txForSign = DynamicByteTracker(); txForSign.add([0]); txForSign.add([sighash]); txForSign.add(version); @@ -348,109 +340,89 @@ class BtcTransaction { List hashOutputs = []; if (!anyoneCanPay) { for (final txin in newTx.inputs) { - List txidBytes = List.from( - BytesUtils.fromHexString(txin.txId).reversed.toList()); - hashPrevouts = List.from([ + final List txidBytes = + BytesUtils.fromHexString(txin.txId).reversed.toList(); + hashPrevouts = [ ...hashPrevouts, ...txidBytes, ...IntUtils.toBytes(txin.txIndex, length: 4, byteOrder: Endian.little) - ]); + ]; } hashPrevouts = QuickCrypto.sha256Hash(hashPrevouts); txForSign.add(hashPrevouts); for (final i in amounts) { - List bytes = - BigintUtils.toBytes(i, length: 8, order: Endian.little); - - hashAmounts = List.from([...hashAmounts, ...bytes]); + final bytes = BigintUtils.toBytes(i, length: 8, order: Endian.little); + hashAmounts = [...hashAmounts, ...bytes]; } hashAmounts = QuickCrypto.sha256Hash(hashAmounts); txForSign.add(hashAmounts); for (final s in scriptPubKeys) { - final h = s.toHex(); - - /// must checked - int scriptLen = h.length ~/ 2; - List scriptBytes = BytesUtils.fromHexString(h); - List lenBytes = List.from([scriptLen]); - hashScriptPubkeys = - List.from([...hashScriptPubkeys, ...lenBytes, ...scriptBytes]); + final scriptBytes = IntUtils.prependVarint(s.toBytes()); + hashScriptPubkeys = [...hashScriptPubkeys, ...scriptBytes]; } hashScriptPubkeys = QuickCrypto.sha256Hash(hashScriptPubkeys); txForSign.add(hashScriptPubkeys); for (final txIn in newTx.inputs) { - hashSequences = List.from([...hashSequences, ...txIn.sequence]); + hashSequences = [...hashSequences, ...txIn.sequence]; } hashSequences = QuickCrypto.sha256Hash(hashSequences); txForSign.add(hashSequences); } if (!(sighashNone || sighashSingle)) { for (final txOut in newTx.outputs) { - List packedAmount = + final List packedAmount = BigintUtils.toBytes(txOut.amount, length: 8, order: Endian.little); - - List scriptBytes = txOut.scriptPubKey.toBytes(); - final lenScriptBytes = List.from([scriptBytes.length]); - hashOutputs = List.from([ - ...hashOutputs, - ...packedAmount, - ...lenScriptBytes, - ...scriptBytes - ]); + final scriptBytes = + IntUtils.prependVarint(txOut.scriptPubKey.toBytes()); + hashOutputs = [...hashOutputs, ...packedAmount, ...scriptBytes]; } hashOutputs = QuickCrypto.sha256Hash(hashOutputs); txForSign.add(hashOutputs); } final int spendType = extFlags * 2 + 0; - txForSign.add(List.from([spendType])); + txForSign.add([spendType]); if (anyoneCanPay) { final txin = newTx.inputs[txIndex]; - List txidBytes = - List.from(BytesUtils.fromHexString(txin.txId).reversed.toList()); - List result = List.from([ + final txidBytes = BytesUtils.fromHexString(txin.txId).reversed.toList(); + final result = [ ...txidBytes, ...IntUtils.toBytes(txin.txIndex, length: 4, byteOrder: Endian.little) - ]); + ]; txForSign.add(result); txForSign.add(BigintUtils.toBytes(amounts[txIndex], length: 8, order: Endian.little)); - final sPubKey = scriptPubKeys[txIndex].toHex(); - final sLength = sPubKey.length ~/ 2; - txForSign.add([sLength]); - txForSign.add(BytesUtils.fromHexString(sPubKey)); + final scriptBytes = + IntUtils.prependVarint(scriptPubKeys[txIndex].toBytes()); + txForSign.add(scriptBytes); txForSign.add(txin.sequence); } else { - int index = txIndex; - List indexBytes = List.filled(4, 0); - writeUint32LE(index, indexBytes); + final indexBytes = + IntUtils.toBytes(txIndex, length: 4, byteOrder: Endian.little); txForSign.add(indexBytes); } if (sighashSingle) { final txOut = newTx.outputs[txIndex]; - - List packedAmount = + final packedAmount = BigintUtils.toBytes(txOut.amount, length: 8, order: Endian.little); - final sBytes = txOut.scriptPubKey.toBytes(); - List lenScriptBytes = List.from([sBytes.length]); - - final hashOut = - List.from([...packedAmount, ...lenScriptBytes, ...sBytes]); + final scriptBytes = IntUtils.prependVarint(txOut.scriptPubKey.toBytes()); + final hashOut = [...packedAmount, ...scriptBytes]; txForSign.add(QuickCrypto.sha256Hash(hashOut)); } if (extFlags == 1) { - final leafVarBytes = List.from( - [leafVar, ...IntUtils.prependVarint(script?.toBytes() ?? [])]); + final leafVarBytes = [ + leafVar, + ...IntUtils.prependVarint(script?.toBytes() ?? []) + ]; txForSign.add(taggedHash(leafVarBytes, "TapLeaf")); txForSign.add([0]); txForSign.add(List.filled(4, mask8)); } final bytes = txForSign.toBytes(); - return taggedHash(bytes, "TapSighash"); } @@ -473,7 +445,7 @@ class BtcTransaction { /// Calculates the tx segwit size int getVSize() { if (!hasSegwit) return getSize(); - int markerSize = 2; + const int markerSize = 2; int witSize = 0; List data = []; for (final w in witnesses) { @@ -481,8 +453,8 @@ class BtcTransaction { data = List.from([...data, ...countBytes, ...w.toBytes()]); } witSize = data.length; - int size = getSize() - (markerSize + witSize); - double vSize = size + (markerSize + witSize) / 4; + final int size = getSize() - (markerSize + witSize); + final double vSize = size + (markerSize + witSize) / 4; return vSize.ceil(); } @@ -493,6 +465,16 @@ class BtcTransaction { return BytesUtils.toHexString(reversedHash); } + Map toJson() { + return { + "inputs": inputs.map((e) => e.toJson()).toList(), + "outputs": outputs.map((e) => e.toJson()).toList(), + "locktime": BytesUtils.toHexString(locktime), + "version": BytesUtils.toHexString(version), + "witnesses": witnesses.map((e) => e.toJson()).toList() + }; + } + @override String toString() { return "BtcTransaction{inputs: ${inputs.join(", ")}, outputs: ${outputs.join(", ")}, locktime: ${BytesUtils.toHexString(locktime)}}, version: ${BytesUtils.toHexString(version)}, hasSegwit: $hasSegwit, witnesses:${witnesses.join(",")} "; diff --git a/lib/src/bitcoin/script/witness.dart b/lib/src/bitcoin/script/witness.dart index 86b0db5..075726f 100644 --- a/lib/src/bitcoin/script/witness.dart +++ b/lib/src/bitcoin/script/witness.dart @@ -1,11 +1,11 @@ +import 'package:blockchain_utils/helper/extensions/extensions.dart'; import 'package:blockchain_utils/utils/utils.dart'; /// A list of the witness items required to satisfy the locking conditions of a segwit input (aka witness stack). /// /// [stack] the witness items (hex str) list class TxWitnessInput { - TxWitnessInput({required List stack}) - : stack = List.unmodifiable(stack); + TxWitnessInput({required List stack}) : stack = stack.immutable; final List stack; /// creates a copy of the object (classmethod) @@ -17,8 +17,8 @@ class TxWitnessInput { List toBytes() { List stackBytes = []; - for (String item in stack) { - List itemBytes = + for (final item in stack) { + final List itemBytes = IntUtils.prependVarint(BytesUtils.fromHexString(item)); stackBytes = [...stackBytes, ...itemBytes]; } @@ -26,6 +26,10 @@ class TxWitnessInput { return stackBytes; } + Map toJson() { + return {"stack": stack}; + } + @override String toString() { return "TxWitnessInput{stack: ${stack.join(", ")}}"; diff --git a/lib/src/bitcoin_cash/bcmr_registery.dart b/lib/src/bitcoin_cash/bcmr_registery.dart index 8d1af2d..6975089 100644 --- a/lib/src/bitcoin_cash/bcmr_registery.dart +++ b/lib/src/bitcoin_cash/bcmr_registery.dart @@ -700,26 +700,16 @@ class ChainSnapshot extends IdentitySnapshot { ); } const ChainSnapshot({ - required String name, - required TokenCategory token, - String? description, - List? tags, - String? migrated, - String? status, - String? splitId, - URIs? uris, - Extensions? extensions, - }) : super( - name: name, - description: description, - tags: tags, - migrated: migrated, - token: token, - status: status, - splitId: splitId, - uris: uris, - extensions: extensions, - ); + required super.name, + required TokenCategory super.token, + super.description, + super.tags, + super.migrated, + super.status, + super.splitId, + super.uris, + super.extensions, + }); } class RegistryTimestampKeyedValues { @@ -732,8 +722,7 @@ class RegistryTimestampKeyedValues { } class ChainHistory extends RegistryTimestampKeyedValues { - const ChainHistory({required Map timestampMap}) - : super(timestampMap: timestampMap); + const ChainHistory({required super.timestampMap}); factory ChainHistory.fromJson(Map json) { return ChainHistory( @@ -745,8 +734,7 @@ class ChainHistory extends RegistryTimestampKeyedValues { } class IdentityHistory extends RegistryTimestampKeyedValues { - const IdentityHistory({required Map timestampMap}) - : super(timestampMap: timestampMap); + const IdentityHistory({required super.timestampMap}); factory IdentityHistory.fromJson(Map json) { return IdentityHistory( diff --git a/lib/src/cash_token/cash_token.dart b/lib/src/cash_token/cash_token.dart index e322257..a28eaaf 100644 --- a/lib/src/cash_token/cash_token.dart +++ b/lib/src/cash_token/cash_token.dart @@ -160,7 +160,7 @@ class CashTokenUtils { } static Tuple _decodeVarintBigInt(List byteint) { - int ni = byteint[0]; + final int ni = byteint[0]; int size = 0; if (ni < 253) { @@ -174,7 +174,7 @@ class CashTokenUtils { } else { size = 8; } - BigInt value = BigintUtils.fromBytes(byteint.sublist(1, 1 + size), + final value = BigintUtils.fromBytes(byteint.sublist(1, 1 + size), byteOrder: Endian.little); return Tuple(value, size + 1); } @@ -240,7 +240,7 @@ class CashToken { ? null : BytesUtils.fromHexString(json["nft"]["commitment"]); } - int bitfield = CashTokenUtils.buildBitfield( + final int bitfield = CashTokenUtils.buildBitfield( hasNFT: capability != null, capability: capability ?? CashTokenCapability.noCapability, hasAmount: amount > BigInt.zero, @@ -261,6 +261,7 @@ class CashToken { /// The commitment contents of the NFT held in this output (0 to 40 bytes). This field is omitted if no NFT is present. final List commitment; final int bitfield; + CashToken.noValidate( {required this.category, required this.amount, @@ -311,7 +312,7 @@ class CashToken { return const Tuple(null, 0); } int cursor = 1; - List id = + final List id = scriptBytes.sublist(cursor, cursor + CashTokenUtils.idBytesLength); cursor += CashTokenUtils.idBytesLength; @@ -351,7 +352,7 @@ class CashToken { /// /// Returns a list of integers representing the serialized byte representation of the [CashToken]. List toBytes() { - DynamicByteTracker bytes = DynamicByteTracker(); + final DynamicByteTracker bytes = DynamicByteTracker(); bytes.add([CashTokenUtils.cashTokenPrefix]); bytes.add(BytesUtils.fromHexString(category).reversed.toList()); bytes.add([bitfield]); @@ -412,6 +413,14 @@ class CashToken { /// Initialized only if the Cash Token has a commitment length. late final String? commitmentInHex = hasCommitment ? BytesUtils.toHexString(commitment) : null; + Map toJson() { + return { + "category": category, + "amount": amount.toString(), + "bitfield": bitfield, + "commitment": BytesUtils.toHexString(commitment), + }; + } @override String toString() { diff --git a/lib/src/crypto/crypto.dart b/lib/src/crypto/crypto.dart index 2af7a21..2d23b95 100644 --- a/lib/src/crypto/crypto.dart +++ b/lib/src/crypto/crypto.dart @@ -1,4 +1,4 @@ -library bitcoin_crypto; +library; import 'package:bitcoin_base/src/bitcoin/script/op_code/constant_lib.dart'; import 'package:blockchain_utils/crypto/quick_crypto.dart'; diff --git a/lib/src/crypto/keypair/ec_private.dart b/lib/src/crypto/keypair/ec_private.dart index ee1b212..d5c5735 100644 --- a/lib/src/crypto/keypair/ec_private.dart +++ b/lib/src/crypto/keypair/ec_private.dart @@ -67,6 +67,17 @@ class ECPrivate { return BytesUtils.toHexString(signature); } + String signSchnorr(List txDigest, + {int sighash = BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL}) { + final btcSigner = BitcoinSigner.fromKeyBytes(toBytes()); + List signatur = btcSigner.signSchnorrTransaction(txDigest, + tapScripts: [], tweak: false); + if (sighash != BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL) { + signatur = [...signatur, sighash]; + } + return BytesUtils.toHexString(signatur); + } + /// sign taproot transaction digest and returns the signature. String signTapRoot(List txDigest, {int sighash = BitcoinOpCodeConst.TAPROOT_SIGHASH_ALL, diff --git a/lib/src/crypto/keypair/ec_public.dart b/lib/src/crypto/keypair/ec_public.dart index b33e29f..26eaed4 100644 --- a/lib/src/crypto/keypair/ec_public.dart +++ b/lib/src/crypto/keypair/ec_public.dart @@ -60,8 +60,8 @@ class ECPublic { /// toSegwitAddress generates a P2WPKH (Pay-to-Witness-Public-Key-Hash) SegWit address /// from the ECPublic key. If 'compressed' is true, the key is in compressed format. - P2wpkhAddress toSegwitAddress({bool compressed = true}) { - final h16 = _toHash160(compressed: compressed); + P2wpkhAddress toSegwitAddress() { + final h16 = _toHash160(); final toHex = BytesUtils.toHexString(h16); return P2wpkhAddress.fromProgram(program: toHex); @@ -124,35 +124,29 @@ class ECPublic { /// toP2wpkhInP2sh generates a P2SH (Pay-to-Script-Hash) address /// wrapping a P2WPKH (Pay-to-Witness-Public-Key-Hash) script derived from the ECPublic key. /// If 'compressed' is true, the key is in compressed format. - P2shAddress toP2wpkhInP2sh({bool compressed = true}) { - final addr = toSegwitAddress(compressed: compressed); + P2shAddress toP2wpkhInP2sh() { + final addr = toSegwitAddress(); return P2shAddress.fromScript( script: addr.toScriptPubKey(), type: P2shAddressType.p2wpkhInP2sh); } /// toP2wshScript generates a P2WSH (Pay-to-Witness-Script-Hash) script /// derived from the ECPublic key. If 'compressed' is true, the key is in compressed format. - Script toP2wshScript({bool compressed = true}) { - return Script(script: [ - 'OP_1', - toHex(compressed: compressed), - "OP_1", - "OP_CHECKMULTISIG" - ]); + Script toP2wshScript() { + return Script(script: ['OP_1', toHex(), "OP_1", "OP_CHECKMULTISIG"]); } /// toP2wshAddress generates a P2WSH (Pay-to-Witness-Script-Hash) address /// from the ECPublic key. If 'compressed' is true, the key is in compressed format. - P2wshAddress toP2wshAddress({bool compressed = true}) { - return P2wshAddress.fromScript( - script: toP2wshScript(compressed: compressed)); + P2wshAddress toP2wshAddress() { + return P2wshAddress.fromScript(script: toP2wshScript()); } /// toP2wshInP2sh generates a P2SH (Pay-to-Script-Hash) address /// wrapping a P2WSH (Pay-to-Witness-Script-Hash) script derived from the ECPublic key. /// If 'compressed' is true, the key is in compressed format. - P2shAddress toP2wshInP2sh({bool compressed = true}) { - final p2sh = toP2wshAddress(compressed: compressed); + P2shAddress toP2wshInP2sh() { + final p2sh = toP2wshAddress(); return P2shAddress.fromScript( script: p2sh.toScriptPubKey(), type: P2shAddressType.p2wshInP2sh); } diff --git a/lib/src/exception/exception.dart b/lib/src/exception/exception.dart index 09652ba..0e031c3 100644 --- a/lib/src/exception/exception.dart +++ b/lib/src/exception/exception.dart @@ -1,7 +1,5 @@ import 'package:blockchain_utils/blockchain_utils.dart'; class DartBitcoinPluginException extends BlockchainUtilsException { - const DartBitcoinPluginException(String message, - {Map? details}) - : super(message, details: details); + const DartBitcoinPluginException(super.message, {super.details}); } diff --git a/lib/src/models/network.dart b/lib/src/models/network.dart index 4250794..4332b41 100644 --- a/lib/src/models/network.dart +++ b/lib/src/models/network.dart @@ -1,9 +1,9 @@ import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/src/exception/exception.dart'; import 'package:bitcoin_base/src/utils/enumerate.dart'; -import 'package:blockchain_utils/bip/bip/bip.dart'; import 'package:blockchain_utils/bip/coin_conf/coin_conf.dart'; import 'package:blockchain_utils/bip/coin_conf/coins_conf.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; /// Abstract class representing a base for UTXO-based cryptocurrency networks. abstract class BasedUtxoNetwork implements Enumerate { diff --git a/lib/src/provider/models/block_cypher/block_cypher_models.dart b/lib/src/provider/models/block_cypher/block_cypher_models.dart index 5d4761b..9d49f11 100644 --- a/lib/src/provider/models/block_cypher/block_cypher_models.dart +++ b/lib/src/provider/models/block_cypher/block_cypher_models.dart @@ -99,7 +99,7 @@ class BlockCypherUtxo { } List toUtxoWithOwner(UtxoAddressDetails owner) { - List utxos = txRefs.map((ref) { + final List utxos = txRefs.map((ref) { return UtxoWithAddress( utxo: ref.toUtxo(owner.address.type), ownerDetails: owner, diff --git a/lib/src/provider/models/config.dart b/lib/src/provider/models/config.dart index 44ebefb..fbbda69 100644 --- a/lib/src/provider/models/config.dart +++ b/lib/src/provider/models/config.dart @@ -24,7 +24,7 @@ class APIConfig { } String getUtxoUrl(String address) { - String baseUrl = url; + final String baseUrl = url; return baseUrl.replaceAll("###", address); } @@ -33,17 +33,17 @@ class APIConfig { } String getTransactionUrl(String transactionId) { - String baseUrl = transaction; + final String baseUrl = transaction; return baseUrl.replaceAll("###", transactionId); } String getTransactionsUrl(String address) { - String baseUrl = transactions; + final String baseUrl = transactions; return baseUrl.replaceAll("###", address); } String getBlockHeight(int blockHaight) { - String baseUrl = blockHeight; + final String baseUrl = blockHeight; return baseUrl.replaceAll("###", "$blockHaight"); } diff --git a/lib/src/provider/models/fee_rate/fee_rate.dart b/lib/src/provider/models/fee_rate/fee_rate.dart index 8064aaa..e3885e5 100644 --- a/lib/src/provider/models/fee_rate/fee_rate.dart +++ b/lib/src/provider/models/fee_rate/fee_rate.dart @@ -50,7 +50,7 @@ class BitcoinFeeRate { BigInt getEstimate(int trSize, {BigInt? customFeeRatePerKb, BitcoinFeeRateType feeRateType = BitcoinFeeRateType.medium}) { - BigInt feeRate = customFeeRatePerKb ?? _feeRatrete(feeRateType); + final BigInt feeRate = customFeeRatePerKb ?? _feeRatrete(feeRateType); final trSizeBigInt = BigInt.from(trSize); return (trSizeBigInt * feeRate) ~/ BigInt.from(1000); } diff --git a/lib/src/provider/models/mempool/mempol_models.dart b/lib/src/provider/models/mempool/mempol_models.dart index b1ea5b5..d194d57 100644 --- a/lib/src/provider/models/mempool/mempol_models.dart +++ b/lib/src/provider/models/mempool/mempol_models.dart @@ -186,7 +186,7 @@ class MempolUtxo implements UTXO { extension MempoolUtxoExtentions on List { List toUtxoWithOwnerList(UtxoAddressDetails owner) { - List utxos = map((e) => UtxoWithAddress( + final List utxos = map((e) => UtxoWithAddress( utxo: BitcoinUtxo( txHash: e.txid, value: e.value, diff --git a/lib/src/provider/models/multisig_script.dart b/lib/src/provider/models/multisig_script.dart index 7a8aed6..3ae38fb 100644 --- a/lib/src/provider/models/multisig_script.dart +++ b/lib/src/provider/models/multisig_script.dart @@ -6,7 +6,7 @@ import 'package:blockchain_utils/blockchain_utils.dart'; /// signers in a multi-signature scheme. A multi-signature signer typically includes /// information about their public key and weight within the scheme. class MultiSignatureSigner { - MultiSignatureSigner._(this.publicKey, this.weight); + MultiSignatureSigner._(this.publicKey, this.weight, this.isCompressed); /// PublicKey returns the public key associated with the signer. final String publicKey; @@ -15,12 +15,15 @@ class MultiSignatureSigner { /// The weight is used to determine the number of signatures required for a valid transaction. final int weight; + final bool isCompressed; + /// creates a new instance of a multi-signature signer with the /// specified public key and weight. factory MultiSignatureSigner( {required String publicKey, required int weight}) { ECPublic.fromHex(publicKey); - return MultiSignatureSigner._(publicKey, weight); + return MultiSignatureSigner._( + publicKey, weight, BtcUtils.isCompressedPubKey(publicKey)); } } @@ -34,6 +37,7 @@ class MultiSignatureAddress { P2shAddressType.p2pkhInP2shwt, P2shAddressType.p2pkhInP2sh32wt ]; + final bool canSelectSegwit; /// Signers is a collection of signers participating in the multi-signature scheme. final List signers; @@ -54,6 +58,10 @@ class MultiSignatureAddress { throw DartBitcoinPluginException( "${network.conf.coinName.name} Bitcoin forks that do not support Segwit. use toP2shAddress"); } + if (!canSelectSegwit) { + throw const DartBitcoinPluginException( + "One of the signer's accounts used an uncompressed public key."); + } return P2wshAddress.fromScript(script: multiSigScript); } @@ -79,10 +87,9 @@ class MultiSignatureAddress { return P2shAddress.fromScript(script: multiSigScript, type: addressType); } - BitcoinBaseAddress fromType({ - required BasedUtxoNetwork network, - required BitcoinAddressType addressType, - }) { + BitcoinBaseAddress fromType( + {required BasedUtxoNetwork network, + required BitcoinAddressType addressType}) { switch (addressType) { case SegwitAddresType.p2wsh: return toP2wshAddress(network: network); @@ -102,17 +109,15 @@ class MultiSignatureAddress { MultiSignatureAddress._( {required this.signers, required this.threshold, - // required this.address, - required this.multiSigScript}); + required this.multiSigScript, + required this.canSelectSegwit}); /// CreateMultiSignatureAddress creates a new instance of a MultiSignatureAddress, representing /// a multi-signature Bitcoin address configuration. It allows you to specify the minimum number /// of required signatures (threshold), provide the collection of signers participating in the /// multi-signature scheme, and specify the address type. - factory MultiSignatureAddress({ - required int threshold, - required List signers, - }) { + factory MultiSignatureAddress( + {required int threshold, required List signers}) { final sumWeight = signers.fold(0, (sum, signer) => sum + signer.weight); if (threshold > 16 || threshold < 1) { @@ -136,6 +141,9 @@ class MultiSignatureAddress { multiSigScript.addAll(['OP_$sumWeight', 'OP_CHECKMULTISIG']); final script = Script(script: multiSigScript); return MultiSignatureAddress._( - signers: signers, threshold: threshold, multiSigScript: script); + signers: signers, + threshold: threshold, + multiSigScript: script, + canSelectSegwit: signers.every((e) => e.isCompressed)); } } diff --git a/lib/src/provider/models/utxo_details.dart b/lib/src/provider/models/utxo_details.dart index 4917e98..606171e 100644 --- a/lib/src/provider/models/utxo_details.dart +++ b/lib/src/provider/models/utxo_details.dart @@ -16,11 +16,16 @@ class UtxoAddressDetails { /// associated with the UTXO owner. It may be null if the UTXO owner is not using a multi-signature scheme. final MultiSignatureAddress? _multiSigAddress; - UtxoAddressDetails({ + UtxoAddressDetails._({ required String publicKey, required this.address, }) : _multiSigAddress = null, _publicKey = publicKey; + factory UtxoAddressDetails( + {required String publicKey, required BitcoinBaseAddress address}) { + ECPublic.fromHex(publicKey); + return UtxoAddressDetails._(publicKey: publicKey, address: address); + } UtxoAddressDetails.multiSigAddress( {required MultiSignatureAddress multiSigAddress, required this.address}) @@ -41,10 +46,19 @@ class UtxoWithAddress { /// OwnerDetails is a UtxoAddressDetails instance containing information about the UTXO owner. final UtxoAddressDetails ownerDetails; - UtxoWithAddress({ - required this.utxo, - required this.ownerDetails, - }); + const UtxoWithAddress._( + {required this.utxo, + required this.ownerDetails, + required this.isCompressed}); + factory UtxoWithAddress( + {required BitcoinUtxo utxo, required UtxoAddressDetails ownerDetails}) { + return UtxoWithAddress._( + utxo: utxo, + ownerDetails: ownerDetails, + isCompressed: ownerDetails._publicKey != null && !utxo.isSegwit + ? BtcUtils.isCompressedPubKey(ownerDetails._publicKey) + : true); + } ECPublic public() { if (isMultiSig()) { @@ -58,6 +72,8 @@ class UtxoWithAddress { return ECPublic.fromHex(ownerDetails._publicKey!); } + final bool isCompressed; + bool isMultiSig() { return ownerDetails._multiSigAddress != null; } @@ -96,10 +112,7 @@ class BitcoinOutput implements BitcoinSpendableBaseOutput { @override final BigInt value; // final CashToken? token; - BitcoinOutput({ - required this.address, - required this.value, - }); + BitcoinOutput({required this.address, required this.value}); @override TxOutput get toOutput => @@ -113,10 +126,7 @@ class BitcoinScriptOutput implements BitcoinBaseOutput { /// The value (amount) of the Bitcoin output. final BigInt value; - const BitcoinScriptOutput({ - required this.script, - required this.value, - }); + const BitcoinScriptOutput({required this.script, required this.value}); /// Convert the custom script output to a standard TxOutput. @override @@ -134,12 +144,11 @@ class BitcoinTokenOutput implements BitcoinSpendableBaseOutput { final BigInt value; final CashToken token; final String? utxoHash; - BitcoinTokenOutput({ - required this.address, - required this.value, - required this.token, - this.utxoHash, - }); + BitcoinTokenOutput( + {required this.address, + required this.value, + required this.token, + this.utxoHash}); /// Convert the custom script output to a standard TxOutput. @override @@ -162,11 +171,7 @@ class BitcoinBurnableOutput extends BitcoinBaseOutput { /// The value (amount) of the burnable output (optional only for token with hasAmount flags). final BigInt? value; - BitcoinBurnableOutput({ - required this.categoryID, - this.utxoHash, - this.value, - }); + BitcoinBurnableOutput({required this.categoryID, this.utxoHash, this.value}); @override TxOutput get toOutput => throw UnimplementedError(); @@ -194,30 +199,45 @@ class BitcoinUtxo { /// BlockHeight represents the block height at which this UTXO was confirmed. final int? blockHeight; - BitcoinUtxo({ - required this.txHash, - required this.value, - required this.vout, - required this.scriptType, - this.blockHeight, - this.token, - }); + BitcoinUtxo._( + {required this.txHash, + required this.value, + required this.vout, + required this.scriptType, + this.blockHeight, + this.token, + required this.isP2tr, + required this.isP2shSegwit, + required this.isSegwit}); + factory BitcoinUtxo( + {required String txHash, + required BigInt value, + required int vout, + required BitcoinAddressType scriptType, + int? blockHeight, + CashToken? token}) { + final bool isP2shSegwit = scriptType == P2shAddressType.p2wpkhInP2sh || + scriptType == P2shAddressType.p2wshInP2sh; + return BitcoinUtxo._( + txHash: txHash, + value: value, + blockHeight: blockHeight, + token: token, + vout: vout, + scriptType: scriptType, + isP2tr: scriptType == SegwitAddresType.p2tr, + isP2shSegwit: isP2shSegwit, + isSegwit: isP2shSegwit || scriptType.isSegwit); + } /// check if utxos is p2tr - bool isP2tr() { - return scriptType == SegwitAddresType.p2tr; - } + final bool isP2tr; /// check if utxos is segwit - bool isSegwit() { - return scriptType.isSegwit || isP2shSegwit(); - } + final bool isSegwit; - /// checl if utxos is p2sh neasted segwit - bool isP2shSegwit() { - return scriptType == P2shAddressType.p2wpkhInP2sh || - scriptType == P2shAddressType.p2wshInP2sh; - } + /// check if utxos is p2sh neasted segwit + final bool isP2shSegwit; /// convert utxos to transaction input with specify sequence like ReplaceByeFee (4Bytes) TxInput toInput([List? sequence]) { @@ -235,7 +255,7 @@ extension Calculate on List { /// sum of utxos network values BigInt sumOfUtxosValue() { BigInt sum = BigInt.zero; - for (var utxo in this) { + for (final utxo in this) { sum += utxo.utxo.value; } return sum; @@ -244,7 +264,7 @@ extension Calculate on List { /// sum of utxos cash token (FToken) amounts Map sumOfTokenUtxos() { final Map tokens = {}; - for (var utxo in this) { + for (final utxo in this) { if (utxo.utxo.token == null) continue; final token = utxo.utxo.token!; if (!token.hasAmount) continue; diff --git a/lib/src/provider/service/electrum/exception.dart b/lib/src/provider/service/electrum/exception.dart deleted file mode 100644 index 2b1e90e..0000000 --- a/lib/src/provider/service/electrum/exception.dart +++ /dev/null @@ -1,51 +0,0 @@ -// import 'package:blockchain_utils/blockchain_utils.dart'; - -// /// Exception class representing errors encountered during RPC (Remote Procedure Call) requests. -// class ElectrumRPCException implements BlockchainUtilsException { -// /// Constructs an instance of [ElectrumRPCException] with the provided details. -// const ElectrumRPCException( -// {required this.message, -// required this.code, -// required this.data, -// required this.request}); - -// /// The error code associated with the error. -// final int code; - -// /// A human-readable error message describing the issue. -// @override -// final String message; - -// /// Additional data providing more context about the error (nullable). -// final dynamic data; - -// /// The original request that triggered the error. -// final Map request; - -// @override -// String toString() { -// return 'RPC Error: Received code $code with message "$message".'; -// } - -// /// Converts the exception details to a JSON-formatted representation. -// Map toJson() { -// final error = {"message": message, "code": code}; -// if (data != null) { -// error["data"] = data; -// } -// final toJson = { -// "jsonrpc": "2.0", -// "error": error, -// }; -// if (request.isNotEmpty) { -// if (request.containsKey("id")) { -// toJson["id"] = request["id"]; -// } -// if (request.containsKey("params") && request.containsKey("method")) { -// toJson["params"] = request["params"]; -// toJson["method"] = request["method"]; -// } -// } -// return toJson; -// } -// } diff --git a/lib/src/provider/service/electrum/params.dart b/lib/src/provider/service/electrum/params.dart index cb245c2..da5a07a 100644 --- a/lib/src/provider/service/electrum/params.dart +++ b/lib/src/provider/service/electrum/params.dart @@ -40,7 +40,7 @@ abstract class ElectrumRequest } ElectrumRequestDetails toRequest(int requestId) { - List inJson = toJson(); + final List inJson = toJson(); inJson.removeWhere((v) => v == null); final params = { "jsonrpc": "2.0", diff --git a/lib/src/provider/transaction_builder/forked_transaction_builder.dart b/lib/src/provider/transaction_builder/forked_transaction_builder.dart index 542c208..1c7c55a 100644 --- a/lib/src/provider/transaction_builder/forked_transaction_builder.dart +++ b/lib/src/provider/transaction_builder/forked_transaction_builder.dart @@ -49,6 +49,9 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { throw const DartBitcoinPluginException( "invalid network. use ForkedTransactionBuilder for BitcoinCashNetwork and BSVNetwork otherwise use BitcoinTransactionBuilder"); } + + /// validate every address is related to network + /// exception if failed. for (final i in utxosInfo) { i.ownerDetails.address.toAddress(network); } @@ -99,9 +102,9 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { /// and doesn't generate errors when determining the transaction size. isFakeTransaction: true); - /// 71 bytes (64 byte signature, 6-7 byte Der encoding length) + /// 72 bytes (64 byte signature, 6-7 byte Der encoding length + sighash) const String fakeECDSASignatureBytes = - "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; + "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; final transaction = transactionBuilder .buildTransaction((trDigest, utxo, multiSigPublicKey, int sighash) { @@ -131,20 +134,21 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { } final senderPub = utxo.public(); + final bool isCompressed = utxo.isCompressed; switch (utxo.utxo.scriptType) { case PubKeyAddressType.p2pk: case P2shAddressType.p2pkInP2sh: case P2shAddressType.p2pkInP2shwt: case P2shAddressType.p2pkInP2sh32: case P2shAddressType.p2pkInP2sh32wt: - return senderPub.toRedeemScript(); + return senderPub.toRedeemScript(compressed: isCompressed); case P2pkhAddressType.p2pkh: case P2shAddressType.p2pkhInP2sh: case P2shAddressType.p2pkhInP2sh32: case P2pkhAddressType.p2pkhwt: case P2shAddressType.p2pkhInP2shwt: case P2shAddressType.p2pkhInP2sh32wt: - return senderPub.toAddress().toScriptPubKey(); + return senderPub.toAddress(compressed: isCompressed).toScriptPubKey(); default: throw DartBitcoinPluginException( "${utxo.utxo.scriptType} does not sudpport on ${network.conf.coinName.name}"); @@ -167,18 +171,18 @@ class ForkedTransactionBuilder implements BasedBitcoinTransacationBuilder { /// Returns: /// - List: representing the transaction digest to be used for signing the input. List _generateTransactionDigest( - Script scriptPubKeys, - int input, - UtxoWithAddress utox, - BtcTransaction transaction, - ) { + {required Script scriptPubKeys, + required int input, + required UtxoWithAddress utox, + required BtcTransaction transaction, + int sighash = + BitcoinOpCodeConst.SIGHASH_ALL | BitcoinOpCodeConst.SIGHASH_FORKED}) { return transaction.getTransactionSegwitDigit( txInIndex: input, script: scriptPubKeys, amount: utox.utxo.value, token: utox.utxo.token, - sighash: - BitcoinOpCodeConst.SIGHASH_ALL | BitcoinOpCodeConst.SIGHASH_FORKED); + sighash: sighash); } /// buildP2wshOrP2shScriptSig constructs and returns a script signature (represented as a List of strings) @@ -206,23 +210,29 @@ that demonstrate the right to spend the bitcoins associated with the correspondi */ List _buildUnlockingScript(String signedDigest, UtxoWithAddress utx) { final senderPub = utx.public(); + final bool isCompressed = utx.isCompressed; switch (utx.utxo.scriptType) { case PubKeyAddressType.p2pk: return [signedDigest]; case P2pkhAddressType.p2pkh: case P2pkhAddressType.p2pkhwt: - return [signedDigest, senderPub.toHex()]; + return [signedDigest, senderPub.toHex(compressed: isCompressed)]; case P2shAddressType.p2pkhInP2sh: case P2shAddressType.p2pkhInP2shwt: case P2shAddressType.p2pkhInP2sh32: case P2shAddressType.p2pkhInP2sh32wt: - final script = senderPub.toAddress().toScriptPubKey(); - return [signedDigest, senderPub.toHex(), script.toHex()]; + final script = + senderPub.toAddress(compressed: isCompressed).toScriptPubKey(); + return [ + signedDigest, + senderPub.toHex(compressed: isCompressed), + script.toHex() + ]; case P2shAddressType.p2pkInP2sh: case P2shAddressType.p2pkInP2shwt: case P2shAddressType.p2pkInP2sh32: case P2shAddressType.p2pkInP2sh32wt: - final script = senderPub.toRedeemScript(); + final script = senderPub.toRedeemScript(compressed: isCompressed); return [signedDigest, script.toHex()]; default: throw DartBitcoinPluginException( @@ -247,7 +257,8 @@ that demonstrate the right to spend the bitcoins associated with the correspondi }, ); } - List inputs = sortedUtxos.map((e) => e.utxo.toInput()).toList(); + final List inputs = + sortedUtxos.map((e) => e.utxo.toInput()).toList(); if (enableRBF && inputs.isNotEmpty) { inputs[0] = inputs[0] .copyWith(sequence: BitcoinOpCodeConst.REPLACE_BY_FEE_SEQUENCE); @@ -308,7 +319,7 @@ be retrieved by anyone who examines the blockchain's history. /// Total token amount to spend. Map _sumTokenOutputAmounts(List outputs) { final Map tokens = {}; - for (var utxo in outputs) { + for (final utxo in outputs) { if (utxo.cashToken == null) continue; final token = utxo.cashToken!; if (!token.hasAmount) continue; @@ -327,7 +338,8 @@ be retrieved by anyone who examines the blockchain's history. required BigInt sumAmountsWithFee, required BigInt sumUtxoAmount, required BigInt sumOutputAmounts}) { - if (!isFakeTransaction && sumAmountsWithFee != sumUtxoAmount) { + if (isFakeTransaction) return; + if (sumAmountsWithFee != sumUtxoAmount) { throw DartBitcoinPluginException('Sum value of utxo not spending', details: { "inputAmount": sumUtxoAmount, @@ -335,56 +347,54 @@ be retrieved by anyone who examines the blockchain's history. "outputAmount": sumOutputAmounts }); } - if (!isFakeTransaction) { - /// sum of token amounts - final sumOfTokenUtxos = utxos.sumOfTokenUtxos(); - - /// sum of token output amounts - final sumTokenOutputAmouts = _sumTokenOutputAmounts(outputs); - for (final i in sumOfTokenUtxos.entries) { - if (sumTokenOutputAmouts[i.key] != i.value) { - BigInt amount = sumTokenOutputAmouts[i.key] ?? BigInt.zero; - amount += outPuts - .whereType() - .where((element) => element.categoryID == i.key) - .fold( - BigInt.zero, - (previousValue, element) => - previousValue + (element.value ?? BigInt.zero)); - - if (amount != i.value) { - throw DartBitcoinPluginException( - 'Sum token value of UTXOs not spending. use BitcoinBurnableOutput if you want to burn tokens.', - details: { - "token": i.key, - "inputValue": i.value, - "outputValue": amount - }); - } - } - } - for (final i in utxos) { - if (i.utxo.token != null) { - final token = i.utxo.token!; - if (token.hasAmount) continue; - if (!token.hasNFT) continue; - final hasOneoutput = outPuts.whereType().any( - (element) => - element.utxoHash == i.utxo.txHash && - element.token.category == token.category); - if (hasOneoutput) continue; - final hasBurnableOutput = outPuts - .whereType() - .any((element) => - element.utxoHash == i.utxo.txHash && - element.categoryID == token.category); - if (hasBurnableOutput) continue; + + /// sum of token amounts + final sumOfTokenUtxos = utxos.sumOfTokenUtxos(); + + /// sum of token output amounts + final sumTokenOutputAmouts = _sumTokenOutputAmounts(outputs); + for (final i in sumOfTokenUtxos.entries) { + if (sumTokenOutputAmouts[i.key] != i.value) { + BigInt amount = sumTokenOutputAmouts[i.key] ?? BigInt.zero; + amount += outPuts + .whereType() + .where((element) => element.categoryID == i.key) + .fold( + BigInt.zero, + (previousValue, element) => + previousValue + (element.value ?? BigInt.zero)); + + if (amount != i.value) { throw DartBitcoinPluginException( - 'Some NFTs in the inputs lack the corresponding spending in the outputs. If you intend to burn tokens, consider utilizing the BitcoinBurnableOutput.', - details: {"category id": token.category}); + 'Sum token value of UTXOs not spending. use BitcoinBurnableOutput if you want to burn tokens.', + details: { + "token": i.key, + "inputValue": i.value, + "outputValue": amount + }); } } } + for (final i in utxos) { + final token = i.utxo.token; + if (token != null && token.hasNFT) { + if (token.hasAmount) continue; + final hasOneoutput = outPuts.whereType().any( + (element) => + element.utxoHash == i.utxo.txHash && + element.token.category == token.category); + if (hasOneoutput) continue; + final hasBurnableOutput = outPuts + .whereType() + .any((element) => + element.utxoHash == i.utxo.txHash && + element.categoryID == token.category); + if (hasBurnableOutput) continue; + throw DartBitcoinPluginException( + 'Some NFTs in the inputs lack the corresponding spending in the outputs. If you intend to burn tokens, consider utilizing the BitcoinBurnableOutput.', + details: {"category id": token.category}); + } + } } @override @@ -419,7 +429,7 @@ be retrieved by anyone who examines the blockchain's history. break; } } - if (sumMultiSigWeight != multiSigAddress.threshold) { + if (sumMultiSigWeight < multiSigAddress.threshold) { throw const DartBitcoinPluginException( "some multisig signature does not exist"); } @@ -460,9 +470,11 @@ be retrieved by anyone who examines the blockchain's history. sumOutputAmounts: sumOutputAmounts); /// create new transaction with inputs and outputs and isSegwit transaction or not + // BtcTransaction transaction = BtcTransaction.fromRaw( + // "0200000001b8249c5a6cf8f24815377222e4c2bab32154151c367111134ef7ce11c651e4c600000000fd5b0300483045022100b4e774511598efbdf256aaa127f2104c5f25e29311936a75034857901c72fa180220232410ec31b9809b0293a7a2c8c0ade53ca4bdde064ac1baaa27d545678891b84147304402203014d1beaa73fc289d0201bff61a40d1e853103d624fb8520cc6b4afea00d3c10220643eed02e2830b7f751b7355a889e4ab4c02d01df0ca71621d4f5affe5cb3a5d41483045022100bf73bd48dcef332924c7578ff7a5b9d3817ecdb2ad2a4148f56069705365427c022044ce67ca87c3e1309ba43fcb3c23a3618ba0dbd44a802c22efab78b42b9a8e3e41483045022100a3d90b8f959850f9e2655b38e61a8e5323a363803bd62ab0560509e9cd675dae022064ba98f689c21f66e0839660bf15c7a0c44e0e65384d82996a4f3b73a55daef141473044022033128a7572b0bc7d22c6cd2080a882b7e03b15167b1d88b6405fa0131ab6254602207d960421e8a1a2e85431b6398a9af742fb492c967c3acac9fd38d82d7c54756e41483045022100c00232655b098d529deea53dcfffa5b25dc4a2015a52b1b587ae73c813a8d4a502201d100762ddb4779d1c484790a1b38cf369f634ee33541d8ec0ec45c2470141ba4147304402201503e2e7bab40cd5a083c0e03506ad1c260d17b821a142be2591e05fa5bdc86d02201d0280a2fc78a2af4bf7f1245bd8bf3384ada22098ba84645958ff1715655630414730440220090757399dc9f989ff1a7d998af385cd9490fe07c2c216b54967d4138067312a02204c3bb010902d72e90a1133cc8ae34cb629e139863f62ab259f25e5cac2125779414d13015821030f0fb9a244ad31a369ee02b7abfbbb0bfa3812b9a39ed93346d03d67d412d17721022f1b310f4c065331bc0d79ba4661bb9822d67d7c4a1b0a1892e1fd0cd23aa68d210299c2aa85d2b21a62f396907a802a58e521dafd5bddaccbd72786eea189bc4dc921021a7a569e91dbf60581509c7fc946d1003b60c7dee85299538db6353538d595742103a92c9b7cac68758de5783ed8e5123598e4ad137091e42987d3bad8a08e35bf3d21034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa21036360e856310ce5d294e8be33fc807077dc56ac80d95d9cd4ddbd21325eff73f721031d16453b3ab3132acb0a5bc16cc49690d819a585267a15cd5a064e2a0ad4059958ae0000000001f0780f000000000017a914e5d65da9624e4754827edb627fe31d4c75954a388700000000"); + // transaction = transaction.copyWith(inputs: inputs); final transaction = BtcTransaction(inputs: inputs, outputs: outputs, hasSegwit: false); - const int sighash = BitcoinOpCodeConst.SIGHASH_ALL | BitcoinOpCodeConst.SIGHASH_FORKED; @@ -474,8 +486,12 @@ be retrieved by anyone who examines the blockchain's history. final script = _buildInputScriptPubKeys(indexUtxo); /// We generate transaction digest for current input - final digest = - _generateTransactionDigest(script, i, indexUtxo, transaction); + final digest = _generateTransactionDigest( + scriptPubKeys: script, + input: i, + utox: indexUtxo, + transaction: transaction, + sighash: sighash); /// handle multisig address if (indexUtxo.isMultiSig()) { @@ -502,7 +518,7 @@ be retrieved by anyone who examines the blockchain's history. break; } } - if (sumMultiSigWeight != multiSigAddress.threshold) { + if (sumMultiSigWeight < multiSigAddress.threshold) { throw const DartBitcoinPluginException( "some multisig signature does not exist"); } @@ -516,7 +532,8 @@ be retrieved by anyone who examines the blockchain's history. final sig = sign(digest, indexUtxo, indexUtxo.public().toHex(), sighash); _addScripts(input: inputs[i], signatures: [sig], utxo: indexUtxo); } - + final ea = BtcTransaction.fromRaw(transaction.serialize()); + assert(ea.serialize() == transaction.serialize(), transaction.serialize()); return transaction; } @@ -564,8 +581,12 @@ be retrieved by anyone who examines the blockchain's history. final script = _buildInputScriptPubKeys(indexUtxo); /// We generate transaction digest for current input - final digest = - _generateTransactionDigest(script, i, indexUtxo, transaction); + final digest = _generateTransactionDigest( + scriptPubKeys: script, + input: i, + utox: indexUtxo, + transaction: transaction, + sighash: sighash); /// handle multisig address if (indexUtxo.isMultiSig()) { @@ -578,6 +599,7 @@ be retrieved by anyone who examines the blockchain's history. /// now we need sign the transaction digest final sig = await sign(digest, indexUtxo, multiSigAddress.signers[ownerIndex].publicKey, sighash); + if (sig.isEmpty) continue; for (int weight = 0; weight < multiSigAddress.signers[ownerIndex].weight; @@ -587,12 +609,13 @@ be retrieved by anyone who examines the blockchain's history. } mutlsiSigSignatures.add(sig); } + sumMultiSigWeight += multiSigAddress.signers[ownerIndex].weight; if (sumMultiSigWeight >= multiSigAddress.threshold) { break; } } - if (sumMultiSigWeight != multiSigAddress.threshold) { + if (sumMultiSigWeight < multiSigAddress.threshold) { throw const DartBitcoinPluginException( "some multisig signature does not exist"); } @@ -607,20 +630,17 @@ be retrieved by anyone who examines the blockchain's history. await sign(digest, indexUtxo, indexUtxo.public().toHex(), sighash); _addScripts(input: inputs[i], signatures: [sig], utxo: indexUtxo); } - return transaction; } - void _addScripts({ - required UtxoWithAddress utxo, - required TxInput input, - required List signatures, - }) { + void _addScripts( + {required UtxoWithAddress utxo, + required TxInput input, + required List signatures}) { /// ok we signed, now we need unlocking script for this input final scriptSig = utxo.isMultiSig() ? _buildMiltisigUnlockingScript(signatures, utxo) : _buildUnlockingScript(signatures.first, utxo); - input.scriptSig = Script(script: scriptSig); } } diff --git a/lib/src/provider/transaction_builder/transaction_builder.dart b/lib/src/provider/transaction_builder/transaction_builder.dart index 07587c6..cdc8e0f 100644 --- a/lib/src/provider/transaction_builder/transaction_builder.dart +++ b/lib/src/provider/transaction_builder/transaction_builder.dart @@ -116,13 +116,13 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { const String fakeSchnorSignaturBytes = "01010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; - /// 71 bytes (64 byte signature, 6-7 byte Der encoding length) + /// 72 bytes (64 byte signature, 6-7 byte Der encoding length) const String fakeECDSASignatureBytes = - "0101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; + "010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; final transaction = transactionBuilder .buildTransaction((trDigest, utxo, multiSigPublicKey, int sighash) { - if (utxo.utxo.isP2tr()) { + if (utxo.utxo.isP2tr) { return fakeSchnorSignaturBytes; } else { return fakeECDSASignatureBytes; @@ -145,7 +145,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { /// - bool: True if at least one UTXO in the list is a SegWit UTXO, false otherwise. bool _hasSegwit() { for (final element in utxosInfo) { - if (element.utxo.isSegwit()) { + if (element.utxo.isSegwit) { return true; } } @@ -160,7 +160,7 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { /// - bool: True if at least one UTXO in the list is a P2TR UTXO, false otherwise. bool _hasTaproot() { for (final element in utxosInfo) { - if (element.utxo.isP2tr()) { + if (element.utxo.isP2tr) { return true; } } @@ -201,14 +201,16 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { final senderPub = utxo.public(); switch (utxo.utxo.scriptType) { case PubKeyAddressType.p2pk: - return senderPub.toRedeemScript(); + return senderPub.toRedeemScript(compressed: utxo.isCompressed); case SegwitAddresType.p2wsh: if (isTaproot) { return senderPub.toP2wshAddress().toScriptPubKey(); } return senderPub.toP2wshScript(); case P2pkhAddressType.p2pkh: - return senderPub.toAddress().toScriptPubKey(); + return senderPub + .toAddress(compressed: utxo.isCompressed) + .toScriptPubKey(); case SegwitAddresType.p2wpkh: if (isTaproot) { return senderPub.toSegwitAddress().toScriptPubKey(); @@ -218,9 +220,13 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { return senderPub.toTaprootAddress().toScriptPubKey(); case P2shAddressType.p2pkhInP2sh: if (isTaproot) { - return senderPub.toP2pkhInP2sh().toScriptPubKey(); + return senderPub + .toP2pkhInP2sh(compressed: utxo.isCompressed) + .toScriptPubKey(); } - return senderPub.toAddress().toScriptPubKey(); + return senderPub + .toAddress(compressed: utxo.isCompressed) + .toScriptPubKey(); case P2shAddressType.p2wpkhInP2sh: if (isTaproot) { return senderPub.toP2wpkhInP2sh().toScriptPubKey(); @@ -233,9 +239,11 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { return senderPub.toP2wshScript(); case P2shAddressType.p2pkInP2sh: if (isTaproot) { - return senderPub.toP2pkInP2sh().toScriptPubKey(); + return senderPub + .toP2pkInP2sh(compressed: utxo.isCompressed) + .toScriptPubKey(); } - return senderPub.toRedeemScript(); + return senderPub.toRedeemScript(compressed: utxo.isCompressed); } throw const DartBitcoinPluginException("invalid bitcoin address type"); } @@ -262,8 +270,8 @@ class BitcoinTransactionBuilder implements BasedBitcoinTransacationBuilder { BtcTransaction transaction, List taprootAmounts, List