diff --git a/example/helper.dart b/example/helper.dart deleted file mode 100644 index 5aa9c57..0000000 --- a/example/helper.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'dart:convert'; -import 'transactions/utxo.dart'; -import 'package:http/http.dart' as http; - -class RPCError implements Exception { - const RPCError(this.errorCode, this.message, this.data, - {required this.request}); - - final int errorCode; - final String message; - final dynamic data; - final Map request; - - @override - String toString() { - return 'RPCError: got code $errorCode with msg "$message".'; - } -} - -Map parseError( - Map data, Map request) { - final error = data['error']; - if (error == null) return data; - final code = (error['code'] ?? 0); - final message = error['message']; - final errorData = error['data']; - throw RPCError(code, message, errorData, request: request); -} - -class BTCRpcHelper { - BTCRpcHelper( - - ///The link is for testing, it might not work, please use your RPC service - {this.url = - "https://serene-wild-dew.btc-testnet.discover.quiknode.pro/33a88cd7b9e1515949682b452f10c134ae4c2959/", - Map? header}) - : _header = header ?? - { - 'Content-Type': 'application/json', - 'x-api-key': "0fd2f4ca-25ac-4e19-a6c2-e66696ba4c8b" - }; - final String url; - final Map _header; - - int _currentRequestId = 1; - - Future call( - String function, [ - List? params, - ]) async { - http.Client client = http.Client(); - try { - params ??= []; - final payload = { - 'jsonrpc': '2.0', - 'method': function, - 'params': params, - 'id': _currentRequestId++, - }; - - final response = await client - .post( - Uri.parse(url), - headers: _header, - body: json.encode(payload), - ) - .timeout(const Duration(seconds: 30)); - final data = parseError(json.decode(response.body), payload); - - final result = data['result']; - - return result; - } finally { - client.close(); - } - } - - Future getSmartEstimate() async { - final data = await call("estimatesmartfee", [2, "CONSERVATIVE"]); - return priceToBtcUnit(data['feerate']); - } - - Future sendRawTransaction(String txDigit) async { - final data = await call("sendrawtransaction", [txDigit]); - return data!; - } - - ///This method is for testing, it may not work, please use your RPC service - Future> getUtxo(String address, - {String? url, - Map header = const { - 'Content-Type': 'application/json', - 'api-key': "dc0cdbc2-d3fc-4ae8-ae45-0a44bc28b5f9" - }}) async { - http.Client client = http.Client(); - try { - String u = - url ?? "https://btcbook-testnet.nownodes.io/api/v2/utxo/$address"; - - final response = await client - .get( - Uri.parse(u), - headers: header, - ) - .timeout(const Duration(seconds: 30)); - final data = json.decode(response.body) as List; - return data.map((e) => UTXO.fromJson(e)).toList(); - } finally { - client.close(); - } - } -} - -/// This converter is not accurate -BigInt priceToBtcUnit(double price, {double decimal = 1e8}) { - final dec = price * decimal; - return BigInt.from(dec); -} diff --git a/example/main.dart b/example/main.dart index b0d5f1c..140ae4b 100644 --- a/example/main.dart +++ b/example/main.dart @@ -1,374 +1,3 @@ -import 'package:bitcoin_base/bitcoin_base.dart'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:flutter_test/flutter_test.dart'; -import './transactions/spend_p2kh_to_p2k.dart'; -import './transactions/spend_p2kh_to_p2kh.dart'; -import './transactions/spend_p2pk_to_p2pkh.dart'; -import './transactions/spend_p2pkh_to_p2sh.dart'; -import './transactions/spend_p2pkh_to_p2wpkh.dart'; -import './transactions/spend_p2sh_to_p2k.dart'; -import './transactions/spend_p2sh_to_p2pkh.dart'; -import './transactions/spend_p2sh_to_p2sh.dart'; -import './transactions/spend_p2sh_to_p2wkh.dart'; -import './transactions/spend_p2wkh_to_p2k.dart'; -import './transactions/spend_p2wkh_to_p2wkh.dart'; -import './transactions/spend_p2wkh_to_p2kh.dart'; -import './transactions/spend_p2wkh_to_p2sh.dart'; -import 'helper.dart'; +// ignore_for_file: unused_local_variable void main() {} - -final BTCRpcHelper testRpc = BTCRpcHelper(); - -/// spend p2wkh utxo -Future testSpendP2wkhToP2wkh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toSegwitAddress(); - final utxo = await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - - final BigInt estimateFee = await testRpc - .getSmartEstimate() - .catchError((e) => priceToBtcUnit(0.00001)); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); - final changeAddress = - recPub.toSegwitAddress(); // change address is p2pk instead of p2wpkh - - // return; - final digit = spendp2wkh( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.one, - changeAddress: changeAddress); - // return; - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2wkh utxo -Future testSpendP2wkhToP2kh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toSegwitAddress(); - final utxo = await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toAddress(); - final digit = spendP2wkhToP2kh( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.one); - // return; - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2kh utxo -Future testSpendp2khToP2kh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toAddress(); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toAddress(); - final digit = spendP2pkhToP2pkh( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: [utxo.first], - estimateFee: estimateFee, - value: null); - - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2kh utxo -Future testSpendp2kToP2kh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toAddress(); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toAddress(); - final digit = spendP2pkToP2pkh( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.zero); - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2kh utxo -Future testSpendp2khToP2wkh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toAddress(); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); - final digit = spendP2khToP2wkh( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: null); - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2kh utxo -Future testSpendp2khToP2sh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toAddress(); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final digit = spendP2khToP2sh( - networkType: NetworkInfo.TESTNET, - receiver: recPub, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: null); - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2wpkh utxo -Future testSpendp2wpkhToP2sh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toSegwitAddress(); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - // return; - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - - final digit = spendP2wkhToP2sh( - networkType: NetworkInfo.TESTNET, - receiver: recPub, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.from(1056000)); - - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2sh utxo -Future testSpendp2shToP2sh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = P2shAddress(script: addr.toRedeemScript()); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - - final digit = spendP2shToP2sh( - networkType: NetworkInfo.TESTNET, - receiver: recPub, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.one); - - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2sh utxo -Future testSpendp2shToP2kh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = P2shAddress(script: addr.toRedeemScript()); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - // return; - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toAddress(); - - final digit = spendP2shToP2pkh( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.zero); - - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2sh utxo -Future testSpendp2shToP2wpkh(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = P2shAddress(script: addr.toRedeemScript()); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - // return; - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toSegwitAddress(); - - final digit = spendP2shToP2wpkh( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.zero); - - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2pkh utxo -Future testSpendp2pkhToP2pk(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toAddress(); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toP2pkAddress(); - - final digit = spendP2pkhToP2pk( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: null); - - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2wpkh utxo -Future testSpendp2wpkhToP2k(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = addr.toSegwitAddress(); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - // return; - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toP2pkAddress(); - - final digit = spendP2wkhToP2pk( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.one); - - await testRpc.sendRawTransaction(digit.$1); -} - -/// spend p2sh utxo -Future testSpendp2shToP2k(ECPrivate sWallet, ECPrivate rWallet) async { - final addr = sWallet.getPublic(); - final sender = P2shAddress(script: addr.toRedeemScript()); - final utxo = (await testRpc.getUtxo(sender.toAddress(NetworkInfo.TESTNET))) - ..sort((a, b) => b.value.compareTo(a.value)); - if (utxo.isEmpty) { - throw Exception( - "account does not have any unspent transaction or mybe no confirmed"); - } - final BigInt estimateFee = await testRpc.getSmartEstimate(); - final prive = sWallet; - final recPub = rWallet.getPublic(); - final receiver = recPub.toP2pkAddress(); - - final digit = spendP2shToP2pk( - networkType: NetworkInfo.TESTNET, - receiver: receiver, - senderPub: addr, - sign: prive.signInput, - utxo: utxo, - estimateFee: estimateFee, - value: BigInt.from(105999)); - - await testRpc.sendRawTransaction(digit.$1); -} diff --git a/example/spend_p2kh_to_p2k.dart b/example/spend_p2kh_to_p2k.dart deleted file mode 100644 index 98ae2f2..0000000 --- a/example/spend_p2kh_to_p2k.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2pkhToP2pk({ - required P2pkAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2pkhAddress? changeAddress, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, - script: Script(script: senderAddress.toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - - if (trSize == null) { - return spendP2pkhToP2pk( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2kh_to_p2kh.dart b/example/spend_p2kh_to_p2kh.dart deleted file mode 100644 index edf6f8a..0000000 --- a/example/spend_p2kh_to_p2kh.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2pkhToP2pkh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, - script: Script(script: senderAddress.toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - - if (trSize == null) { - return spendP2pkhToP2pkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2pk_to_p2pkh.dart b/example/spend_p2pk_to_p2pkh.dart deleted file mode 100644 index 4ce53f3..0000000 --- a/example/spend_p2pk_to_p2pkh.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2pkToP2pkh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final sc = senderPub.toRedeemScript(); - final txDigit = - tx.getTransactionDigest(txInIndex: i, script: sc, sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx]); - } - - if (trSize == null) { - return spendP2pkToP2pkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2pk_to_p2sh.dart b/example/spend_p2pk_to_p2sh.dart deleted file mode 100644 index 822f548..0000000 --- a/example/spend_p2pk_to_p2sh.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2pkToP2sh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final sc = senderPub.toRedeemScript(); - final txDigit = - tx.getTransactionDigest(txInIndex: i, script: sc, sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx]); - } - - if (trSize == null) { - return spendP2pkToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2pkh_to_p2sh.dart b/example/spend_p2pkh_to_p2sh.dart deleted file mode 100644 index d657dc6..0000000 --- a/example/spend_p2pkh_to_p2sh.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; -// from segwit to P2pkhAddress -// need segwit pub key -// output pay-to-pubkey-hash -// input pay-to-witness-pubkey-hash - -(String, String) spendP2khToP2sh({ - required ECPublic receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: receiver.toRedeemScript().toP2shScriptPubKey()) - ]; - if (needChangeTx > BigInt.zero) { - final senderAddress = senderPub.toAddress(); - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int b = 0; b < txin.length; b++) { - final txDigit = tx.getTransactionDigest( - txInIndex: b, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit, sigHash: sighash); - txin[b].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - - if (trSize == null) { - return spendP2khToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2pkh_to_p2wpkh.dart b/example/spend_p2pkh_to_p2wpkh.dart deleted file mode 100644 index 059cb4e..0000000 --- a/example/spend_p2pkh_to_p2wpkh.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2khToP2wkh({ - required P2wpkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - final senderAddress = senderPub.toAddress(); - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int b = 0; b < txin.length; b++) { - final txDigit = tx.getTransactionDigest( - txInIndex: b, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit, sigHash: sighash); - txin[b].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - if (trSize == null) { - return spendP2khToP2wkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2sh_to_p2k.dart b/example/spend_p2sh_to_p2k.dart deleted file mode 100644 index 985c2a9..0000000 --- a/example/spend_p2sh_to_p2k.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2shToP2pk({ - required P2pkAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit, sigHash: sighash); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2pk( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2sh_to_p2pkh.dart b/example/spend_p2sh_to_p2pkh.dart deleted file mode 100644 index 1c6166c..0000000 --- a/example/spend_p2sh_to_p2pkh.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2shToP2pkh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2pkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2sh_to_p2sh.dart b/example/spend_p2sh_to_p2sh.dart deleted file mode 100644 index 97b6c52..0000000 --- a/example/spend_p2sh_to_p2sh.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; -// from segwit to P2pkhAddress -// need segwit pub key -// output pay-to-pubkey-hash -// input pay-to-witness-pubkey-hash - -(String, String) spendP2shToP2sh({ - required ECPublic receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: receiver.toRedeemScript().toP2shScriptPubKey()) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2sh_to_p2wkh.dart b/example/spend_p2sh_to_p2wkh.dart deleted file mode 100644 index 0d0e8af..0000000 --- a/example/spend_p2sh_to_p2wkh.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2shToP2wpkh({ - required P2wpkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2wpkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2wkh_to_p2k.dart b/example/spend_p2wkh_to_p2k.dart deleted file mode 100644 index 34d7d9a..0000000 --- a/example/spend_p2wkh_to_p2k.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2wkhToP2pk( - {required P2pkAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2wpkhAddress? changeAddress}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendP2wkhToP2pk( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2wkh_to_p2kh.dart b/example/spend_p2wkh_to_p2kh.dart deleted file mode 100644 index 002f58a..0000000 --- a/example/spend_p2wkh_to_p2kh.dart +++ /dev/null @@ -1,89 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; -// from segwit to P2pkhAddress -// need segwit pub key -// output pay-to-pubkey-hash -// input pay-to-witness-pubkey-hash - -(String, String) spendP2wkhToP2kh( - {required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2wpkhAddress? changeAddress}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendP2wkhToP2kh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2wkh_to_p2sh.dart b/example/spend_p2wkh_to_p2sh.dart deleted file mode 100644 index 1ba3f88..0000000 --- a/example/spend_p2wkh_to_p2sh.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendP2wkhToP2sh({ - required ECPublic receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: receiver.toRedeemScript().toP2shScriptPubKey()) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: - Script(script: senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendP2wkhToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spend_p2wkh_to_p2wkh.dart b/example/spend_p2wkh_to_p2wkh.dart deleted file mode 100644 index c5b7483..0000000 --- a/example/spend_p2wkh_to_p2wkh.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import 'transactions/utxo.dart'; - -(String, String) spendp2wkh({ - required P2wpkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2wpkhAddress? changeAddress, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit, sigHash: sighash); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendp2wkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/spending_with_scripts/spending_builders.dart b/example/spending_with_scripts/spending_builders.dart new file mode 100644 index 0000000..52fd8f4 --- /dev/null +++ b/example/spending_with_scripts/spending_builders.dart @@ -0,0 +1,395 @@ +import 'dart:typed_data'; +import 'package:bitcoin_base/src/bitcoin/address/core.dart'; +import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; +import 'package:bitcoin_base/src/bitcoin/script/input.dart'; +import 'package:bitcoin_base/src/bitcoin/script/output.dart'; +import 'package:bitcoin_base/src/bitcoin/script/script.dart'; +import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; +import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; +import 'package:bitcoin_base/src/provider/utxo_details.dart'; + +BtcTransaction buildP2wpkTransaction({ + required List receiver, + required String Function(Uint8List, String publicKey, int sigHash) sign, + required List utxo, +}) { + // We define transaction inputs by specifying the transaction ID and index. + final txin = utxo + .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) + .toList(); + // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script + // for the transaction inputs. In traditional non-SegWit transactions, + // the unlocking script is part of the scriptSig field, which contains + // the signatures and other data required to spend a transaction output. + // However, in SegWit transactions, the unlocking script (also known as the witness or witness script) + // is moved to a separate part of the transaction called the "witness" field + final List witnesses = []; + + // Create transaction outputs + // Parameters + // amount: This is the quantity of Bitcoin being sent to the recipient's address. + // It represents the value of the transaction output in Bitcoin. + + // scriptPubKey: This is a script that specifies the conditions that must be met in order to spend the output. + // It includes the recipient's Bitcoin address (encoded in a specific way) + // and can also include additional conditions or requirements based on the type of script used. + final List txOut = receiver + .map((e) => + TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .toList(); + + // create BtcTransaction instance with inputs, outputs and segwit + // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. + final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + + for (int i = 0; i < txin.length; i++) { + // For SegWit transactions (excluding P2TR), we use the 'getTransactionSegwitDigit' method + // to obtain the input digest for signing. + final txDigit = tx.getTransactionSegwitDigit( + // index of input + txInIndex: i, + // script pub key of spender address + script: utxo[i].public().toAddress().toScriptPubKey(), + // amount of utxo + amount: utxo[i].utxo.value); + // sign transaction + final signedTx = sign(txDigit, utxo[i].public().toHex(), SIGHASH_ALL); + + // create unlock script + + // P2WPKH: (Pay-to-Witness-Public-Key-Hash): When you're spending from a SegWit P2WPKH address, + // you typically create a SegWit transaction. You'll use the witness (witnessScript) to provide + // the required signatures, and the transaction will indicate it's a SegWit transaction. + + // P2WSH (Pay-to-Witness-Script-Hash): Similarly, for P2WSH addresses, you create SegWit transactions, + // and the witness data (signatures and script) is separated from the transaction data. + final p2wpkhWitness = + TxWitnessInput(stack: [signedTx, utxo[i].public().toHex()]); + witnesses.add(p2wpkhWitness); + } + // add all witnesses to the transaction + tx.witnesses.addAll(witnesses); + return tx; +} + +BtcTransaction buildP2WSHTransaction({ + required List receiver, + required String Function(Uint8List, String publicKey, int sigHash) sign, + required List utxo, +}) { + // We define transaction inputs by specifying the transaction ID and index. + final txin = utxo + .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) + .toList(); + + // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script + // for the transaction inputs. In traditional non-SegWit transactions, + // the unlocking script is part of the scriptSig field, which contains + // the signatures and other data required to spend a transaction output. + // However, in SegWit transactions, the unlocking script (also known as the witness or witness script) + // is moved to a separate part of the transaction called the "witness" field + final List witnesses = []; + + // Create transaction outputs + // Parameters + // amount: This is the quantity of Bitcoin being sent to the recipient's address. + // It represents the value of the transaction output in Bitcoin. + + // scriptPubKey: This is a script that specifies the conditions that must be met in order to spend the output. + // It includes the recipient's Bitcoin address (encoded in a specific way) + // and can also include additional conditions or requirements based on the type of script used. + final List txOut = receiver + .map((e) => + TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .toList(); + + // create BtcTransaction instance with inputs, outputs and segwit + // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. + final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + for (int i = 0; i < txin.length; i++) { + // For SegWit transactions (excluding P2TR), we use the 'getTransactionSegwitDigit' method + // to obtain the input digest for signing. + final txDigit = tx.getTransactionSegwitDigit( + // index of utxo + txInIndex: i, + // P2WSH scripts + script: utxo[i].public().toP2wshScript(), + // amount of utxo + amount: utxo[i].utxo.value); + + // sign transaction + final signedTx = + sign(txDigit, utxo[i].public().toP2wshScript().toHex(), SIGHASH_ALL); + + // create unlock script + + // P2WPKH: (Pay-to-Witness-Public-Key-Hash): When you're spending from a SegWit P2WPKH address, + // you typically create a SegWit transaction. You'll use the witness (witnessScript) to provide + // the required signatures, and the transaction will indicate it's a SegWit transaction. + + // P2WSH (Pay-to-Witness-Script-Hash): Similarly, for P2WSH addresses, you create SegWit transactions, + // and the witness data (signatures and script) is separated from the transaction data. + final p2wshWitness = + TxWitnessInput(stack: ['', signedTx, utxo[i].public().toHex()]); + witnesses.add(p2wshWitness); + } + // add all witnesses to the transaction + tx.witnesses.addAll(witnesses); + return tx; +} + +BtcTransaction buildP2pkhTransaction({ + required List receiver, + required String Function(Uint8List, String publicKey, int sigHash) sign, + required List utxo, +}) { + // We define transaction inputs by specifying the transaction ID and index. + final txin = utxo + .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) + .toList(); + + // Create transaction outputs + // Parameters + // amount: This is the quantity of Bitcoin being sent to the recipient's address. + // It represents the value of the transaction output in Bitcoin. + + // scriptPubKey: This is a script that specifies the conditions that must be met in order to spend the output. + // It includes the recipient's Bitcoin address (encoded in a specific way) + // and can also include additional conditions or requirements based on the type of script used. + final List txOut = receiver + .map((e) => + TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .toList(); + + // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to false. + final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); + for (int i = 0; i < txin.length; i++) { + // For None-SegWit transactions, we use the 'getTransactionDigest' method + // to obtain the input digest for signing. + final txDigit = tx.getTransactionDigest( + // index of utxo + txInIndex: i, + // spender script pub key + script: utxo[i].public().toAddress().toScriptPubKey(), + ); + + // sign transaction + final signedTx = sign(txDigit, utxo[i].public().toHex(), SIGHASH_ALL); + + // set unlocking script for current index + txin[i].scriptSig = Script(script: [signedTx, utxo[i].public().toHex()]); + } + + return tx; +} + +BtcTransaction buildP2shNoneSegwitTransaction({ + required List receiver, + required String Function(Uint8List, String publicKey, int sigHash) sign, + required List utxo, +}) { + // We define transaction inputs by specifying the transaction ID and index. + final txin = utxo + .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) + .toList(); + + // Create transaction outputs + // Parameters + // amount: This is the quantity of Bitcoin being sent to the recipient's address. + // It represents the value of the transaction output in Bitcoin. + + // scriptPubKey: This is a script that specifies the conditions that must be met in order to spend the output. + // It includes the recipient's Bitcoin address (encoded in a specific way) + // and can also include additional conditions or requirements based on the type of script used. + final List txOut = receiver + .map((e) => + TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .toList(); + + // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to false. + final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); + for (int i = 0; i < txin.length; i++) { + final ownerPublic = utxo[i].public(); + final scriptPubKey = + utxo[i].ownerDetails.address.type == AddressType.p2pkhInP2sh + ? ownerPublic.toAddress().toScriptPubKey() + : ownerPublic.toRedeemScript(); + // For None-SegWit transactions, we use the 'getTransactionDigest' method + // to obtain the input digest for signing. + final txDigit = tx.getTransactionDigest( + // index of utxo + txInIndex: i, + // script pub key + script: scriptPubKey, + ); + // sign transaction + final signedTx = sign(txDigit, utxo[i].public().toHex(), SIGHASH_ALL); + + // set unlocking script for current index + switch (utxo[i].ownerDetails.address.type) { + case AddressType.p2pkhInP2sh: + txin[i].scriptSig = Script( + script: [signedTx, ownerPublic.toHex(), scriptPubKey.toHex()]); + break; + case AddressType.p2pkInP2sh: + txin[i].scriptSig = Script(script: [signedTx, scriptPubKey.toHex()]); + break; + default: + throw ArgumentError("invalid address type"); + } + } + + return tx; +} + +BtcTransaction buildP2SHSegwitTransaction({ + required List receiver, + required String Function(Uint8List, String publicKey, int sigHash) sign, + required List utxo, +}) { + // We define transaction inputs by specifying the transaction ID and index. + final txin = utxo + .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) + .toList(); + + // Create transaction outputs + // Parameters + // amount: This is the quantity of Bitcoin being sent to the recipient's address. + // It represents the value of the transaction output in Bitcoin. + + // scriptPubKey: This is a script that specifies the conditions that must be met in order to spend the output. + // It includes the recipient's Bitcoin address (encoded in a specific way) + // and can also include additional conditions or requirements based on the type of script used. + final List txOut = receiver + .map((e) => + TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .toList(); + // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script + // for the transaction inputs. In traditional non-SegWit transactions, + // the unlocking script is part of the scriptSig field, which contains + // the signatures and other data required to spend a transaction output. + // However, in SegWit transactions, the unlocking script (also known as the witness or witness script) + // is moved to a separate part of the transaction called the "witness" field + final List witnesses = []; + + // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. + final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + + for (int i = 0; i < txin.length; i++) { + final ownerPublic = utxo[i].public(); + final scriptPubKey = + utxo[i].ownerDetails.address.type == AddressType.p2wpkhInP2sh + ? ownerPublic.toAddress().toScriptPubKey() + : ownerPublic.toP2wshScript(); + + // For SegWit transactions (excluding P2TR), we use the 'getTransactionSegwitDigit' method + // to obtain the input digest for signing. + final txDigit = tx.getTransactionSegwitDigit( + // index of utxo + txInIndex: i, + // script pub key + script: scriptPubKey, + // amount of utxo + amount: utxo[i].utxo.value); + + // sign transaction + final signedTx = sign(txDigit, utxo[i].public().toHex(), SIGHASH_ALL); + + // In a SegWit P2SH (Pay-to-Script-Hash) transaction, you will find both a scriptSig field and a witness field. + // This combination is used to maintain compatibility with non-SegWit Bitcoin nodes while taking advantage + // of Segregated Witness (SegWit). + + // ScriptSig: This field contains a P2SH redeem script. + // This redeem script is a script that encloses the witness script (the actual spending condition). + // Non-SegWit nodes use this script to validate the transaction as they do + // not understand the witness structure. + + // Witness: This field contains segregated witness data. + // It includes the signatures and any additional data required to unlock the transaction inputs. + // SegWit nodes use this data to validate the transaction and check the signatures. + switch (utxo[i].ownerDetails.address.type) { + case AddressType.p2wpkhInP2sh: + witnesses.add(TxWitnessInput(stack: [signedTx, ownerPublic.toHex()])); + final script = ownerPublic.toSegwitAddress().toScriptPubKey(); + txin[i].scriptSig = Script(script: [script.toHex()]); + break; + case AddressType.p2wshInP2sh: + witnesses.add(TxWitnessInput(stack: [signedTx, scriptPubKey.toHex()])); + final script = ownerPublic.toP2wshAddress().toScriptPubKey(); + txin[i].scriptSig = Script(script: [script.toHex()]); + break; + default: + throw ArgumentError("invalid address type"); + } + } + + return tx; +} + +BtcTransaction buildP2trTransaction({ + required List receiver, + required String Function(Uint8List, String publicKey, int sigHash) sign, + required List utxo, +}) { + // We define transaction inputs by specifying the transaction ID and index. + final txin = utxo + .map((e) => TxInput(txId: e.utxo.txHash, txIndex: e.utxo.vout)) + .toList(); + + // Create transaction outputs + // Parameters + // amount: This is the quantity of Bitcoin being sent to the recipient's address. + // It represents the value of the transaction output in Bitcoin. + + // scriptPubKey: This is a script that specifies the conditions that must be met in order to spend the output. + // It includes the recipient's Bitcoin address (encoded in a specific way) + // and can also include additional conditions or requirements based on the type of script used. + final List txOut = receiver + .map((e) => + TxOutput(amount: e.value, scriptPubKey: e.address.toScriptPubKey())) + .toList(); + + // For P2TR, P2WPKH, P2WSH, and P2SH (SegWit) transactions, we need to set 'hasSegwit' to true. + final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); + + // in a SegWit (Segregated Witness) transaction, the witness data serves as the unlocking script + // for the transaction inputs. In traditional non-SegWit transactions, + // the unlocking script is part of the scriptSig field, which contains + // the signatures and other data required to spend a transaction output. + // However, in SegWit transactions, the unlocking script (also known as the witness or witness script) + // is moved to a separate part of the transaction called the "witness" field + final List witnesses = []; + + for (int i = 0; i < txin.length; i++) { + // For P2TR transactions, we use the 'getTransactionTaprootDigset' method + // to obtain the input digest for signing. + // For Tapleaf (Tapscript), you should create the script yourself. + final txDigit = tx.getTransactionTaprootDigset( + // utxo index + txIndex: i, + // In Taproot, when you create a transaction digest for signing, + // you typically need to include all the scriptPubKeys of the UTXOs + // being spent and their corresponding amounts. + // This information is required to ensure that the transaction is properly structured and secure + scriptPubKeys: + utxo.map((e) => e.ownerDetails.address.toScriptPubKey()).toList(), + amounts: utxo.map((e) => e.utxo.value).toList(), + + // The script that we are spending (ext_flag=1) + script: const Script(script: []), + sighash: TAPROOT_SIGHASH_ALL, + // default is 0; 1 is for script spending (BIP342) + extFlags: 0, + ); + + // sign transaction using `signTapRoot` method of thransaction + final signedTx = sign(txDigit, utxo[i].public().toHex(), SIGHASH_ALL); + + // add witness for current index + witnesses.add(TxWitnessInput(stack: [signedTx])); + } + // add all witness to transaction + tx.witnesses.addAll(witnesses); + + return tx; +} diff --git a/example/spending_with_scripts/spending_single_type.dart b/example/spending_with_scripts/spending_single_type.dart new file mode 100644 index 0000000..0d33d4a --- /dev/null +++ b/example/spending_with_scripts/spending_single_type.dart @@ -0,0 +1,339 @@ +// ignore_for_file: unused_local_variable + +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:bitcoin_base/src/bitcoin/address/core.dart'; +import 'package:bitcoin_base/src/models/network.dart'; +import 'package:bitcoin_base/src/provider/api_provider.dart'; + +import 'package:bitcoin_base/src/provider/transaction_builder.dart'; +import 'package:bitcoin_base/src/provider/utxo_details.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'spending_builders.dart'; + +// Define the network as the Testnet (used for testing and development purposes). +const network = NetworkInfo.TESTNET; + +// Initialize an API provider for interacting with the Testnet's blockchain data. +final api = ApiProvider.fromMempl(network); + +// In these tutorials, you will learn how to spend various types of UTXOs. +// Each method is specific to a type of UTXO. + +// The number of inputs is not important, but ensure that all entries are of the type associated with the method. +// If you want to use several different types for spending in a single transaction, please refer to `transaction_builder_test.dart`. + +// The number of outputs and their types are not important, and you can choose from hundreds of addresses with different types for the outputs + +// Spend P2WPKH: Please note that all input addresses must be of P2WPKH type; otherwise, the transaction will fail. +Future spendingP2WPKH(ECPrivate sWallet, ECPrivate rWallet) async { + // The public keys and P2PWPH addresses we want to spend. + // In this section, you can add any number of addresses with type P2PWPH to this transaction. + final publicKey = sWallet.getPublic(); + // P2WPKH + final sender = publicKey.toSegwitAddress(); + // Read UTXOs of accounts from the BlockCypher API. + final utxo = await api.getAccountUtxo( + UtxoOwnerDetails(address: sender, publicKey: publicKey.toHex())); + // The total amount of UTXOs that we can spend. + final sumOfUtxo = utxo.sumOfUtxosValue(); + if (sumOfUtxo == BigInt.zero) { + throw Exception( + "account does not have any unspent transaction or mybe no confirmed"); + } + // Receive network fees + final feeRate = await api.getNetworkFeeRate(); + // feeRate.medium, feeRate.high ,feeRate.low P/KB + + // In this section, we select the transaction outputs; the number and type of addresses are not important + final prive = sWallet; + final recPub = rWallet.getPublic(); + // P2WPKH + final receiver = recPub.toSegwitAddress(); + // P2TR + final changeAddress = recPub.toTaprootAddress(); + + // Well, now that we have received the UTXOs and determined the outputs, + final List outputsAdress = [receiver, changeAddress]; + + // we need the transaction fee at this point to calculate the output amounts, including the transaction fee. + + // To achieve this, we create a dummy transaction with specified inputs and outputs + // to obtain the actual transaction size in bytes. + // The `estimateTransactionSize` method of the `BitcoinTransactionBuilder` class does this for us + final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxo, outputs: outputsAdress, network: network); + + // Now that we've determined the transaction size, let's calculate the transaction fee + // based on the transaction size and the desired fee rate. + final estimateFee = + feeRate.getEstimate(transactionSize, feeRate: feeRate.medium); + + // We subtract the fee from the total amount of UTXOs to calculate + // the actual amount we can spend in this transaction. + final canSpend = sumOfUtxo - estimateFee; + + // We specify the desired amount for each address. Here, I have divided the desired total + // amount by the number of outputs to ensure an equal amount for each. + final outPutWithValue = outputsAdress + .map((e) => BitcoinOutputDetails( + address: e, value: canSpend ~/ BigInt.from(outputsAdress.length))) + .toList(); + + // I use the 'buildP2wpkTransaction' method to create a transaction. + // You can refer to this method to learn how to create a transaction. + final transaction = buildP2wpkTransaction( + receiver: outPutWithValue, + sign: (p0, publicKey, sigHash) { + // Here, we find the corresponding private key based on the public key and proceed to sign the transaction." + // Note that to sign Taproot transactions, you must use the 'signTapRoot' method for signing. + // Below is a method for spending Taproot transactions that you can review. + return prive.signInput(p0, sigHash: sigHash); + }, + utxo: utxo, + ); + // Now that the transaction is ready, + // we can obtain the output and send it to the network using the 'serialize' method of the transaction. + final ser = transaction.serialize(); + + // transaction id + final id = transaction.txId(); + + // send transaction to the network + final result = await api.sendRawTransaction(ser); +} + +// Spend P2WSH: Please note that all input addresses must be of P2WSH type; otherwise, the transaction will fail. +// This method is for standard 1-1 Multisig P2WSH. +// For standard n-of-m multi-signature scripts, please refer to the 'multi_sig_transactions.dart' tutorial. +Future spendingP2WSH(ECPrivate sWallet, ECPrivate rWallet) async { + // All the steps are the same as in the first tutorial; + // the only difference is the transaction input type, + // and we use method `buildP2WSHTransaction` to create the transaction. + final addr = sWallet.getPublic(); + // P2WSH ADDRESS + final sender = addr.toP2wshAddress(); + final utxo = await api.getAccountUtxo( + UtxoOwnerDetails(address: sender, publicKey: addr.toHex())); + final sumOfUtxo = utxo.sumOfUtxosValue(); + if (sumOfUtxo == BigInt.zero) { + throw Exception( + "account does not have any unspent transaction or mybe no confirmed"); + } + + final feeRate = await api.getNetworkFeeRate(); + final prive = sWallet; + + final recPub = rWallet.getPublic(); + final receiver = recPub.toSegwitAddress(); + + final changeAddress = recPub.toSegwitAddress(); + final List outputsAdress = [receiver, changeAddress]; + final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxo, outputs: outputsAdress, network: network); + final estimateFee = + feeRate.getEstimate(transactionSize, feeRate: feeRate.medium); + final canSpend = sumOfUtxo - estimateFee; + final outPutWithValue = outputsAdress + .map((e) => BitcoinOutputDetails( + address: e, value: canSpend ~/ BigInt.from(outputsAdress.length))) + .toList(); + final transaction = buildP2WSHTransaction( + receiver: outPutWithValue, + sign: (p0, publicKey, sigHash) { + return prive.signInput(p0, sigHash: sigHash); + }, + utxo: utxo, + ); + final ser = transaction.serialize(); + final id = transaction.txId(); + await api.sendRawTransaction(ser); +} + +// Spend P2PKH: Please note that all input addresses must be of P2PKH type; otherwise, the transaction will fail. +Future spendingP2PKH(ECPrivate sWallet, ECPrivate rWallet) async { + // All the steps are the same as in the first tutorial; + // the only difference is the transaction input type, + // and we use method `buildP2pkhTransaction` to create the transaction. + final addr = sWallet.getPublic(); + // P2PKH + final sender = addr.toAddress(); + final utxo = await api.getAccountUtxo( + UtxoOwnerDetails(address: sender, publicKey: addr.toHex())); + final sumOfUtxo = utxo.sumOfUtxosValue(); + if (sumOfUtxo == BigInt.zero) { + throw Exception( + "account does not have any unspent transaction or mybe no confirmed"); + } + + final feeRate = await api.getNetworkFeeRate(); + final prive = sWallet; + + final recPub = rWallet.getPublic(); + final receiver = recPub.toSegwitAddress(); + final changeAddress = recPub.toSegwitAddress(); + final List outputsAdress = [receiver, changeAddress]; + final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxo, outputs: outputsAdress, network: network); + final estimateFee = + feeRate.getEstimate(transactionSize, feeRate: feeRate.medium); + final canSpend = sumOfUtxo - estimateFee; + final outPutWithValue = outputsAdress + .map((e) => BitcoinOutputDetails( + address: e, value: canSpend ~/ BigInt.from(outputsAdress.length))) + .toList(); + + final transaction = buildP2pkhTransaction( + receiver: outPutWithValue, + sign: (p0, publicKey, sigHash) { + return prive.signInput(p0, sigHash: sigHash); + }, + utxo: utxo, + ); + final ser = transaction.serialize(); + final id = transaction.txId(); + await api.sendRawTransaction(ser); +} + +// Spend P2SH(P2PKH) or P2SH(P2PK): Please note that all input addresses must be of P2SH(P2PKH) or P2SH(P2PK) type; otherwise, the transaction will fail. +// This method is for standard 1-1 Multisig P2SH. +// For standard n-of-m multi-signature scripts, please refer to the 'multi_sig_transactions.dart' tutorial. +Future spendingP2SHNoneSegwit( + ECPrivate sWallet, ECPrivate rWallet) async { + // All the steps are the same as in the first tutorial; + // the only difference is the transaction input type, + // and we use method `buildP2shNoneSegwitTransaction` to create the transaction. + final addr = sWallet.getPublic(); + // P2SH(P2PK) + final sender = addr.toP2pkInP2sh(); + final utxo = await api.getAccountUtxo( + UtxoOwnerDetails(address: sender, publicKey: addr.toHex())); + final sumOfUtxo = utxo.sumOfUtxosValue(); + if (sumOfUtxo == BigInt.zero) { + throw Exception( + "account does not have any unspent transaction or mybe no confirmed"); + } + + final feeRate = await api.getNetworkFeeRate(); + final prive = sWallet; + + final recPub = rWallet.getPublic(); + final receiver = recPub.toSegwitAddress(); + final changeAddress = recPub.toSegwitAddress(); + final List outputsAdress = [receiver, changeAddress]; + final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxo, outputs: outputsAdress, network: network); + final estimateFee = + feeRate.getEstimate(transactionSize, feeRate: feeRate.medium); + final canSpend = sumOfUtxo - estimateFee; + final outPutWithValue = outputsAdress + .map((e) => BitcoinOutputDetails( + address: e, value: canSpend ~/ BigInt.from(outputsAdress.length))) + .toList(); + final transaction = buildP2shNoneSegwitTransaction( + receiver: outPutWithValue, + sign: (p0, publicKey, sigHash) { + return prive.signInput(p0, sigHash: sigHash); + }, + utxo: utxo, + ); + final ser = transaction.serialize(); + final id = transaction.txId(); + await api.sendRawTransaction(ser); +} + +// Spend P2SH(P2WPKH) or P2SH(P2WSH): Please note that all input addresses must be of P2SH(P2WPKH) or P2SH(P2WSH) type; otherwise, the transaction will fail. +// This method is for standard 1-1 Multisig P2SH. +// For standard n-of-m multi-signature scripts, please refer to the 'multi_sig_transactions.dart' tutorial. +Future spendingP2shSegwit(ECPrivate sWallet, ECPrivate rWallet) async { + // All the steps are the same as in the first tutorial; + // the only difference is the transaction input type, + // and we use method `buildP2SHSegwitTransaction` to create the transaction. + final addr = sWallet.getPublic(); + // P2SH(P2PWKH) + final sender = addr.toP2wpkhInP2sh(); + final utxo = await api.getAccountUtxo( + UtxoOwnerDetails(address: sender, publicKey: addr.toHex())); + final sumOfUtxo = utxo.sumOfUtxosValue(); + if (sumOfUtxo == BigInt.zero) { + throw Exception( + "account does not have any unspent transaction or mybe no confirmed"); + } + + final feeRate = await api.getNetworkFeeRate(); + final prive = sWallet; + + final recPub = rWallet.getPublic(); + final receiver = recPub.toSegwitAddress(); + + final changeAddress = recPub.toSegwitAddress(); + final List outputsAdress = [receiver, changeAddress]; + final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxo, outputs: outputsAdress, network: network); + final estimateFee = + feeRate.getEstimate(transactionSize, feeRate: feeRate.medium); + final canSpend = sumOfUtxo - estimateFee; + final outPutWithValue = outputsAdress + .map((e) => BitcoinOutputDetails( + address: e, value: canSpend ~/ BigInt.from(outputsAdress.length))) + .toList(); + + // return; + final transaction = buildP2SHSegwitTransaction( + receiver: outPutWithValue, + sign: (p0, publicKey, sigHash) { + return prive.signInput(p0, sigHash: sigHash); + }, + utxo: utxo, + ); + final ser = transaction.serialize(); + final id = transaction.txId(); + await api.sendRawTransaction(ser); +} + +// Spend P2TR: Please note that all input addresses must be of P2TR type; otherwise, the transaction will fail. +Future spendingP2TR(ECPrivate sWallet, ECPrivate rWallet) async { + // All the steps are the same as in the first tutorial; + // the only difference is the transaction input type, + // and we use method `buildP2trTransaction` to create the transaction. + // we use `signTapRoot` of ECPrivate for signing taproot transaction + final addr = sWallet.getPublic(); + // P2TR address + final sender = addr.toTaprootAddress(); + final utxo = await api.getAccountUtxo( + UtxoOwnerDetails(address: sender, publicKey: addr.toHex())); + final sumOfUtxo = utxo.sumOfUtxosValue(); + if (sumOfUtxo == BigInt.zero) { + throw Exception( + "account does not have any unspent transaction or mybe no confirmed"); + } + + final feeRate = await api.getNetworkFeeRate(); + final prive = sWallet; + + final recPub = rWallet.getPublic(); + final receiver = recPub.toSegwitAddress(); + final changeAddress = recPub.toSegwitAddress(); + final List outputsAdress = [receiver, changeAddress]; + final transactionSize = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxo, outputs: outputsAdress, network: network); + final estimateFee = + feeRate.getEstimate(transactionSize, feeRate: feeRate.medium); + final canSpend = sumOfUtxo - estimateFee; + final outPutWithValue = outputsAdress + .map((e) => BitcoinOutputDetails( + address: e, value: canSpend ~/ BigInt.from(outputsAdress.length))) + .toList(); + + final transaction = buildP2trTransaction( + receiver: outPutWithValue, + sign: (p0, publicKey, sigHash) { + // Use signTapRoot instead of signInput for the taproot transaction input. + return prive.signTapRoot(p0, sighash: sigHash, tweak: true); + }, + utxo: utxo, + ); + final ser = transaction.serialize(); + final id = transaction.txId(); + await api.sendRawTransaction(ser); +} diff --git a/example/spending_with_transaction_builder/multi_sig_transactions.dart b/example/spending_with_transaction_builder/multi_sig_transactions.dart new file mode 100644 index 0000000..2708339 --- /dev/null +++ b/example/spending_with_transaction_builder/multi_sig_transactions.dart @@ -0,0 +1,302 @@ +// spend from 8 different address type to 10 different output +// ignore_for_file: unused_local_variable + +import 'package:bitcoin_base/bitcoin.dart'; +import 'package:bitcoin_base/src/bitcoin/address/core.dart'; +import 'package:bitcoin_base/src/models/network.dart'; +import 'package:bitcoin_base/src/provider/api_provider.dart'; +import 'package:bitcoin_base/src/provider/multisig_script.dart'; +import 'package:bitcoin_base/src/provider/transaction_builder.dart'; +import 'package:bitcoin_base/src/provider/utxo_details.dart'; + +void main() async { + // select network + const NetworkInfo network = NetworkInfo.TESTNET; + + // select api for read accounts UTXOs and send transaction + // Mempool or BlockCypher + final api = ApiProvider.fromMempl(network); + + const mnemonic = + "spy often critic spawn produce volcano depart fire theory fog turn retire"; + + final masterWallet = BIP32HWallet.fromMnemonic(mnemonic); + + // i generate 4 HD wallet for this test and now i have access to private and pulic key of each wallet + final sp1 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/1"); + final sp2 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/2"); + final sp3 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/3"); + final sp4 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/4"); + final sp5 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/4"); + final sp6 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/4"); + // access to private key `ECPrivate` + final private1 = ECPrivate.fromBytes(sp1.privateKey); + final private2 = ECPrivate.fromBytes(sp2.privateKey); + final private3 = ECPrivate.fromBytes(sp3.privateKey); + final private4 = ECPrivate.fromBytes(sp4.privateKey); + + final private5 = ECPrivate.fromBytes(sp5.privateKey); + final private6 = ECPrivate.fromBytes(sp6.privateKey); + // access to public key `ECPublic` + final public1 = private1.getPublic(); + final public2 = private2.getPublic(); + final public3 = private3.getPublic(); + final public4 = private4.getPublic(); + final public5 = private5.getPublic(); + final public6 = private6.getPublic(); + + final signer1 = MultiSignatureSigner(publicKey: public1.toHex(), weight: 2); + + final signer2 = MultiSignatureSigner(publicKey: public2.toHex(), weight: 2); + + final signer3 = MultiSignatureSigner(publicKey: public3.toHex(), weight: 1); + + final signer4 = MultiSignatureSigner(publicKey: public4.toHex(), weight: 1); + + final MultiSignatureAddress multiSignatureAddress = MultiSignatureAddress( + threshold: 4, + signers: [signer1, signer2, signer3, signer4], + addressType: AddressType.p2wsh); + // P2WSH Multisig 4-6 + // tb1qxt3c7849m0m6cv3z3s35c3zvdna3my3yz0r609qd9g0dcyyk580sgyldhe + + final p2wshMultiSigAddress = multiSignatureAddress.address.toAddress(network); + + // p2sh(p2wsh) multisig + final signerP2sh1 = + MultiSignatureSigner(publicKey: public5.toHex(), weight: 1); + + final signerP2sh2 = + MultiSignatureSigner(publicKey: public6.toHex(), weight: 1); + + final signerP2sh3 = + MultiSignatureSigner(publicKey: public1.toHex(), weight: 1); + + final MultiSignatureAddress p2shMultiSignature = MultiSignatureAddress( + threshold: 2, + signers: [signerP2sh1, signerP2sh2, signerP2sh3], + addressType: AddressType.p2wshInP2sh); + // P2SH(P2WSH) miltisig 2-3 + // 2N8co8bth9CNKtnWGfHW6HuUNgnNPNdpsMj + final p2shMultisigAddress = p2shMultiSignature.address.toAddress(network); + + // P2TR + final exampleAddr2 = public2.toTaprootAddress(); + // P2KH + final exampleAddr4 = public3.toAddress(); + // Spending List + // i use some different address type for this + // now i want to spending from 8 address in one transaction + // we need publicKeys and address + final spenders = [ + UtxoOwnerDetails( + multiSigAddress: multiSignatureAddress, + address: multiSignatureAddress.address), + UtxoOwnerDetails( + multiSigAddress: p2shMultiSignature, + address: p2shMultiSignature.address), + UtxoOwnerDetails(publicKey: public2.toHex(), address: exampleAddr2), + ]; + + // i need now to read spenders account UTXOS + final List utxos = []; + + // i add some method for provider to read utxos from mempol or blockCypher + // looping address to read Utxos + for (final spender in spenders) { + try { + // read each address utxo from mempool + final spenderUtxos = await api.getAccountUtxo(spender); + // check if account have any utxo for spending (balance) + + if (!spenderUtxos.canSpend()) { + // address does not have any satoshi for spending: + continue; + } + + utxos.addAll(spenderUtxos); + } on ApiProviderException { + // something bad happen when reading Utxos: + return; + } + } + // Well, now we calculate how much we can spend + final sumOfUtxo = utxos.sumOfUtxosValue(); + + // 1,479,604 sum of all utxos + + final hasSatoshi = sumOfUtxo != BigInt.zero; + + if (!hasSatoshi) { + // Are you kidding? We don't have btc to spend + return; + } + String? memo = "https://github.com/mrtnetwork"; + + // fee calculation + // To calculate the transaction fee, we need to have the transaction size + + // To achieve this, we create a dummy transaction with the desired inputs + // and outputs to determine the transaction size accurately. + // The correctness of UTXOs, the type of address for outputs, + // and data (memo) are crucial. If any of these aspects differ from the original transaction, + // the transaction size may vary. We consider the maximum amount for each transaction in the fake transaction. + // In any case, the size of each input amount is 8 bytes + // I have created a method for accomplishing this. + int size = BitcoinTransactionBuilder.estimateTransactionSize( + utxos: utxos, + outputs: [ + p2shMultiSignature.address, + multiSignatureAddress.address, + exampleAddr2, + exampleAddr4 + ], + network: network, + memo: memo, + enableRBF: true); + // transaction size: 565 byte + + // Ok now we have the transaction size, let's get the estimated cost + // Use the BlockCypher API to obtain the network cost because Mempool doesn't provide us + // with the actual transaction cost for the test network. + // That's my perspective, of course. + final blockCypher = ApiProvider.fromBlocCypher(network, client: api.client); + + final feeRate = await blockCypher.getNetworkFeeRate(); + // fee rate inKB + // feeRate.medium: 32279 P/KB + // feeRate.high: 43009 P/KB + // feeRate.low: 22594 P/KB + + // Well now we have the transaction fee and we can create the outputs based on this + // 565 byte / 1024 * (feeRate / 32279 ) = 17810 + + final fee = feeRate.getEstimate(size, feeRate: feeRate.medium); + // fee = 17,810 + + // We consider 17,810 satoshi for the cost + + // now we have 1,461,794(1,479,604/sumOfUtxo - 17,810/fee) satoshi for spending let do it + // we create 4 different output with different address type like (p2sh, p2wsh, etc.) + // We consider the spendable amount for 4 outputs and divide by 4, each output 365448.5, + // 365448 for two addresses and 365449 for two addresses because of decimal + final output1 = BitcoinOutputDetails( + address: p2shMultiSignature.address, value: BigInt.from(365449)); + final output2 = BitcoinOutputDetails( + address: multiSignatureAddress.address, value: BigInt.from(365449)); + final output3 = + BitcoinOutputDetails(address: exampleAddr2, value: BigInt.from(365448)); + final output4 = + BitcoinOutputDetails(address: exampleAddr4, value: BigInt.from(365448)); + + // Well, now it is clear to whom we are going to pay the amount + // Now let's create the transaction + final transactionBuilder = BitcoinTransactionBuilder( + // Now, we provide the UTXOs we want to spend. + utxos: utxos, + // We select transaction outputs + outPuts: [output1, output2, output3, output4], + /* + Transaction fee + Ensure that you have accurately calculated the amounts. + If the sum of the outputs, including the transaction fee, + does not match the total amount of UTXOs, + it will result in an error. Please double-check your calculations. + */ + fee: fee, + // network, testnet, mainnet + network: network, + // If you wish to record information in your transaction on the blockchain network, + // please enter it here (memo). Keep in mind that the transaction fee will increase + // relative to the amount of information included + memo: memo, + /* + RBF, or Replace-By-Fee, is a feature in Bitcoin that allows you to increase the fee of an unconfirmed + transaction that you've broadcasted to the network. + This feature is useful when you want to speed up a + transaction that is taking longer than expected to get confirmed due to low transaction fees. + */ + enableRBF: true, + ); + + // now we use BuildTransaction to complete them + // I considered a method parameter for this, to sign the transaction + + // I've added a method for signing the transaction as a parameter. + // This method sends you the public key for each UTXO, + // allowing you to sign the desired input with the associated private key + final transaction = + transactionBuilder.buildTransaction((trDigest, utxo, publicKey) { + late ECPrivate key; + + // ok we have the public key of the current UTXO and we use some conditions to find private key and sign transaction + String currentPublicKey = publicKey; + + // if is multi-sig and we dont have access to some private key of address we return empty string + // Note that you must have access to keys with the required signature(threshhold); otherwise, + // you will receive an error. + if (utxo.isMultiSig()) { + // check we have private keys of this sigerns or not + // return "" + } + if (currentPublicKey == public3.toHex()) { + key = private3; + } else if (currentPublicKey == public2.toHex()) { + key = private2; + } else if (currentPublicKey == public1.toHex()) { + key = private1; + } else if (currentPublicKey == public4.toHex()) { + key = private4; + } else if (currentPublicKey == public5.toHex()) { + key = private5; + } else if (currentPublicKey == public6.toHex()) { + key = private6; + } else { + throw Exception("Cannot find private key"); + } + + // 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()) { + // 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. + return key.signTapRoot(trDigest); + } else { + // is seqwit(v0) or lagacy address we use SingInput (ECDSA) + return key.signInput(trDigest); + } + }); + + // ok everything is fine and we need a transaction output for broadcasting + // We use the Serialize method to receive the transaction output + final digest = transaction.serialize(); + + // we check if transaction is segwit or not + // When one of the input UTXO addresses is SegWit, the transaction is considered SegWit. + final isSegwitTr = transactionBuilder.hasSegwit(); + + // transaction id + final transactionId = transaction.txId(); + + // transaction size + int transactionSize; + + if (isSegwitTr) { + transactionSize = transaction.getVSize(); + } else { + transactionSize = transaction.getSize(); + } + // real transaction size: 565 + // fake transaction size: 565 + // In all cases this should be the same + + try { + // now we send transaction to network + final txId = await blockCypher.sendRawTransaction(digest); + // Yes, we did :) 19317835855d50a822257247ee8ff2bab0e4c7d3a9000bd4006190d52975517e + // Now we check Mempol for what happened https://mempool.space/testnet/tx/19317835855d50a822257247ee8ff2bab0e4c7d3a9000bd4006190d52975517e + } on ApiProviderException { + // Something went wrong when sending the transaction + } +} diff --git a/example/spending_with_transaction_builder/transaction_builder_test.dart b/example/spending_with_transaction_builder/transaction_builder_test.dart new file mode 100644 index 0000000..3376e7f --- /dev/null +++ b/example/spending_with_transaction_builder/transaction_builder_test.dart @@ -0,0 +1,255 @@ +import 'package:bitcoin_base/bitcoin.dart'; +import 'package:bitcoin_base/src/models/network.dart'; +import 'package:bitcoin_base/src/provider/api_provider.dart'; +import 'package:bitcoin_base/src/provider/transaction_builder.dart'; +import 'package:bitcoin_base/src/provider/utxo_details.dart'; + +// spend from 8 different address type to 10 different output +void main() async { + // select network + const NetworkInfo network = NetworkInfo.TESTNET; + + // select api for read accounts UTXOs and send transaction + // Mempool or BlockCypher + final api = ApiProvider.fromMempl(network); + + const mnemonic = + "spy often critic spawn produce volcano depart fire theory fog turn retire"; + + final masterWallet = BIP32HWallet.fromMnemonic(mnemonic); + + // i generate 4 HD wallet for this test and now i have access to private and pulic key of each wallet + final sp1 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/1"); + final sp2 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/2"); + final sp3 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/3"); + final sp4 = BIP32HWallet.drivePath(masterWallet, "m/44'/0'/0'/0/0/4"); + + // access to private key `ECPrivate` + final private1 = ECPrivate.fromBytes(sp1.privateKey); + final private2 = ECPrivate.fromBytes(sp2.privateKey); + final private3 = ECPrivate.fromBytes(sp3.privateKey); + final private4 = ECPrivate.fromBytes(sp4.privateKey); + + // access to public key `ECPublic` + final public1 = private1.getPublic(); + final public2 = private2.getPublic(); + final public3 = private3.getPublic(); + final public4 = private4.getPublic(); + + // P2PKH ADDRESS + final exampleAddr1 = public1.toAddress(); + // P2TR + final exampleAddr2 = public2.toTaprootAddress(); + // P2PKHINP2SH + final exampleAddr3 = public2.toP2pkhInP2sh(); + // P2KH + final exampleAddr4 = public3.toAddress(); + // P2PKHINP2SH + final exampleAddr5 = public3.toP2pkhInP2sh(); + // P2WSHINP2SH 1-1 multisig + final exampleAddr6 = public3.toP2wshInP2sh(); + // P2WPKHINP2SH + final exampleAddr7 = public3.toP2wpkhInP2sh(); + // P2PKINP2SH + final exampleAddr8 = public4.toP2pkInP2sh(); + // P2WPKH + final exampleAddr9 = public3.toSegwitAddress(); + // P2WSH 1-1 multisig + final exampleAddr10 = public3.toP2wshAddress(); + + // Spending List + // i use some different address type for this + // now i want to spending from 8 address in one transaction + // we need publicKeys and address + final spenders = [ + UtxoOwnerDetails(publicKey: public1.toHex(), address: exampleAddr1), + UtxoOwnerDetails(publicKey: public2.toHex(), address: exampleAddr2), + UtxoOwnerDetails(publicKey: public3.toHex(), address: exampleAddr7), + UtxoOwnerDetails(publicKey: public3.toHex(), address: exampleAddr9), + UtxoOwnerDetails(publicKey: public3.toHex(), address: exampleAddr10), + UtxoOwnerDetails(publicKey: public2.toHex(), address: exampleAddr3), + UtxoOwnerDetails(publicKey: public4.toHex(), address: exampleAddr8), + UtxoOwnerDetails(publicKey: public3.toHex(), address: exampleAddr4), + ]; + + // i need now to read spenders account UTXOS + final List utxos = []; + + // i add some method for provider to read utxos from mempol or blockCypher + // looping address to read Utxos + for (final spender in spenders) { + try { + // read each address utxo from mempool + final spenderUtxos = await api.getAccountUtxo(spender); + // check if account have any utxo for spending (balance) + if (!spenderUtxos.canSpend()) { + // address does not have any satoshi for spending: + continue; + } + + utxos.addAll(spenderUtxos); + } on ApiProviderException { + // something bad happen when reading Utxos: + return; + } + } + // Well, now we calculate how much we can spend + final sumOfUtxo = utxos.sumOfUtxosValue(); + // 1,224,143 sum of all utxos + + final hasSatoshi = sumOfUtxo != BigInt.zero; + + if (!hasSatoshi) { + // Are you kidding? We don't have btc to spend + return; + } + + // In the 'p2wsh_multi_sig_test' example, I have provided a comprehensive + // explanation of how to determine the transaction fee + // before creating the original transaction. + + // We consider 50,003 satoshi for the cost + final fee = BigInt.from(50003); + + // now we have 1,174,140 satoshi for spending let do it + // we create 10 different output with different address type like (pt2r, p2sh(p2wpkh), p2sh(p2wsh), p2pkh, etc.) + // We consider the spendable amount for 10 outputs and divide by 10, each output 117,414 + final output1 = + BitcoinOutputDetails(address: exampleAddr4, value: BigInt.from(117414)); + final output2 = + BitcoinOutputDetails(address: exampleAddr9, value: BigInt.from(117414)); + final output3 = + BitcoinOutputDetails(address: exampleAddr10, value: BigInt.from(117414)); + final output4 = + BitcoinOutputDetails(address: exampleAddr1, value: BigInt.from(117414)); + final output5 = + BitcoinOutputDetails(address: exampleAddr3, value: BigInt.from(117414)); + final output6 = + BitcoinOutputDetails(address: exampleAddr2, value: BigInt.from(117414)); + final output7 = + BitcoinOutputDetails(address: exampleAddr7, value: BigInt.from(117414)); + final output8 = + BitcoinOutputDetails(address: exampleAddr8, value: BigInt.from(117414)); + final output9 = + BitcoinOutputDetails(address: exampleAddr5, value: BigInt.from(117414)); + final output10 = + BitcoinOutputDetails(address: exampleAddr6, value: BigInt.from(117414)); + + // Well, now it is clear to whom we are going to pay the amount + // Now let's create the transaction + final transactionBuilder = BitcoinTransactionBuilder( + // Now, we provide the UTXOs we want to spend. + utxos: utxos, + // We select transaction outputs + outPuts: [ + output1, + output2, + output3, + output4, + output5, + output6, + output7, + output8, + output9, + output10 + ], + /* + Transaction fee + Ensure that you have accurately calculated the amounts. + If the sum of the outputs, including the transaction fee, + does not match the total amount of UTXOs, + it will result in an error. Please double-check your calculations. + */ + fee: fee, + // network, testnet, mainnet + network: network, + // If you like the note write something else and leave it blank + // I will put my GitHub address here + memo: "https://github.com/mrtnetwork", + /* + RBF, or Replace-By-Fee, is a feature in Bitcoin that allows you to increase the fee of an unconfirmed + transaction that you've broadcasted to the network. + This feature is useful when you want to speed up a + transaction that is taking longer than expected to get confirmed due to low transaction fees. + */ + enableRBF: true, + ); + + // now we use BuildTransaction to complete them + // I considered a method parameter for this, to sign the transaction + + // parameters + // utxo infos with owner details + // trDigest transaction digest of current UTXO (must be sign with correct privateKey) + final transaction = + transactionBuilder.buildTransaction((trDigest, utxo, publicKey) { + late ECPrivate key; + + // ok we have the public key of the current UTXO and we use some conditions to find private key and sign transaction + String currentPublicKey = publicKey; + + // if is multi-sig and we dont have access to some private key of address we return empty string + // Note that you must have access to keys with the required signature(threshhold) ; otherwise, + // you will receive an error. + if (utxo.isMultiSig()) { + // check we have private keys of this sigerns or not + // return "" + } + + if (currentPublicKey == public3.toHex()) { + key = private3; + } else if (currentPublicKey == public2.toHex()) { + key = private2; + } else if (currentPublicKey == public1.toHex()) { + key = private1; + } else if (currentPublicKey == public4.toHex()) { + key = private4; + } else { + throw Exception("Cannot find private key"); + } + + // 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()) { + // 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. + return key.signTapRoot(trDigest); + } else { + // is seqwit(v0) or lagacy address we use SingInput (ECDSA) + return key.signInput(trDigest); + } + }); + + // ok everything is fine and we need a transaction output for broadcasting + // We use the Serialize method to receive the transaction output + final digest = transaction.serialize(); + + // we check if transaction is segwit or not + // When one of the input UTXO addresses is SegWit, the transaction is considered SegWit. + final isSegwitTr = transactionBuilder.hasSegwit(); + + // transaction id + // ignore: unused_local_variable + final transactionId = transaction.txId(); + + // transaction size + // ignore: unused_local_variable + int transactionSize; + + if (isSegwitTr) { + transactionSize = transaction.getVSize(); + } else { + transactionSize = transaction.getSize(); + } + + try { + // now we send transaction to network + // ignore: unused_local_variable + final txId = await api.sendRawTransaction(digest); + // Yes, we did :) 2625cd75f6576c38445deb2a9573c12ccc3438c3a6dd16fd431162d3f2fbb6c8 + // Now we check Mempol for what happened https://mempool.space/testnet/tx/2625cd75f6576c38445deb2a9573c12ccc3438c3a6dd16fd431162d3f2fbb6c8 + } on ApiProviderException { + // Something went wrong when sending the transaction + } +} diff --git a/example/transactions/helper.dart b/example/transactions/helper.dart deleted file mode 100644 index 0bde530..0000000 --- a/example/transactions/helper.dart +++ /dev/null @@ -1,118 +0,0 @@ -import 'dart:convert'; -import './utxo.dart'; -import 'package:http/http.dart' as http; - -class RPCError implements Exception { - const RPCError(this.errorCode, this.message, this.data, - {required this.request}); - - final int errorCode; - final String message; - final dynamic data; - final Map request; - - @override - String toString() { - return 'RPCError: got code $errorCode with msg "$message".'; - } -} - -Map parseError( - Map data, Map request) { - final error = data['error']; - if (error == null) return data; - final code = (error['code'] ?? 0); - final message = error['message']; - final errorData = error['data']; - throw RPCError(code, message, errorData, request: request); -} - -class BTCRpcHelper { - BTCRpcHelper( - - ///The link is for testing, it might not work, please use your RPC service - {this.url = - "https://serene-wild-dew.btc-testnet.discover.quiknode.pro/33a88cd7b9e1515949682b452f10c134ae4c2959/", - Map? header}) - : _header = header ?? - { - 'Content-Type': 'application/json', - 'x-api-key': "0fd2f4ca-25ac-4e19-a6c2-e66696ba4c8b" - }; - final String url; - final Map _header; - - int _currentRequestId = 1; - - Future call( - String function, [ - List? params, - ]) async { - http.Client client = http.Client(); - try { - params ??= []; - final payload = { - 'jsonrpc': '2.0', - 'method': function, - 'params': params, - 'id': _currentRequestId++, - }; - - final response = await client - .post( - Uri.parse(url), - headers: _header, - body: json.encode(payload), - ) - .timeout(const Duration(seconds: 30)); - final data = parseError(json.decode(response.body), payload); - - final result = data['result']; - - return result; - } finally { - client.close(); - } - } - - Future getSmartEstimate() async { - final data = await call("estimatesmartfee", [2, "CONSERVATIVE"]); - return priceToBtcUnit(data['feerate']); - } - - Future sendRawTransaction(String txDigit) async { - final data = await call("sendrawtransaction", [txDigit]); - return data!; - } - - ///This method is for testing, it may not work, please use your RPC service - Future> getUtxo(String address, - {String? url, - Map header = const { - 'Content-Type': 'application/json', - 'api-key': "dc0cdbc2-d3fc-4ae8-ae45-0a44bc28b5f9" - }}) async { - http.Client client = http.Client(); - try { - String u = - url ?? "https://btcbook-testnet.nownodes.io/api/v2/utxo/$address"; - - final response = await client - .get( - Uri.parse(u), - headers: header, - ) - .timeout(const Duration(seconds: 30)); - final data = json.decode(response.body) as List; - return data.map((e) => UTXO.fromJson(e)).toList(); - } finally { - client.close(); - } - } -} - -/// This converter is not accurate -BigInt priceToBtcUnit(double price, {double decimal = 1e8}) { - final dec = price * decimal; - return BigInt.from(dec); -} diff --git a/example/transactions/spend_p2kh_to_p2k.dart b/example/transactions/spend_p2kh_to_p2k.dart deleted file mode 100644 index ab34ebb..0000000 --- a/example/transactions/spend_p2kh_to_p2k.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2pkhToP2pk({ - required P2pkAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2pkhAddress? changeAddress, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, - script: Script(script: senderAddress.toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - - if (trSize == null) { - return spendP2pkhToP2pk( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2kh_to_p2kh.dart b/example/transactions/spend_p2kh_to_p2kh.dart deleted file mode 100644 index e9ca842..0000000 --- a/example/transactions/spend_p2kh_to_p2kh.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2pkhToP2pkh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, - script: Script(script: senderAddress.toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - - if (trSize == null) { - return spendP2pkhToP2pkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2pk_to_p2pkh.dart b/example/transactions/spend_p2pk_to_p2pkh.dart deleted file mode 100644 index cf9356c..0000000 --- a/example/transactions/spend_p2pk_to_p2pkh.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2pkToP2pkh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final sc = senderPub.toRedeemScript(); - final txDigit = - tx.getTransactionDigest(txInIndex: i, script: sc, sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx]); - } - - if (trSize == null) { - return spendP2pkToP2pkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2pk_to_p2sh.dart b/example/transactions/spend_p2pk_to_p2sh.dart deleted file mode 100644 index a147d2a..0000000 --- a/example/transactions/spend_p2pk_to_p2sh.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2pkToP2sh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - required BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - final senderAddress = senderPub.toAddress(); - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (trSize != null && mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut); - for (int i = 0; i < txin.length; i++) { - final sc = senderPub.toRedeemScript(); - final txDigit = - tx.getTransactionDigest(txInIndex: i, script: sc, sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = Script(script: [signedTx]); - } - - if (trSize == null) { - return spendP2pkToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2pkh_to_p2sh.dart b/example/transactions/spend_p2pkh_to_p2sh.dart deleted file mode 100644 index 7cd4e5d..0000000 --- a/example/transactions/spend_p2pkh_to_p2sh.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -// from segwit to P2pkhAddress -// need segwit pub key -// output pay-to-pubkey-hash -// input pay-to-witness-pubkey-hash - -(String, String) spendP2khToP2sh({ - required ECPublic receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: receiver.toRedeemScript().toP2shScriptPubKey()) - ]; - if (needChangeTx > BigInt.zero) { - final senderAddress = senderPub.toAddress(); - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int b = 0; b < txin.length; b++) { - final txDigit = tx.getTransactionDigest( - txInIndex: b, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit, sigHash: sighash); - txin[b].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - - if (trSize == null) { - return spendP2khToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2pkh_to_p2wpkh.dart b/example/transactions/spend_p2pkh_to_p2wpkh.dart deleted file mode 100644 index 7213999..0000000 --- a/example/transactions/spend_p2pkh_to_p2wpkh.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2khToP2wkh({ - required P2wpkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - final senderAddress = senderPub.toAddress(); - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script(script: senderAddress.toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int b = 0; b < txin.length; b++) { - final txDigit = tx.getTransactionDigest( - txInIndex: b, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash); - final signedTx = sign(txDigit, sigHash: sighash); - txin[b].scriptSig = Script(script: [signedTx, senderPub.toHex()]); - } - if (trSize == null) { - return spendP2khToP2wkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2sh_to_p2k.dart b/example/transactions/spend_p2sh_to_p2k.dart deleted file mode 100644 index 894ebb1..0000000 --- a/example/transactions/spend_p2sh_to_p2k.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2shToP2pk({ - required P2pkAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit, sigHash: sighash); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2pk( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2sh_to_p2pkh.dart b/example/transactions/spend_p2sh_to_p2pkh.dart deleted file mode 100644 index 576e522..0000000 --- a/example/transactions/spend_p2sh_to_p2pkh.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2shToP2pkh({ - required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2pkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2sh_to_p2sh.dart b/example/transactions/spend_p2sh_to_p2sh.dart deleted file mode 100644 index 5a89c94..0000000 --- a/example/transactions/spend_p2sh_to_p2sh.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -// from segwit to P2pkhAddress -// need segwit pub key -// output pay-to-pubkey-hash -// input pay-to-witness-pubkey-hash - -(String, String) spendP2shToP2sh({ - required ECPublic receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: receiver.toRedeemScript().toP2shScriptPubKey()) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2sh_to_p2wkh.dart b/example/transactions/spend_p2sh_to_p2wkh.dart deleted file mode 100644 index 4ce2e36..0000000 --- a/example/transactions/spend_p2sh_to_p2wkh.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2shToP2wpkh({ - required P2wpkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: senderPub.toRedeemScript().toP2shScriptPubKey())); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: false); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionDigest( - txInIndex: i, script: senderPub.toRedeemScript(), sighash: sighash); - final signedTx = sign(txDigit); - txin[i].scriptSig = - Script(script: [signedTx, senderPub.toRedeemScript().toHex()]); - } - - if (trSize == null) { - return spendP2shToP2wpkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2wkh_to_p2k.dart b/example/transactions/spend_p2wkh_to_p2k.dart deleted file mode 100644 index 1c51801..0000000 --- a/example/transactions/spend_p2wkh_to_p2k.dart +++ /dev/null @@ -1,85 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2wkhToP2pk( - {required P2pkAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2wpkhAddress? changeAddress}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendP2wkhToP2pk( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2wkh_to_p2kh.dart b/example/transactions/spend_p2wkh_to_p2kh.dart deleted file mode 100644 index 6b2072b..0000000 --- a/example/transactions/spend_p2wkh_to_p2kh.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/address.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -// from segwit to P2pkhAddress -// need segwit pub key -// output pay-to-pubkey-hash -// input pay-to-witness-pubkey-hash - -(String, String) spendP2wkhToP2kh( - {required P2pkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2wpkhAddress? changeAddress}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendP2wkhToP2kh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2wkh_to_p2sh.dart b/example/transactions/spend_p2wkh_to_p2sh.dart deleted file mode 100644 index c011c15..0000000 --- a/example/transactions/spend_p2wkh_to_p2sh.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendP2wkhToP2sh({ - required ECPublic receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: receiver.toRedeemScript().toP2shScriptPubKey()) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: - Script(script: senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendP2wkhToP2sh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/spend_p2wkh_to_p2wkh.dart b/example/transactions/spend_p2wkh_to_p2wkh.dart deleted file mode 100644 index 9dbe558..0000000 --- a/example/transactions/spend_p2wkh_to_p2wkh.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/bitcoin/address/segwit_address.dart'; -import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; -import 'package:bitcoin_base/src/bitcoin/script/input.dart'; -import 'package:bitcoin_base/src/bitcoin/script/output.dart'; -import 'package:bitcoin_base/src/bitcoin/script/script.dart'; -import 'package:bitcoin_base/src/bitcoin/script/transaction.dart'; -import 'package:bitcoin_base/src/bitcoin/script/witness.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_public.dart'; -import './utxo.dart'; - -(String, String) spendp2wkh({ - required P2wpkhAddress receiver, - required ECPublic senderPub, - required NetworkInfo networkType, - required String Function(Uint8List, {int sigHash}) sign, - required List utxo, - BigInt? value, - required BigInt estimateFee, - int? trSize, - int sighash = SIGHASH_ALL, - P2wpkhAddress? changeAddress, -}) { - int someBytes = 100 + (utxo.length * 100); - - final fee = BigInt.from((trSize ?? someBytes)) * estimateFee; - final BigInt sumUtxo = utxo.fold( - BigInt.zero, (previousValue, element) => previousValue + element.value); - BigInt mustSend = value ?? sumUtxo; - if (value == null) { - mustSend = sumUtxo - fee; - } else { - BigInt currentValue = value + fee; - if (trSize != null && sumUtxo < currentValue) { - throw Exception( - "need money balance $sumUtxo value + fee = $currentValue"); - } - } - if (mustSend.isNegative) { - throw Exception( - "your balance must >= transaction ${value ?? sumUtxo} + $fee"); - } - BigInt needChangeTx = sumUtxo - (mustSend + fee); - final txin = utxo.map((e) => TxInput(txId: e.txId, txIndex: e.vout)).toList(); - final List w = []; - final List txOut = [ - TxOutput( - amount: mustSend, - scriptPubKey: Script(script: receiver.toScriptPubKey())) - ]; - if (needChangeTx > BigInt.zero) { - txOut.add(TxOutput( - amount: needChangeTx, - scriptPubKey: Script( - script: changeAddress?.toScriptPubKey() ?? - senderPub.toSegwitAddress().toScriptPubKey()))); - } - final tx = BtcTransaction(inputs: txin, outputs: txOut, hasSegwit: true); - for (int i = 0; i < txin.length; i++) { - final txDigit = tx.getTransactionSegwitDigit( - txInIndex: i, - script: Script(script: senderPub.toAddress().toScriptPubKey()), - sighash: sighash, - amount: utxo[i].value); - final signedTx = sign(txDigit, sigHash: sighash); - w.add(TxWitnessInput(stack: [signedTx, senderPub.toHex()])); - } - - tx.witnesses.addAll(w); - if (trSize == null) { - return spendp2wkh( - estimateFee: estimateFee, - networkType: networkType, - receiver: receiver, - senderPub: senderPub, - sign: sign, - utxo: utxo, - value: value, - sighash: sighash, - trSize: tx.getVSize()); - } - return (tx.serialize(), tx.txId()); -} diff --git a/example/transactions/utxo.dart b/example/transactions/utxo.dart deleted file mode 100644 index 8953ffb..0000000 --- a/example/transactions/utxo.dart +++ /dev/null @@ -1,10 +0,0 @@ -class UTXO { - UTXO({required this.txId, required this.value, required this.vout}); - UTXO.fromJson(Map json) - : txId = json["txid"], - vout = json["vout"], - value = BigInt.parse(json["value"]); - final String txId; - final int vout; - final BigInt value; -} diff --git a/lib/bitcoin.dart b/lib/bitcoin.dart index b39a2cd..0a45d65 100644 --- a/lib/bitcoin.dart +++ b/lib/bitcoin.dart @@ -14,3 +14,5 @@ export 'package:bitcoin_base/src/bitcoin/script/control_block.dart'; export 'package:bitcoin_base/src/bitcoin/constant/constant_lib.dart'; export 'package:bitcoin_base/src/crypto/crypto.dart'; + +export 'package:blockchain_utils/blockchain_utils.dart'; diff --git a/lib/src/base58/base58.dart b/lib/src/base58/base58.dart deleted file mode 100644 index 45476d1..0000000 --- a/lib/src/base58/base58.dart +++ /dev/null @@ -1,106 +0,0 @@ -// import 'package:bitcoin/src/crypto/crypto.dart'; -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/crypto/crypto.dart'; -import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; -// import 'package:flutter/foundation.dart'; - -const String _btc = - '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; -final base58 = Base(_btc); - -class Base { - final String alphabet; - // ignore: non_constant_identifier_names - Map ALPHABET_MAP = {}; - late final int _base; - - late final String _leader; - - Base(this.alphabet) { - _base = alphabet.length; - _leader = (alphabet)[0]; - for (var i = 0; i < (alphabet).length; i++) { - ALPHABET_MAP[(alphabet)[i]] = i; - } - } - String encodeCheck(Uint8List bytes) { - Uint8List hash = doubleHash(bytes); - Uint8List combine = Uint8List.fromList( - [bytes, hash.sublist(0, 4)].expand((i) => i).toList(growable: false)); - return encode(combine); - } - - String encode(Uint8List source) { - if (source.isEmpty) { - return ""; - } - List digits = [0]; - - for (var i = 0; i < source.length; ++i) { - var carry = source[i]; - for (var j = 0; j < digits.length; ++j) { - carry += digits[j] << 8; - digits[j] = carry % _base; - carry = carry ~/ _base; - } - while (carry > 0) { - digits.add(carry % _base); - carry = carry ~/ _base; - } - } - var string = ""; - - // deal with leading zeros - for (var k = 0; source[k] == 0 && k < source.length - 1; ++k) { - string += _leader; - } - // convert digits to a string - for (var q = digits.length - 1; q >= 0; --q) { - string += alphabet[digits[q]]; - } - return string; - } - - Uint8List decode(String string) { - if (string.isEmpty) { - throw ArgumentError('Non-base$_base character'); - } - List bytes = [0]; - for (var i = 0; i < string.length; i++) { - var value = ALPHABET_MAP[string[i]]; - if (value == null) { - throw ArgumentError('Non-base$_base character'); - } - var carry = value; - for (var j = 0; j < bytes.length; ++j) { - carry += bytes[j] * _base; - bytes[j] = carry & 0xff; - carry >>= 8; - } - while (carry > 0) { - bytes.add(carry & 0xff); - carry >>= 8; - } - } - // deal with leading zeros - for (var k = 0; string[k] == _leader && k < string.length - 1; ++k) { - bytes.add(0); - } - return Uint8List.fromList(bytes.reversed.toList()); - } - - Uint8List decodeCheck(String string) { - final bytes = decode(string); - if (bytes.length < 5) { - throw const FormatException("invalid base58check"); - } - Uint8List payload = bytes.sublist(0, bytes.length - 4); - Uint8List checksum = bytes.sublist(bytes.length - 4); - Uint8List newChecksum = doubleHash(payload).sublist(0, 4); - if (!bytesListEqual(checksum, newChecksum)) { - throw ArgumentError("Invalid checksum"); - } - return payload; - } -} diff --git a/lib/src/bech32/bech32.dart b/lib/src/bech32/bech32.dart deleted file mode 100644 index 95f06da..0000000 --- a/lib/src/bech32/bech32.dart +++ /dev/null @@ -1,134 +0,0 @@ -import 'dart:typed_data'; - -import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; - -enum Bech32Type { - bech32(1), - bech32M(0x2bc830a3); - - final int value; - const Bech32Type(this.value); -} - -int _bech32Polymod(List values) { - List generator = [ - 0x3b6a57b2, - 0x26508e6d, - 0x1ea119fa, - 0x3d4233dd, - 0x2a1462b3 - ]; - int chk = 1; - for (int value in values) { - int top = chk >> 25; - chk = (chk & 0x1ffffff) << 5 ^ value; - for (int i = 0; i < 5; i++) { - chk ^= ((top >> i) & 1) != 0 ? generator[i] : 0; - } - } - return chk; -} - -List _bech32HrpExpand(String hrp) { - List values = []; - for (int i = 0; i < hrp.length; i++) { - values.add(hrp.codeUnitAt(i) >> 5); - } - values.add(0); - for (int i = 0; i < hrp.length; i++) { - values.add(hrp.codeUnitAt(i) & 31); - } - return values; -} - -Bech32Type? _bech32VerifyChecksum(String hrp, List data) { - List combined = _bech32HrpExpand(hrp) + data; - int c = _bech32Polymod(combined); - - if (c == Bech32Type.bech32.value) { - return Bech32Type.bech32; - } - if (c == Bech32Type.bech32M.value) { - return Bech32Type.bech32M; - } - return null; -} - -List _bech32CreateChecksum(String hrp, List data, Bech32Type spec) { - List values = _bech32HrpExpand(hrp) + data; - - int polymod = _bech32Polymod(values + [0, 0, 0, 0, 0, 0]) ^ spec.value; - List checksum = []; - for (int i = 0; i < 6; i++) { - checksum.add((polymod >> 5 * (5 - i)) & 31); - } - return checksum; -} - -const _charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; -String _bech32Encode(String hrp, List data, Bech32Type spec) { - List combined = data + _bech32CreateChecksum(hrp, data, spec); - String encoded = '${hrp}1${combined.map((d) => _charset[d]).join('')}'; - return encoded; -} - -(String, List, Bech32Type)? _bech32Decode(String bech) { - if (bech.runes.any((x) => x < 33 || x > 126) || - (bech.toLowerCase() != bech && bech.toUpperCase() != bech)) { - return null; - } - bech = bech.toLowerCase(); - int pos = bech.lastIndexOf('1'); - if (pos < 1 || pos + 7 > bech.length || bech.length > 90) { - return null; - } - if (!bech.substring(pos + 1).split('').every((x) => _charset.contains(x))) { - return null; - } - String hrp = bech.substring(0, pos); - List data = bech - .substring(pos + 1) - .split('') - .map((x) => _charset.indexOf(x)) - .toList(); - Bech32Type? spec = _bech32VerifyChecksum(hrp, data); - if (spec == null) { - return null; - } - return (hrp, data.sublist(0, data.length - 6), spec); -} - -(int, List)? decodeBech32(String address) { - final decodeBech = _bech32Decode(address); - if (decodeBech == null) return null; - final data = decodeBech.$2; - final bits = convertBits(data.sublist(1), 5, 8, pad: false); - if (bits == null || bits.length < 2 || bits.length > 40) { - return null; - } - if (data[0] > 16) { - return null; - } - if (data[0] == 0 && bits.length != 20 && bits.length != 32) { - return null; - } - final spec = decodeBech.$3; - if (data[0] == 0 && spec != Bech32Type.bech32 || - data[0] != 0 && spec != Bech32Type.bech32M) { - return null; - } - return (data[0], bits); -} - -String? encodeBech32(String hrp, int version, Uint8List data) { - final type = version == 0 ? Bech32Type.bech32 : Bech32Type.bech32M; - final bits = convertBits(data, 8, 5); - if (bits == null) { - return null; - } - final en = _bech32Encode(hrp, Uint16List.fromList([version, ...bits]), type); - if (decodeBech32(en) == null) { - return null; - } - return en; -} diff --git a/lib/src/bip39/bip39.dart b/lib/src/bip39/bip39.dart deleted file mode 100644 index b4f9850..0000000 --- a/lib/src/bip39/bip39.dart +++ /dev/null @@ -1,2149 +0,0 @@ -import 'dart:typed_data'; -import 'package:bitcoin_base/src/crypto/crypto.dart'; -import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; - -class BIP39 { - static String _deriveChecksumBits(Uint8List entropy) { - final ent = entropy.length * 8; - final cs = ent ~/ 32; - final hash = singleHash(entropy); - return bytesToBinary(hash).substring(0, cs); - } - - static String generateMnemonic({int strength = 128}) { - assert(strength % 32 == 0); - final entropy = generateRandom(size: strength ~/ 8); - return _entropyToMnemonic(bytesToHex(entropy)); - } - - static Uint8List toSeed(String mnemonic, {String passphrase = ""}) { - final String salt = "mnemonic$passphrase"; - return pbkdfDeriveDigest(mnemonic, salt); - } - - static String _entropyToMnemonic(String entropyString) { - final entropy = Uint8List.fromList(hexToBytes(entropyString)); - if (entropy.length < 16) { - throw ArgumentError("Invalid entropy"); - } - if (entropy.length > 32) { - throw ArgumentError("Invalid entropy"); - } - if (entropy.length % 4 != 0) { - throw ArgumentError("Invalid entropy"); - } - final entropyBits = bytesToBinary(entropy); - final checksumBits = _deriveChecksumBits(entropy); - final bits = entropyBits + checksumBits; - final regex = RegExp(r".{1,11}", caseSensitive: false, multiLine: false); - final chunks = regex - .allMatches(bits) - .map((match) => match.group(0)!) - .toList(growable: false); - - String words = - chunks.map((binary) => wordlist[binaryToByte(binary)]).join(' '); - return words; - } - - static bool validateMnemonic(String mnemonic) { - try { - _mnemonicToEntropy(mnemonic); - } catch (e) { - return false; - } - return true; - } - - static String _mnemonicToEntropy(String mnemonic) { - List words = mnemonic.split(' '); - if (words.length % 3 != 0) { - throw ArgumentError('Invalid mnemonic'); - } - - // convert word indices to 11 bit binary strings - final bits = words.map((word) { - final index = wordlist.indexOf(word); - if (index == -1) { - throw ArgumentError('Invalid mnemonic'); - } - return index.toRadixString(2).padLeft(11, '0'); - }).join(''); - final dividerIndex = (bits.length / 33).floor() * 32; - final entropyBits = bits.substring(0, dividerIndex); - final checksumBits = bits.substring(dividerIndex); - - final regex = RegExp(r".{1,8}"); - final entropyBytes = Uint8List.fromList(regex - .allMatches(entropyBits) - .map((match) => binaryToByte(match.group(0)!)) - .toList(growable: false)); - if (entropyBytes.length < 16) { - throw StateError("Invalid entropy"); - } - if (entropyBytes.length > 32) { - throw StateError("Invalid entropy"); - } - if (entropyBytes.length % 4 != 0) { - throw StateError("Invalid entropy"); - } - final newChecksum = _deriveChecksumBits(entropyBytes); - if (newChecksum != checksumBits) { - throw StateError("Invalid mnemonic checksum"); - } - return entropyBytes.map((byte) { - return byte.toRadixString(16).padLeft(2, '0'); - }).join(''); - } - - static const wordlist = [ - "abandon", - "ability", - "able", - "about", - "above", - "absent", - "absorb", - "abstract", - "absurd", - "abuse", - "access", - "accident", - "account", - "accuse", - "achieve", - "acid", - "acoustic", - "acquire", - "across", - "act", - "action", - "actor", - "actress", - "actual", - "adapt", - "add", - "addict", - "address", - "adjust", - "admit", - "adult", - "advance", - "advice", - "aerobic", - "affair", - "afford", - "afraid", - "again", - "age", - "agent", - "agree", - "ahead", - "aim", - "air", - "airport", - "aisle", - "alarm", - "album", - "alcohol", - "alert", - "alien", - "all", - "alley", - "allow", - "almost", - "alone", - "alpha", - "already", - "also", - "alter", - "always", - "amateur", - "amazing", - "among", - "amount", - "amused", - "analyst", - "anchor", - "ancient", - "anger", - "angle", - "angry", - "animal", - "ankle", - "announce", - "annual", - "another", - "answer", - "antenna", - "antique", - "anxiety", - "any", - "apart", - "apology", - "appear", - "apple", - "approve", - "april", - "arch", - "arctic", - "area", - "arena", - "argue", - "arm", - "armed", - "armor", - "army", - "around", - "arrange", - "arrest", - "arrive", - "arrow", - "art", - "artefact", - "artist", - "artwork", - "ask", - "aspect", - "assault", - "asset", - "assist", - "assume", - "asthma", - "athlete", - "atom", - "attack", - "attend", - "attitude", - "attract", - "auction", - "audit", - "august", - "aunt", - "author", - "auto", - "autumn", - "average", - "avocado", - "avoid", - "awake", - "aware", - "away", - "awesome", - "awful", - "awkward", - "axis", - "baby", - "bachelor", - "bacon", - "badge", - "bag", - "balance", - "balcony", - "ball", - "bamboo", - "banana", - "banner", - "bar", - "barely", - "bargain", - "barrel", - "base", - "basic", - "basket", - "battle", - "beach", - "bean", - "beauty", - "because", - "become", - "beef", - "before", - "begin", - "behave", - "behind", - "believe", - "below", - "belt", - "bench", - "benefit", - "best", - "betray", - "better", - "between", - "beyond", - "bicycle", - "bid", - "bike", - "bind", - "biology", - "bird", - "birth", - "bitter", - "black", - "blade", - "blame", - "blanket", - "blast", - "bleak", - "bless", - "blind", - "blood", - "blossom", - "blouse", - "blue", - "blur", - "blush", - "board", - "boat", - "body", - "boil", - "bomb", - "bone", - "bonus", - "book", - "boost", - "border", - "boring", - "borrow", - "boss", - "bottom", - "bounce", - "box", - "boy", - "bracket", - "brain", - "brand", - "brass", - "brave", - "bread", - "breeze", - "brick", - "bridge", - "brief", - "bright", - "bring", - "brisk", - "broccoli", - "broken", - "bronze", - "broom", - "brother", - "brown", - "brush", - "bubble", - "buddy", - "budget", - "buffalo", - "build", - "bulb", - "bulk", - "bullet", - "bundle", - "bunker", - "burden", - "burger", - "burst", - "bus", - "business", - "busy", - "butter", - "buyer", - "buzz", - "cabbage", - "cabin", - "cable", - "cactus", - "cage", - "cake", - "call", - "calm", - "camera", - "camp", - "can", - "canal", - "cancel", - "candy", - "cannon", - "canoe", - "canvas", - "canyon", - "capable", - "capital", - "captain", - "car", - "carbon", - "card", - "cargo", - "carpet", - "carry", - "cart", - "case", - "cash", - "casino", - "castle", - "casual", - "cat", - "catalog", - "catch", - "category", - "cattle", - "caught", - "cause", - "caution", - "cave", - "ceiling", - "celery", - "cement", - "census", - "century", - "cereal", - "certain", - "chair", - "chalk", - "champion", - "change", - "chaos", - "chapter", - "charge", - "chase", - "chat", - "cheap", - "check", - "cheese", - "chef", - "cherry", - "chest", - "chicken", - "chief", - "child", - "chimney", - "choice", - "choose", - "chronic", - "chuckle", - "chunk", - "churn", - "cigar", - "cinnamon", - "circle", - "citizen", - "city", - "civil", - "claim", - "clap", - "clarify", - "claw", - "clay", - "clean", - "clerk", - "clever", - "click", - "client", - "cliff", - "climb", - "clinic", - "clip", - "clock", - "clog", - "close", - "cloth", - "cloud", - "clown", - "club", - "clump", - "cluster", - "clutch", - "coach", - "coast", - "coconut", - "code", - "coffee", - "coil", - "coin", - "collect", - "color", - "column", - "combine", - "come", - "comfort", - "comic", - "common", - "company", - "concert", - "conduct", - "confirm", - "congress", - "connect", - "consider", - "control", - "convince", - "cook", - "cool", - "copper", - "copy", - "coral", - "core", - "corn", - "correct", - "cost", - "cotton", - "couch", - "country", - "couple", - "course", - "cousin", - "cover", - "coyote", - "crack", - "cradle", - "craft", - "cram", - "crane", - "crash", - "crater", - "crawl", - "crazy", - "cream", - "credit", - "creek", - "crew", - "cricket", - "crime", - "crisp", - "critic", - "crop", - "cross", - "crouch", - "crowd", - "crucial", - "cruel", - "cruise", - "crumble", - "crunch", - "crush", - "cry", - "crystal", - "cube", - "culture", - "cup", - "cupboard", - "curious", - "current", - "curtain", - "curve", - "cushion", - "custom", - "cute", - "cycle", - "dad", - "damage", - "damp", - "dance", - "danger", - "daring", - "dash", - "daughter", - "dawn", - "day", - "deal", - "debate", - "debris", - "decade", - "december", - "decide", - "decline", - "decorate", - "decrease", - "deer", - "defense", - "define", - "defy", - "degree", - "delay", - "deliver", - "demand", - "demise", - "denial", - "dentist", - "deny", - "depart", - "depend", - "deposit", - "depth", - "deputy", - "derive", - "describe", - "desert", - "design", - "desk", - "despair", - "destroy", - "detail", - "detect", - "develop", - "device", - "devote", - "diagram", - "dial", - "diamond", - "diary", - "dice", - "diesel", - "diet", - "differ", - "digital", - "dignity", - "dilemma", - "dinner", - "dinosaur", - "direct", - "dirt", - "disagree", - "discover", - "disease", - "dish", - "dismiss", - "disorder", - "display", - "distance", - "divert", - "divide", - "divorce", - "dizzy", - "doctor", - "document", - "dog", - "doll", - "dolphin", - "domain", - "donate", - "donkey", - "donor", - "door", - "dose", - "double", - "dove", - "draft", - "dragon", - "drama", - "drastic", - "draw", - "dream", - "dress", - "drift", - "drill", - "drink", - "drip", - "drive", - "drop", - "drum", - "dry", - "duck", - "dumb", - "dune", - "during", - "dust", - "dutch", - "duty", - "dwarf", - "dynamic", - "eager", - "eagle", - "early", - "earn", - "earth", - "easily", - "east", - "easy", - "echo", - "ecology", - "economy", - "edge", - "edit", - "educate", - "effort", - "egg", - "eight", - "either", - "elbow", - "elder", - "electric", - "elegant", - "element", - "elephant", - "elevator", - "elite", - "else", - "embark", - "embody", - "embrace", - "emerge", - "emotion", - "employ", - "empower", - "empty", - "enable", - "enact", - "end", - "endless", - "endorse", - "enemy", - "energy", - "enforce", - "engage", - "engine", - "enhance", - "enjoy", - "enlist", - "enough", - "enrich", - "enroll", - "ensure", - "enter", - "entire", - "entry", - "envelope", - "episode", - "equal", - "equip", - "era", - "erase", - "erode", - "erosion", - "error", - "erupt", - "escape", - "essay", - "essence", - "estate", - "eternal", - "ethics", - "evidence", - "evil", - "evoke", - "evolve", - "exact", - "example", - "excess", - "exchange", - "excite", - "exclude", - "excuse", - "execute", - "exercise", - "exhaust", - "exhibit", - "exile", - "exist", - "exit", - "exotic", - "expand", - "expect", - "expire", - "explain", - "expose", - "express", - "extend", - "extra", - "eye", - "eyebrow", - "fabric", - "face", - "faculty", - "fade", - "faint", - "faith", - "fall", - "false", - "fame", - "family", - "famous", - "fan", - "fancy", - "fantasy", - "farm", - "fashion", - "fat", - "fatal", - "father", - "fatigue", - "fault", - "favorite", - "feature", - "february", - "federal", - "fee", - "feed", - "feel", - "female", - "fence", - "festival", - "fetch", - "fever", - "few", - "fiber", - "fiction", - "field", - "figure", - "file", - "film", - "filter", - "final", - "find", - "fine", - "finger", - "finish", - "fire", - "firm", - "first", - "fiscal", - "fish", - "fit", - "fitness", - "fix", - "flag", - "flame", - "flash", - "flat", - "flavor", - "flee", - "flight", - "flip", - "float", - "flock", - "floor", - "flower", - "fluid", - "flush", - "fly", - "foam", - "focus", - "fog", - "foil", - "fold", - "follow", - "food", - "foot", - "force", - "forest", - "forget", - "fork", - "fortune", - "forum", - "forward", - "fossil", - "foster", - "found", - "fox", - "fragile", - "frame", - "frequent", - "fresh", - "friend", - "fringe", - "frog", - "front", - "frost", - "frown", - "frozen", - "fruit", - "fuel", - "fun", - "funny", - "furnace", - "fury", - "future", - "gadget", - "gain", - "galaxy", - "gallery", - "game", - "gap", - "garage", - "garbage", - "garden", - "garlic", - "garment", - "gas", - "gasp", - "gate", - "gather", - "gauge", - "gaze", - "general", - "genius", - "genre", - "gentle", - "genuine", - "gesture", - "ghost", - "giant", - "gift", - "giggle", - "ginger", - "giraffe", - "girl", - "give", - "glad", - "glance", - "glare", - "glass", - "glide", - "glimpse", - "globe", - "gloom", - "glory", - "glove", - "glow", - "glue", - "goat", - "goddess", - "gold", - "good", - "goose", - "gorilla", - "gospel", - "gossip", - "govern", - "gown", - "grab", - "grace", - "grain", - "grant", - "grape", - "grass", - "gravity", - "great", - "green", - "grid", - "grief", - "grit", - "grocery", - "group", - "grow", - "grunt", - "guard", - "guess", - "guide", - "guilt", - "guitar", - "gun", - "gym", - "habit", - "hair", - "half", - "hammer", - "hamster", - "hand", - "happy", - "harbor", - "hard", - "harsh", - "harvest", - "hat", - "have", - "hawk", - "hazard", - "head", - "health", - "heart", - "heavy", - "hedgehog", - "height", - "hello", - "helmet", - "help", - "hen", - "hero", - "hidden", - "high", - "hill", - "hint", - "hip", - "hire", - "history", - "hobby", - "hockey", - "hold", - "hole", - "holiday", - "hollow", - "home", - "honey", - "hood", - "hope", - "horn", - "horror", - "horse", - "hospital", - "host", - "hotel", - "hour", - "hover", - "hub", - "huge", - "human", - "humble", - "humor", - "hundred", - "hungry", - "hunt", - "hurdle", - "hurry", - "hurt", - "husband", - "hybrid", - "ice", - "icon", - "idea", - "identify", - "idle", - "ignore", - "ill", - "illegal", - "illness", - "image", - "imitate", - "immense", - "immune", - "impact", - "impose", - "improve", - "impulse", - "inch", - "include", - "income", - "increase", - "index", - "indicate", - "indoor", - "industry", - "infant", - "inflict", - "inform", - "inhale", - "inherit", - "initial", - "inject", - "injury", - "inmate", - "inner", - "innocent", - "input", - "inquiry", - "insane", - "insect", - "inside", - "inspire", - "install", - "intact", - "interest", - "into", - "invest", - "invite", - "involve", - "iron", - "island", - "isolate", - "issue", - "item", - "ivory", - "jacket", - "jaguar", - "jar", - "jazz", - "jealous", - "jeans", - "jelly", - "jewel", - "job", - "join", - "joke", - "journey", - "joy", - "judge", - "juice", - "jump", - "jungle", - "junior", - "junk", - "just", - "kangaroo", - "keen", - "keep", - "ketchup", - "key", - "kick", - "kid", - "kidney", - "kind", - "kingdom", - "kiss", - "kit", - "kitchen", - "kite", - "kitten", - "kiwi", - "knee", - "knife", - "knock", - "know", - "lab", - "label", - "labor", - "ladder", - "lady", - "lake", - "lamp", - "language", - "laptop", - "large", - "later", - "latin", - "laugh", - "laundry", - "lava", - "law", - "lawn", - "lawsuit", - "layer", - "lazy", - "leader", - "leaf", - "learn", - "leave", - "lecture", - "left", - "leg", - "legal", - "legend", - "leisure", - "lemon", - "lend", - "length", - "lens", - "leopard", - "lesson", - "letter", - "level", - "liar", - "liberty", - "library", - "license", - "life", - "lift", - "light", - "like", - "limb", - "limit", - "link", - "lion", - "liquid", - "list", - "little", - "live", - "lizard", - "load", - "loan", - "lobster", - "local", - "lock", - "logic", - "lonely", - "long", - "loop", - "lottery", - "loud", - "lounge", - "love", - "loyal", - "lucky", - "luggage", - "lumber", - "lunar", - "lunch", - "luxury", - "lyrics", - "machine", - "mad", - "magic", - "magnet", - "maid", - "mail", - "main", - "major", - "make", - "mammal", - "man", - "manage", - "mandate", - "mango", - "mansion", - "manual", - "maple", - "marble", - "march", - "margin", - "marine", - "market", - "marriage", - "mask", - "mass", - "master", - "match", - "material", - "math", - "matrix", - "matter", - "maximum", - "maze", - "meadow", - "mean", - "measure", - "meat", - "mechanic", - "medal", - "media", - "melody", - "melt", - "member", - "memory", - "mention", - "menu", - "mercy", - "merge", - "merit", - "merry", - "mesh", - "message", - "metal", - "method", - "middle", - "midnight", - "milk", - "million", - "mimic", - "mind", - "minimum", - "minor", - "minute", - "miracle", - "mirror", - "misery", - "miss", - "mistake", - "mix", - "mixed", - "mixture", - "mobile", - "model", - "modify", - "mom", - "moment", - "monitor", - "monkey", - "monster", - "month", - "moon", - "moral", - "more", - "morning", - "mosquito", - "mother", - "motion", - "motor", - "mountain", - "mouse", - "move", - "movie", - "much", - "muffin", - "mule", - "multiply", - "muscle", - "museum", - "mushroom", - "music", - "must", - "mutual", - "myself", - "mystery", - "myth", - "naive", - "name", - "napkin", - "narrow", - "nasty", - "nation", - "nature", - "near", - "neck", - "need", - "negative", - "neglect", - "neither", - "nephew", - "nerve", - "nest", - "net", - "network", - "neutral", - "never", - "news", - "next", - "nice", - "night", - "noble", - "noise", - "nominee", - "noodle", - "normal", - "north", - "nose", - "notable", - "note", - "nothing", - "notice", - "novel", - "now", - "nuclear", - "number", - "nurse", - "nut", - "oak", - "obey", - "object", - "oblige", - "obscure", - "observe", - "obtain", - "obvious", - "occur", - "ocean", - "october", - "odor", - "off", - "offer", - "office", - "often", - "oil", - "okay", - "old", - "olive", - "olympic", - "omit", - "once", - "one", - "onion", - "online", - "only", - "open", - "opera", - "opinion", - "oppose", - "option", - "orange", - "orbit", - "orchard", - "order", - "ordinary", - "organ", - "orient", - "original", - "orphan", - "ostrich", - "other", - "outdoor", - "outer", - "output", - "outside", - "oval", - "oven", - "over", - "own", - "owner", - "oxygen", - "oyster", - "ozone", - "pact", - "paddle", - "page", - "pair", - "palace", - "palm", - "panda", - "panel", - "panic", - "panther", - "paper", - "parade", - "parent", - "park", - "parrot", - "party", - "pass", - "patch", - "path", - "patient", - "patrol", - "pattern", - "pause", - "pave", - "payment", - "peace", - "peanut", - "pear", - "peasant", - "pelican", - "pen", - "penalty", - "pencil", - "people", - "pepper", - "perfect", - "permit", - "person", - "pet", - "phone", - "photo", - "phrase", - "physical", - "piano", - "picnic", - "picture", - "piece", - "pig", - "pigeon", - "pill", - "pilot", - "pink", - "pioneer", - "pipe", - "pistol", - "pitch", - "pizza", - "place", - "planet", - "plastic", - "plate", - "play", - "please", - "pledge", - "pluck", - "plug", - "plunge", - "poem", - "poet", - "point", - "polar", - "pole", - "police", - "pond", - "pony", - "pool", - "popular", - "portion", - "position", - "possible", - "post", - "potato", - "pottery", - "poverty", - "powder", - "power", - "practice", - "praise", - "predict", - "prefer", - "prepare", - "present", - "pretty", - "prevent", - "price", - "pride", - "primary", - "print", - "priority", - "prison", - "private", - "prize", - "problem", - "process", - "produce", - "profit", - "program", - "project", - "promote", - "proof", - "property", - "prosper", - "protect", - "proud", - "provide", - "public", - "pudding", - "pull", - "pulp", - "pulse", - "pumpkin", - "punch", - "pupil", - "puppy", - "purchase", - "purity", - "purpose", - "purse", - "push", - "put", - "puzzle", - "pyramid", - "quality", - "quantum", - "quarter", - "question", - "quick", - "quit", - "quiz", - "quote", - "rabbit", - "raccoon", - "race", - "rack", - "radar", - "radio", - "rail", - "rain", - "raise", - "rally", - "ramp", - "ranch", - "random", - "range", - "rapid", - "rare", - "rate", - "rather", - "raven", - "raw", - "razor", - "ready", - "real", - "reason", - "rebel", - "rebuild", - "recall", - "receive", - "recipe", - "record", - "recycle", - "reduce", - "reflect", - "reform", - "refuse", - "region", - "regret", - "regular", - "reject", - "relax", - "release", - "relief", - "rely", - "remain", - "remember", - "remind", - "remove", - "render", - "renew", - "rent", - "reopen", - "repair", - "repeat", - "replace", - "report", - "require", - "rescue", - "resemble", - "resist", - "resource", - "response", - "result", - "retire", - "retreat", - "return", - "reunion", - "reveal", - "review", - "reward", - "rhythm", - "rib", - "ribbon", - "rice", - "rich", - "ride", - "ridge", - "rifle", - "right", - "rigid", - "ring", - "riot", - "ripple", - "risk", - "ritual", - "rival", - "river", - "road", - "roast", - "robot", - "robust", - "rocket", - "romance", - "roof", - "rookie", - "room", - "rose", - "rotate", - "rough", - "round", - "route", - "royal", - "rubber", - "rude", - "rug", - "rule", - "run", - "runway", - "rural", - "sad", - "saddle", - "sadness", - "safe", - "sail", - "salad", - "salmon", - "salon", - "salt", - "salute", - "same", - "sample", - "sand", - "satisfy", - "satoshi", - "sauce", - "sausage", - "save", - "say", - "scale", - "scan", - "scare", - "scatter", - "scene", - "scheme", - "school", - "science", - "scissors", - "scorpion", - "scout", - "scrap", - "screen", - "script", - "scrub", - "sea", - "search", - "season", - "seat", - "second", - "secret", - "section", - "security", - "seed", - "seek", - "segment", - "select", - "sell", - "seminar", - "senior", - "sense", - "sentence", - "series", - "service", - "session", - "settle", - "setup", - "seven", - "shadow", - "shaft", - "shallow", - "share", - "shed", - "shell", - "sheriff", - "shield", - "shift", - "shine", - "ship", - "shiver", - "shock", - "shoe", - "shoot", - "shop", - "short", - "shoulder", - "shove", - "shrimp", - "shrug", - "shuffle", - "shy", - "sibling", - "sick", - "side", - "siege", - "sight", - "sign", - "silent", - "silk", - "silly", - "silver", - "similar", - "simple", - "since", - "sing", - "siren", - "sister", - "situate", - "six", - "size", - "skate", - "sketch", - "ski", - "skill", - "skin", - "skirt", - "skull", - "slab", - "slam", - "sleep", - "slender", - "slice", - "slide", - "slight", - "slim", - "slogan", - "slot", - "slow", - "slush", - "small", - "smart", - "smile", - "smoke", - "smooth", - "snack", - "snake", - "snap", - "sniff", - "snow", - "soap", - "soccer", - "social", - "sock", - "soda", - "soft", - "solar", - "soldier", - "solid", - "solution", - "solve", - "someone", - "song", - "soon", - "sorry", - "sort", - "soul", - "sound", - "soup", - "source", - "south", - "space", - "spare", - "spatial", - "spawn", - "speak", - "special", - "speed", - "spell", - "spend", - "sphere", - "spice", - "spider", - "spike", - "spin", - "spirit", - "split", - "spoil", - "sponsor", - "spoon", - "sport", - "spot", - "spray", - "spread", - "spring", - "spy", - "square", - "squeeze", - "squirrel", - "stable", - "stadium", - "staff", - "stage", - "stairs", - "stamp", - "stand", - "start", - "state", - "stay", - "steak", - "steel", - "stem", - "step", - "stereo", - "stick", - "still", - "sting", - "stock", - "stomach", - "stone", - "stool", - "story", - "stove", - "strategy", - "street", - "strike", - "strong", - "struggle", - "student", - "stuff", - "stumble", - "style", - "subject", - "submit", - "subway", - "success", - "such", - "sudden", - "suffer", - "sugar", - "suggest", - "suit", - "summer", - "sun", - "sunny", - "sunset", - "super", - "supply", - "supreme", - "sure", - "surface", - "surge", - "surprise", - "surround", - "survey", - "suspect", - "sustain", - "swallow", - "swamp", - "swap", - "swarm", - "swear", - "sweet", - "swift", - "swim", - "swing", - "switch", - "sword", - "symbol", - "symptom", - "syrup", - "system", - "table", - "tackle", - "tag", - "tail", - "talent", - "talk", - "tank", - "tape", - "target", - "task", - "taste", - "tattoo", - "taxi", - "teach", - "team", - "tell", - "ten", - "tenant", - "tennis", - "tent", - "term", - "test", - "text", - "thank", - "that", - "theme", - "then", - "theory", - "there", - "they", - "thing", - "this", - "thought", - "three", - "thrive", - "throw", - "thumb", - "thunder", - "ticket", - "tide", - "tiger", - "tilt", - "timber", - "time", - "tiny", - "tip", - "tired", - "tissue", - "title", - "toast", - "tobacco", - "today", - "toddler", - "toe", - "together", - "toilet", - "token", - "tomato", - "tomorrow", - "tone", - "tongue", - "tonight", - "tool", - "tooth", - "top", - "topic", - "topple", - "torch", - "tornado", - "tortoise", - "toss", - "total", - "tourist", - "toward", - "tower", - "town", - "toy", - "track", - "trade", - "traffic", - "tragic", - "train", - "transfer", - "trap", - "trash", - "travel", - "tray", - "treat", - "tree", - "trend", - "trial", - "tribe", - "trick", - "trigger", - "trim", - "trip", - "trophy", - "trouble", - "truck", - "true", - "truly", - "trumpet", - "trust", - "truth", - "try", - "tube", - "tuition", - "tumble", - "tuna", - "tunnel", - "turkey", - "turn", - "turtle", - "twelve", - "twenty", - "twice", - "twin", - "twist", - "two", - "type", - "typical", - "ugly", - "umbrella", - "unable", - "unaware", - "uncle", - "uncover", - "under", - "undo", - "unfair", - "unfold", - "unhappy", - "uniform", - "unique", - "unit", - "universe", - "unknown", - "unlock", - "until", - "unusual", - "unveil", - "update", - "upgrade", - "uphold", - "upon", - "upper", - "upset", - "urban", - "urge", - "usage", - "use", - "used", - "useful", - "useless", - "usual", - "utility", - "vacant", - "vacuum", - "vague", - "valid", - "valley", - "valve", - "van", - "vanish", - "vapor", - "various", - "vast", - "vault", - "vehicle", - "velvet", - "vendor", - "venture", - "venue", - "verb", - "verify", - "version", - "very", - "vessel", - "veteran", - "viable", - "vibrant", - "vicious", - "victory", - "video", - "view", - "village", - "vintage", - "violin", - "virtual", - "virus", - "visa", - "visit", - "visual", - "vital", - "vivid", - "vocal", - "voice", - "void", - "volcano", - "volume", - "vote", - "voyage", - "wage", - "wagon", - "wait", - "walk", - "wall", - "walnut", - "want", - "warfare", - "warm", - "warrior", - "wash", - "wasp", - "waste", - "water", - "wave", - "way", - "wealth", - "weapon", - "wear", - "weasel", - "weather", - "web", - "wedding", - "weekend", - "weird", - "welcome", - "west", - "wet", - "whale", - "what", - "wheat", - "wheel", - "when", - "where", - "whip", - "whisper", - "wide", - "width", - "wife", - "wild", - "will", - "win", - "window", - "wine", - "wing", - "wink", - "winner", - "winter", - "wire", - "wisdom", - "wise", - "wish", - "witness", - "wolf", - "woman", - "wonder", - "wood", - "wool", - "word", - "work", - "world", - "worry", - "worth", - "wrap", - "wreck", - "wrestle", - "wrist", - "write", - "wrong", - "yard", - "year", - "yellow", - "you", - "young", - "youth", - "zebra", - "zero", - "zone", - "zoo" - ]; -} diff --git a/lib/src/bitcoin/address/address.dart b/lib/src/bitcoin/address/address.dart index 2844cc9..9e0bb95 100644 --- a/lib/src/bitcoin/address/address.dart +++ b/lib/src/bitcoin/address/address.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; -import 'package:bitcoin_base/src/base58/base58.dart' as bs58; +// import 'package:bitcoin_base/src/base58/base58.dart' as bs58; import 'package:bitcoin_base/src/bitcoin/address/core.dart'; import 'package:bitcoin_base/src/bitcoin/script/script.dart'; import 'package:bitcoin_base/src/bitcoin/tools/tools.dart'; @@ -8,6 +8,7 @@ import 'package:bitcoin_base/src/crypto/crypto.dart'; import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; import 'package:bitcoin_base/src/models/network.dart'; import 'package:bitcoin_base/src/crypto/ec/ec_encryption.dart' as ecc; +import 'package:blockchain_utils/base58/base58.dart' as bs58; abstract class BipAddress implements BitcoinAddress { /// Represents a Bitcoin address @@ -43,8 +44,8 @@ abstract class BipAddress implements BitcoinAddress { } static String _addressToHash160(String address) { - final dec = bs58.base58.decode(address); - return bytesToHex(dec.sublist(1, dec.length - 4)); + final decode = bs58.decodeCheck(address); + return bytesToHex(decode.sublist(1)); } static String _scriptToHash160(Script s) { @@ -54,12 +55,14 @@ abstract class BipAddress implements BitcoinAddress { } /// returns the address's string encoding + @override String toAddress(NetworkInfo networkType, {Uint8List? h160}) { Uint8List tobytes = h160 ?? hexToBytes(_h160); switch (type) { case AddressType.p2wpkhInP2sh: case AddressType.p2wshInP2sh: - case AddressType.p2sh: + case AddressType.p2pkhInP2sh: + case AddressType.p2pkInP2sh: tobytes = Uint8List.fromList([networkType.p2shPrefix, ...tobytes]); break; case const (AddressType.p2pkh) || const (AddressType.p2pk): @@ -67,19 +70,17 @@ abstract class BipAddress implements BitcoinAddress { break; default: } - Uint8List hash = doubleHash(tobytes); - hash = Uint8List.fromList( - [tobytes, hash.sublist(0, 4)].expand((i) => i).toList(growable: false)); - return bs58.base58.encode(hash); + return bs58.encodeCheck(tobytes); } } class P2shAddress extends BipAddress { /// Encapsulates a P2SH address. P2shAddress({super.address, super.hash160, super.script}) - : type = AddressType.p2sh; - P2shAddress.fromSegwitScript({super.script, this.type = AddressType.p2sh}) - : assert(type == AddressType.p2sh || + : type = AddressType.p2pkInP2sh; + P2shAddress.fromScript({super.script, this.type = AddressType.p2pkInP2sh}) + : assert(type == AddressType.p2pkInP2sh || + type == AddressType.p2pkhInP2sh || type == AddressType.p2wpkhInP2sh || type == AddressType.p2wshInP2sh); @@ -88,8 +89,8 @@ class P2shAddress extends BipAddress { /// Returns the scriptPubKey (P2SH) that corresponds to this address @override - List toScriptPubKey() { - return ['OP_HASH160', _h160, 'OP_EQUAL']; + Script toScriptPubKey() { + return Script(script: ['OP_HASH160', _h160, 'OP_EQUAL']); } } @@ -98,8 +99,14 @@ class P2pkhAddress extends BipAddress { /// Returns the scriptPubKey (P2SH) that corresponds to this address @override - List toScriptPubKey() { - return ['OP_DUP', 'OP_HASH160', _h160, 'OP_EQUALVERIFY', 'OP_CHECKSIG']; + Script toScriptPubKey() { + return Script(script: [ + 'OP_DUP', + 'OP_HASH160', + _h160, + 'OP_EQUALVERIFY', + 'OP_CHECKSIG' + ]); } @override @@ -118,8 +125,8 @@ class P2pkAddress extends BipAddress { /// Returns the scriptPubKey (P2SH) that corresponds to this address @override - List toScriptPubKey() { - return [publicHex, 'OP_CHECKSIG']; + Script toScriptPubKey() { + return Script(script: [publicHex, 'OP_CHECKSIG']); } @override diff --git a/lib/src/bitcoin/address/core.dart b/lib/src/bitcoin/address/core.dart index 5ccc2a9..64de708 100644 --- a/lib/src/bitcoin/address/core.dart +++ b/lib/src/bitcoin/address/core.dart @@ -1,16 +1,20 @@ +import 'package:bitcoin_base/bitcoin.dart'; +import 'package:bitcoin_base/src/models/network.dart'; + enum AddressType { p2pkh, - p2sh, p2wpkh, p2pk, p2tr, p2wsh, p2wshInP2sh, - p2wpkhInP2sh + p2wpkhInP2sh, + p2pkhInP2sh, + p2pkInP2sh } abstract class BitcoinAddress { AddressType get type; - - List toScriptPubKey(); + Script toScriptPubKey(); + String toAddress(NetworkInfo networkType); } diff --git a/lib/src/bitcoin/address/segwit_address.dart b/lib/src/bitcoin/address/segwit_address.dart index 37ce6d5..e22a551 100644 --- a/lib/src/bitcoin/address/segwit_address.dart +++ b/lib/src/bitcoin/address/segwit_address.dart @@ -1,4 +1,3 @@ -import 'package:bitcoin_base/src/bech32/bech32.dart' as bech32; import 'package:bitcoin_base/src/crypto/crypto.dart'; import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; @@ -6,6 +5,7 @@ import 'package:bitcoin_base/src/models/network.dart'; import 'package:bitcoin_base/src/bitcoin/address/core.dart'; import 'package:bitcoin_base/src/bitcoin/constant/constant.dart'; import 'package:bitcoin_base/src/bitcoin/script/script.dart'; +import 'package:blockchain_utils/bech32/bech32.dart' as bech32; abstract class SegwitAddress implements BitcoinAddress { /// Represents a Bitcoin segwit address @@ -46,17 +46,18 @@ abstract class SegwitAddress implements BitcoinAddress { if (convert == null) { throw ArgumentError("Invalid value for parameter address."); } - final version = convert.$1; + final version = convert.version; if (version != segwitNumVersion) { throw ArgumentError("Invalid segwit version."); } - return bytesToHex(convert.$2); + return bytesToHex(convert.data); } /// returns the address's string encoding (Bech32) + @override String toAddress(NetworkInfo networkType) { final bytes = hexToBytes(_program); - final sw = bech32.encodeBech32(networkType.bech32, segwitNumVersion, bytes); + final sw = bech32.encodeBech32(networkType.bech32, bytes, segwitNumVersion); if (sw == null) { throw ArgumentError("invalid address"); } @@ -64,10 +65,10 @@ abstract class SegwitAddress implements BitcoinAddress { return sw; } - String _scriptToHash(Script s) { - final toBytes = s.toBytes(); - final h = singleHash(toBytes); - return bytesToHex(h); + String _scriptToHash(Script script) { + final toBytes = script.toBytes(); + final toHash = singleHash(toBytes); + return bytesToHex(toHash); } } @@ -78,8 +79,8 @@ class P2wpkhAddress extends SegwitAddress { /// returns the scriptPubKey of a P2WPKH witness script @override - List toScriptPubKey() { - return ['OP_0', _program]; + Script toScriptPubKey() { + return Script(script: ['OP_0', _program]); } /// returns the type of address @@ -96,8 +97,8 @@ class P2trAddress extends SegwitAddress { /// returns the scriptPubKey of a P2TR witness script @override - List toScriptPubKey() { - return ['OP_1', _program]; + Script toScriptPubKey() { + return Script(script: ['OP_1', _program]); } /// returns the type of address @@ -107,12 +108,13 @@ class P2trAddress extends SegwitAddress { class P2wshAddress extends SegwitAddress { /// Encapsulates a P2WSH address. - P2wshAddress({required super.script}) : super(version: P2WSH_ADDRESS_V0); + P2wshAddress({super.script, super.address}) + : super(version: P2WSH_ADDRESS_V0); /// Returns the scriptPubKey of a P2WPKH witness script @override - List toScriptPubKey() { - return ['OP_0', _program]; + Script toScriptPubKey() { + return Script(script: ['OP_0', _program]); } /// Returns the type of address diff --git a/lib/src/bitcoin/constant/constant.dart b/lib/src/bitcoin/constant/constant.dart index e9cf691..d40c726 100644 --- a/lib/src/bitcoin/constant/constant.dart +++ b/lib/src/bitcoin/constant/constant.dart @@ -1,5 +1,5 @@ // ignore_for_file: constant_identifier_names, equal_keys_in_map, non_constant_identifier_names - +// Constants and identifiers used in the Bitcoin-related code. const Map> OP_CODES = { 'OP_0': [0x00], 'OP_FALSE': [0x00], @@ -220,30 +220,32 @@ Map CODE_OPS = { 178: 'OP_CHECKSEQUENCEVERIFY', }; +// SIGHASH types const int SIGHASH_SINGLE = 0x03; const int SIGHASH_ANYONECANPAY = 0x80; +const int SIGHASH_ALL = 0x01; +const int SIGHASH_NONE = 0x02; +const int TAPROOT_SIGHASH_ALL = 0x00; +// Transaction lock types const int TYPE_ABSOLUTE_TIMELOCK = 0x101; const int TYPE_RELATIVE_TIMELOCK = 0x201; const int TYPE_REPLACE_BY_FEE = 0x301; -const int SIGHASH_ALL = 0x01; -const int SIGHASH_NONE = 0x02; -const int TAPROOT_SIGHASH_ALL = 0x00; -const List DEFAULT_TX_LOCKTIME = [0x00, 0x00, 0x00, 0x00]; +// Default values and sequences +const List DEFAULT_TX_LOCKTIME = [0x00, 0x00, 0x00, 0x00]; const List EMPTY_TX_SEQUENCE = [0x00, 0x00, 0x00, 0x00]; const List DEFAULT_TX_SEQUENCE = [0xff, 0xff, 0xff, 0xff]; const List ABSOLUTE_TIMELOCK_SEQUENCE = [0xfe, 0xff, 0xff, 0xff]; - const List REPLACE_BY_FEE_SEQUENCE = [0x01, 0x00, 0x00, 0x00]; +// Script version and Bitcoin-related identifiers const int LEAF_VERSION_TAPSCRIPT = 0xc0; - const List DEFAULT_TX_VERSION = [0x02, 0x00, 0x00, 0x00]; - const int SATOSHIS_PER_BITCOIN = 100000000; const int NEGATIVE_SATOSHI = -1; +// Bitcoin address types const String P2PKH_ADDRESS = "p2pkh"; const String P2SH_ADDRESS = "p2sh"; const String P2WPKH_ADDRESS_V0 = "p2wpkhv0"; diff --git a/lib/src/bitcoin/script/transaction.dart b/lib/src/bitcoin/script/transaction.dart index 2e72165..68e687f 100644 --- a/lib/src/bitcoin/script/transaction.dart +++ b/lib/src/bitcoin/script/transaction.dart @@ -66,6 +66,7 @@ class BtcTransaction { for (int index = 0; index < vi.$1; index++) { final inp = TxInput.fromRaw(raw: raw, hasSegwit: hasSegwit, cursor: cursor); + inputs.add(inp.$1); cursor = inp.$2; } @@ -95,6 +96,7 @@ class BtcTransaction { cursor += wtVi.$1 + wtVi.$2; witnessesTmp.add(bytesToHex(witness)); } + witnesses.add(TxWitnessInput(stack: witnessesTmp)); } } @@ -446,7 +448,7 @@ class BtcTransaction { /// Calculates txid and returns it String txId() { final bytes = toBytes(segwit: false); - final h = doubleHash(bytes).reversed.toList(); - return bytesToHex(h); + final reversedHash = doubleHash(bytes).reversed.toList(); + return bytesToHex(reversedHash); } } diff --git a/lib/src/bitcoin/tools/tools.dart b/lib/src/bitcoin/tools/tools.dart index 75beb25..ff30394 100644 --- a/lib/src/bitcoin/tools/tools.dart +++ b/lib/src/bitcoin/tools/tools.dart @@ -1,7 +1,5 @@ import 'dart:typed_data'; - -import 'package:bitcoin_base/src/base58/base58.dart' as bs58; -import 'package:bitcoin_base/src/crypto/crypto.dart'; +import 'package:blockchain_utils/base58/base58.dart' as bs58; import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; import 'package:convert/convert.dart'; @@ -9,14 +7,14 @@ bool isValidAddress(String address) { if (address.length < 26 || address.length > 35) { return false; } - final decode = bs58.base58.decode(address); - Uint8List data = decode.sublist(0, decode.length - 4); - Uint8List checksum = decode.sublist(decode.length - 4); - Uint8List hash = doubleHash(data).sublist(0, 4); - if (!isValidCheckSum(checksum, hash)) { + try { + bs58.decodeCheck(address); + return true; + } on ArgumentError { return false; + } catch (e) { + rethrow; } - return true; } bool isValidHash160(String hash160) { @@ -85,15 +83,3 @@ Uint8List bytes32FromInt(int x) { } return result; } - -bool isValidCheckSum(List a, List b) { - if (a.length != b.length) { - return false; - } - for (int index = 0; index < a.length; index += 1) { - if (a[index] != b[index]) { - return false; - } - } - return true; -} diff --git a/lib/src/crypto/crypto.dart b/lib/src/crypto/crypto.dart index 5c66995..235fc8c 100644 --- a/lib/src/crypto/crypto.dart +++ b/lib/src/crypto/crypto.dart @@ -1,6 +1,5 @@ library bitcoin_crypto; -import 'dart:convert'; import 'package:pointycastle/export.dart'; import "dart:typed_data"; export 'ec/ec_private.dart'; @@ -9,54 +8,96 @@ export 'ec/ec_public.dart'; import 'package:pointycastle/src/platform_check/platform_check.dart' as platform; +// Function: doubleSh256 +// Description: Performs a double SHA-256 hash on the input data. +// Input: Uint8List value - The data to be hashed. +// Output: Uint8List - The result of applying SHA-256 twice on the input data. +// Note: This function is commonly used in cryptographic operations to enhance security. +// It applies SHA-256 hash function twice (double hashing) to the input data for added protection. Uint8List doubleSh256(Uint8List value) { + // Initialize a SHA-256 digest. Digest digest = SHA256Digest(); + + // Apply SHA-256 twice (double hashing) to the input data. return digest.process(digest.process(value)); } +// Function: hash160 +// Description: Computes the RIPEMD-160 hash of the SHA-256 hash of the input data. +// Input: Uint8List buffer - The data to be hashed. +// Output: Uint8List - The resulting RIPEMD-160 hash. +// Note: This function is commonly used in Bitcoin and other blockchain-related operations +// to create hash representations of public keys and addresses. Uint8List hash160(Uint8List buffer) { + // Compute the SHA-256 hash of the input data. Uint8List tmp = SHA256Digest().process(buffer); - return RIPEMD160Digest().process(tmp); -} -Uint8List hmacSHA512(Uint8List key, Uint8List data) { - final tmp = HMac(SHA512Digest(), 128)..init(KeyParameter(key)); - return tmp.process(data); + // Compute the RIPEMD-160 hash of the SHA-256 hash. + return RIPEMD160Digest().process(tmp); } +// Function: doubleHash +// Description: Computes a double SHA-256 hash of the input data. +// Input: Uint8List buffer - The data to be hashed. +// Output: Uint8List - The resulting double SHA-256 hash. +// Note: Double hashing is a common cryptographic technique used to enhance data security. Uint8List doubleHash(Uint8List buffer) { + // Compute the first SHA-256 hash of the input data. Uint8List tmp = SHA256Digest().process(buffer); + + // Compute the second SHA-256 hash of the first hash. return SHA256Digest().process(tmp); } +// Function: singleHash +// Description: Computes a single SHA-256 hash of the input data. +// Input: Uint8List buffer - The data to be hashed. +// Output: Uint8List - The resulting single SHA-256 hash. +// Note: This function calculates a single SHA-256 hash of the input data. Uint8List singleHash(Uint8List buffer) { - Uint8List tmp = SHA256Digest().process(buffer); - return tmp; + // Compute a single SHA-256 hash of the input data. + return SHA256Digest().process(buffer); } +// Function: taggedHash +// Description: Computes a tagged hash of the input data with a provided tag. +// Input: +// - Uint8List data - The data to be hashed. +// - String tag - A unique tag to differentiate the hash. +// Output: Uint8List - The resulting tagged hash. +// Note: This function combines the provided tag with the input data to create a unique +// hash by applying a double SHA-256 hash. Uint8List taggedHash(Uint8List data, String tag) { - final tagDIgits = singleHash(Uint8List.fromList(tag.codeUnits)); - final concat = Uint8List.fromList([...tagDIgits, ...tagDIgits, ...data]); + // Calculate the hash of the tag as Uint8List. + final tagDigest = singleHash(Uint8List.fromList(tag.codeUnits)); + + // Concatenate the tag hash with itself and the input data. + final concat = Uint8List.fromList([...tagDigest, ...tagDigest, ...data]); + + // Compute a double SHA-256 hash of the concatenated data. return singleHash(concat); } FortunaRandom? _randomGenerator; +// Variable: _randomGenerator +// Description: An instance of the FortunaRandom generator for generating random data. + +// Function: generateRandom +// Description: Generates random data of the specified size using the FortunaRandom generator. +// Input: int size - The size of the random data to generate (default is 32 bytes). +// Output: Uint8List - The generated random data. +// Note: This function initializes the FortunaRandom generator if it's not already initialized, +// seeds it with platform entropy, and then generates random data of the specified size. Uint8List generateRandom({int size = 32}) { if (_randomGenerator == null) { + // Initialize the FortunaRandom generator and seed it with platform entropy. _randomGenerator = FortunaRandom(); _randomGenerator!.seed(KeyParameter( platform.Platform.instance.platformEntropySource().getBytes(32))); } + // Generate random data of the specified size. final r = _randomGenerator!.nextBytes(size); return r; } - -Uint8List pbkdfDeriveDigest(String mnemonic, String salt) { - final toBytesSalt = Uint8List.fromList(utf8.encode(salt)); - final derive = PBKDF2KeyDerivator(HMac(SHA512Digest(), 128)); - derive.reset(); - derive.init(Pbkdf2Parameters(toBytesSalt, 2048, 64)); - return derive.process(Uint8List.fromList(mnemonic.codeUnits)); -} diff --git a/lib/src/crypto/ec/ec_encryption.dart b/lib/src/crypto/ec/ec_encryption.dart index 7d1c8c9..b5364a0 100644 --- a/lib/src/crypto/ec/ec_encryption.dart +++ b/lib/src/crypto/ec/ec_encryption.dart @@ -124,7 +124,9 @@ Uint8List? pointAddScalar(Uint8List p, Uint8List tweak, bool compress) { if (!isOrderScalar(tweak)) throw ArgumentError("Bad Tweek"); bool compressed = assumeCompression(compress, p); ECPoint? pp = _decodeFrom(p); - if (_compare(tweak, ZERO32) == 0) return pp!.getEncoded(compressed); + if (_compare(tweak, ZERO32) == 0) { + return pp!.getEncoded(compressed); + } BigInt tt = decodeBigInt(tweak); ECPoint qq = (G * tt) as ECPoint; ECPoint uu = (pp! + qq) as ECPoint; diff --git a/lib/src/crypto/ec/ec_private.dart b/lib/src/crypto/ec/ec_private.dart index e507f46..b05fa01 100644 --- a/lib/src/crypto/ec/ec_private.dart +++ b/lib/src/crypto/ec/ec_private.dart @@ -1,6 +1,6 @@ import 'dart:convert'; import 'dart:typed_data'; -import 'package:bitcoin_base/src/base58/base58.dart' as bs58; +import 'package:blockchain_utils/base58/base58.dart' as bs58; import 'package:bitcoin_base/src/bitcoin/tools/tools.dart'; import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; import 'package:bitcoin_base/src/formating/magic_prefix.dart'; @@ -36,14 +36,7 @@ class ECPrivate { /// creates an object from a WIF of WIFC format (string) factory ECPrivate.fromWif(String wif) { - final b64 = Uint8List.fromList(bs58.base58.decode(wif)); - Uint8List keyBytes = b64.sublist(0, b64.length - 4); - final checksum = b64.sublist(b64.length - 4); - final h = doubleHash(keyBytes); - final isValid = isValidCheckSum(h.sublist(0, 4), checksum); - if (!isValid) { - throw Exception('Checksum is wrong. Possible mistype?'); // listtEqual - } + Uint8List keyBytes = Uint8List.fromList(bs58.decodeCheck(wif)); keyBytes = keyBytes.sublist(1); if (keyBytes.length > 32) { keyBytes = keyBytes.sublist(0, keyBytes.length - 1); @@ -60,10 +53,7 @@ class ECPrivate { if (compressed) { bytes = Uint8List.fromList([...bytes, 0x01]); } - Uint8List hash = doubleHash(bytes); - hash = Uint8List.fromList( - [bytes, hash.sublist(0, 4)].expand((i) => i).toList(growable: false)); - return bs58.base58.encode(hash); + return bs58.encodeCheck(bytes); } /// returns the key's raw bytes @@ -154,7 +144,7 @@ class ECPrivate { /// sign taproot transaction digest and returns the signature. String signTapRoot(Uint8List txDigest, - {sighash = TAPROOT_SIGHASH_ALL, + {int sighash = TAPROOT_SIGHASH_ALL, List scripts = const [], bool tweak = true}) { Uint8List byteKey = Uint8List(0); diff --git a/lib/src/crypto/ec/ec_public.dart b/lib/src/crypto/ec/ec_public.dart index 26f020a..989bba6 100644 --- a/lib/src/crypto/ec/ec_public.dart +++ b/lib/src/crypto/ec/ec_public.dart @@ -11,6 +11,7 @@ import 'package:bitcoin_base/src/formating/magic_prefix.dart'; import 'ec_encryption.dart' as ec; class ECPublic { + // Constructs an ECPublic key from a byte representation. ECPublic.fromBytes(Uint8List public) { if (!ec.isPoint(public)) { throw ArgumentError("Bad point"); @@ -19,7 +20,7 @@ class ECPublic { _key = d; } - /// creates an object from a hex string in SEC format (classmethod) + // Constructs an ECPublic key from hex representation. ECPublic.fromHex(String hex) { final toBytes = hexToBytes(hex); if (!ec.isPoint(toBytes)) { @@ -31,7 +32,8 @@ class ECPublic { late final Uint8List _key; - /// returns the key as hex string (in SEC format - compressed by default) + // toHex converts the ECPublic key to a hex-encoded string. + // If 'compressed' is true, the key is in compressed format. String toHex({bool compressed = true}) { final bytes = toBytes(); if (compressed) { @@ -41,18 +43,22 @@ class ECPublic { return bytesToHex(bytes); } + // _toHash160 computes the RIPEMD160 hash of the ECPublic key. + // If 'compressed' is true, the key is in compressed format. Uint8List _toHash160({bool compressed = true}) { final bytes = hexToBytes(toHex(compressed: compressed)); return hash160(bytes); } - /// returns the hash160 hex string of the public key + // toHash160 computes the RIPEMD160 hash of the ECPublic key. + // If 'compressed' is true, the key is in compressed format. String toHash160({bool compressed = true}) { final bytes = hexToBytes(toHex(compressed: compressed)); return bytesToHex(hash160(bytes)); } - /// returns the corresponding P2pkhAddress object + // toAddress generates a P2PKH (Pay-to-Public-Key-Hash) address from the ECPublic key. + // If 'compressed' is true, the key is in compressed format. P2pkhAddress toAddress({bool compressed = true}) { final h16 = _toHash160(compressed: compressed); final toHex = bytesToHex(h16); @@ -60,7 +66,8 @@ class ECPublic { return P2pkhAddress(hash160: toHex); } - /// returns the corresponding P2wpkhAddress object + // 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); final toHex = bytesToHex(h16); @@ -68,36 +75,56 @@ class ECPublic { return P2wpkhAddress(program: toHex); } + // toP2pkAddress generates a P2PK (Pay-to-Public-Key) address from the ECPublic key. + // If 'compressed' is true, the key is in compressed format. P2pkAddress toP2pkAddress({bool compressed = true}) { final h = toHex(compressed: compressed); return P2pkAddress(publicKey: h); } + // toRedeemScript generates a redeem script from the ECPublic key. + // If 'compressed' is true, the key is in compressed format. Script toRedeemScript({bool compressed = true}) { final redeem = toHex(compressed: compressed); return Script(script: [redeem, "OP_CHECKSIG"]); } - /// p2sh nested p2kh + // toP2pkhInP2sh generates a P2SH (Pay-to-Script-Hash) address + // wrapping a P2PK (Pay-to-Public-Key) script derived from the ECPublic key. + // If 'compressed' is true, the key is in compressed format. P2shAddress toP2pkhInP2sh({bool compressed = true}) { final addr = toAddress(compressed: compressed); - return P2shAddress(script: Script(script: addr.toScriptPubKey())); + return P2shAddress.fromScript( + script: addr.toScriptPubKey(), type: AddressType.p2pkhInP2sh); } - // return p2sh(p2pk) address + // toP2pkInP2sh generates a P2SH (Pay-to-Script-Hash) address + // wrapping a P2PK (Pay-to-Public-Key) script derived from the ECPublic key. + // If 'compressed' is true, the key is in compressed format. P2shAddress toP2pkInP2sh({bool compressed = true}) { return P2shAddress(script: toRedeemScript(compressed: compressed)); } - /// p2sh nested segwit(p2wpkh) +// ToTaprootAddress generates a P2TR(Taproot) address from the ECPublic key +// and an optional script. The 'script' parameter can be used to specify +// custom spending conditions. + P2trAddress toTaprootAddress({List? scripts}) { + final pubKey = toTapRotHex(script: scripts); + + return P2trAddress(program: pubKey); + } + + // toP2wpkhInP2sh generates a P2SH (Pay-to-Script-Hash) address + // wrapping a P2WPKH (Pay-to-Witness-Public-Key-Hash) script derived from the ECPublic key. + // If 'compressed' is true, the key is in compressed format. P2shAddress toP2wpkhInP2sh({bool compressed = true}) { final addr = toSegwitAddress(compressed: compressed); - return P2shAddress.fromSegwitScript( - script: Script(script: addr.toScriptPubKey()), - type: AddressType.p2wpkhInP2sh); + return P2shAddress.fromScript( + script: addr.toScriptPubKey(), type: AddressType.p2wpkhInP2sh); } - /// return 1-1 multisig segwit script + // 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', @@ -107,25 +134,30 @@ class ECPublic { ]); } - /// return p2wshaddress with 1-1 multisig segwit script + // 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(script: toP2wshScript(compressed: compressed)); } - /// return p2sh(p2wsh) nested 1-1 multisig segwit address + // 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); - return P2shAddress.fromSegwitScript( - script: Script(script: p2sh.toScriptPubKey()), - type: AddressType.p2wshInP2sh); + return P2shAddress.fromScript( + script: p2sh.toScriptPubKey(), type: AddressType.p2wshInP2sh); } + // calculateTweek computes and returns the TapTweak value based on the ECPublic key + // and an optional script. It uses the key's x-coordinate and the Merkle root of the script + // (if provided) to calculate the tweak. BigInt calculateTweek({dynamic script}) { final tweak = _calculateTweek(_key, script: script); return decodeBigInt(tweak); } - /// returns the unCompressed key's raw bytes + // toBytes returns the uncompressed byte representation of the ECPublic key. Uint8List toBytes({int? prefix = 0x04}) { if (prefix != null) { return Uint8List.fromList([prefix, ..._key]); @@ -133,24 +165,28 @@ class ECPublic { return Uint8List.fromList([..._key]); } - /// returns the Compressed key's raw bytes + // toCompressedBytes returns the compressed byte representation of the ECPublic key. Uint8List toCompressedBytes() { final point = reEncodedFromForm(toBytes(), true); return point; } - /// returns the x coordinate only as hex string after tweaking (needed for taproot) + // returns the x coordinate only as hex string after tweaking (needed for taproot) String toTapRotHex({List? script}) { final tweak = _calculateTweek(_key, script: script); final point = tweakTaprootPoint(_key, tweak); return bytesToHex(point.sublist(0, 32)); } - /// returns the x coordinate only as hex string before tweaking (needed for taproot) + // toXOnlyHex extracts and returns the x-coordinate (first 32 bytes) of the ECPublic key + // as a hexadecimal string. String toXOnlyHex() { return bytesToHex(_key.sublist(0, 32)); } + // _calculateTweek computes and returns the TapTweak value based on the ECPublic key + // and an optional script. It uses the key's x-coordinate and the Merkle root of the script + // (if provided) to calculate the tweak. Uint8List _calculateTweek(Uint8List public, {dynamic script}) { final keyX = Uint8List.fromList(public.getRange(0, 32).toList()); if (script == null) { @@ -163,6 +199,9 @@ class ECPublic { return tweek; } + // _getTagHashedMerkleRoot computes and returns the tagged hashed Merkle root for Taproot + // based on the provided argument. It handles different argument types, including scripts + // and lists of scripts. Uint8List _getTagHashedMerkleRoot(dynamic args) { if (args is Script) { final tagged = _tapleafTaggedHash(args); @@ -182,6 +221,8 @@ class ECPublic { throw Exception("List cannot have more than 2 branches."); } + // _tapleafTaggedHash computes and returns the tagged hash of a script for Taproot, + // using the specified script. It prepends a version byte and then tags the hash with "TapLeaf". Uint8List _tapleafTaggedHash(Script script) { final scriptBytes = prependVarint(script.toBytes()); @@ -189,6 +230,9 @@ class ECPublic { return taggedHash(part, 'TapLeaf'); } + // _tapBranchTaggedHash computes and returns the tagged hash of two byte slices + // for Taproot, where 'a' and 'b' are the input byte slices. It ensures that 'a' and 'b' + // are sorted and concatenated before tagging the hash with "TapBranch". Uint8List _tapBranchTaggedHash(Uint8List a, Uint8List b) { if (isLessThanBytes(a, b)) { return taggedHash(Uint8List.fromList([...a, ...b]), "TapBranch"); @@ -196,20 +240,15 @@ class ECPublic { return taggedHash(Uint8List.fromList([...b, ...a]), "TapBranch"); } - /// return p2tr address - P2trAddress toTaprootAddress({List? scripts}) { - final pubKey = toTapRotHex(script: scripts); - - return P2trAddress(program: pubKey); - } - /// returns true if the message was signed with this public key's bool verify(String message, Uint8List signature) { final msg = singleHash(magicMessage(message)); return ec.verify(msg, toBytes(), signature.sublist(1)); } - /// get ECPublic of signatur + // GetSignaturePublic extracts and returns the public key associated with a signature + // for the given message. If the extraction is successful, it returns an ECPublic key; + // otherwise, it returns nil. static ECPublic? getSignaturPublic(String message, Uint8List signatur) { final msg = singleHash(magicMessage(message)); int prefix = signatur[0]; diff --git a/lib/src/hd_wallet/hd_wallet.dart b/lib/src/hd_wallet/hd_wallet.dart deleted file mode 100644 index 039094f..0000000 --- a/lib/src/hd_wallet/hd_wallet.dart +++ /dev/null @@ -1,280 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; -import 'package:bitcoin_base/src/bip39/bip39.dart'; -import 'package:bitcoin_base/src/bitcoin/address/core.dart'; -import 'package:bitcoin_base/src/crypto/crypto.dart'; -import 'package:bitcoin_base/src/crypto/ec/ec_encryption.dart' as ec; -import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; -import 'package:bitcoin_base/src/models/network.dart'; -import 'package:bitcoin_base/src/base58/base58.dart' as bs; - -class HdWallet { - static const String _bitcoinKey = "Bitcoin seed"; - HdWallet._fromPrivateKey( - {required ECPrivate privateKey, - required Uint8List chainCode, - int depth = 0, - int index = 0, - Uint8List? fingerPrint}) - : _fingerPrint = fingerPrint ?? Uint8List(4), - _chainCode = chainCode, - _private = privateKey, - _ecPublic = privateKey.getPublic(), - _fromXpub = false, - _depth = depth, - _index = index; - HdWallet._fromPublicKey( - {required ECPublic public, - required Uint8List chainCode, - int depth = 0, - int index = 0, - Uint8List? fingerPrint}) - : _fingerPrint = fingerPrint ?? Uint8List(4), - _chainCode = chainCode, - _ecPublic = public, - _fromXpub = true, - _depth = depth, - _index = index; - - int _depth = 0; - int get depth => _depth; - - int _index = 0; - int get index => _index; - - final Uint8List _fingerPrint; - Uint8List get fingerPrint => _fingerPrint; - - late final bool isRoot = bytesListEqual(_fingerPrint, Uint8List(4)); - - late final ECPrivate _private; - ECPrivate get privateKey => _fromXpub - ? throw ArgumentError("connot access private from publicKey wallet") - : _private; - - late final ECPublic _ecPublic; - ECPublic get publicKey => _ecPublic; - final bool _fromXpub; - bool get isPublicKeyWallet => _fromXpub; - - factory HdWallet.fromMnemonic(String mnemonic, - {String passphrase = "", String key = _bitcoinKey}) { - final seed = BIP39.toSeed(mnemonic, passphrase: passphrase); - if (seed.length < 16) { - throw ArgumentError("Seed should be at least 128 bits"); - } - if (seed.length > 64) { - throw ArgumentError("Seed should be at most 512 bits"); - } - final hash = hmacSHA512(utf8.encode(key) as Uint8List, seed); - final private = ECPrivate.fromBytes(hash.sublist(0, 32)); - final chainCode = hash.sublist(32); - final wallet = - HdWallet._fromPrivateKey(privateKey: private, chainCode: chainCode); - return wallet; - } - - final Uint8List _chainCode; - - static const _highBit = 0x80000000; - static const _maxUint31 = 2147483647; - static const _maxUint32 = 4294967295; - - String chainCode() { - return bytesToHex(_chainCode); - } - - HdWallet _addDrive(int index) { - if (index > _maxUint32 || index < 0) throw ArgumentError("Expected UInt32"); - final isHardened = index >= _highBit; - Uint8List data = Uint8List(37); - - if (isHardened) { - if (_fromXpub) { - throw ArgumentError("cannot use hardened path in public wallet"); - } - data[0] = 0x00; - data.setRange(1, 33, (_private).toBytes()); - data.buffer.asByteData().setUint32(33, index); - } else { - data.setRange(0, 33, publicKey.toCompressedBytes()); - data.buffer.asByteData().setUint32(33, index); - } - final masterKey = hmacSHA512(_chainCode, data); - final key = masterKey.sublist(0, 32); - final chain = masterKey.sublist(32); - if (!ec.isPrivate(key)) { - return _addDrive(index + 1); - } - final childDeph = depth + 1; - final childIndex = index; - final finger = hash160(publicKey.toCompressedBytes()).sublist(0, 4); - if (_fromXpub) { - final newPoint = ec.pointAddScalar(_ecPublic.toBytes(), key, true); - if (newPoint == null) { - return _addDrive(index + 1); - } - return HdWallet._fromPublicKey( - public: ECPublic.fromBytes(newPoint), - chainCode: chain, - depth: childDeph, - index: childIndex, - fingerPrint: finger); - } - final newPrivate = ec.generateTweek((_private).toBytes(), key); - return HdWallet._fromPrivateKey( - privateKey: ECPrivate.fromBytes(newPrivate!), - chainCode: chain, - depth: childDeph, - index: childIndex, - fingerPrint: finger); - } - - static bool isValidPath(String path) { - final regex = RegExp(r"^(m\/)?(\d+'?\/)*\d+'?$"); - return regex.hasMatch(path); - } - - static (bool, Uint8List) isRootKey(String xPrivateKey, NetworkInfo network, - {bool isPublicKey = false}) { - final dec = bs.base58.decodeCheck(xPrivateKey); - if (dec.length != 78) { - throw ArgumentError("invalid xPrivateKey"); - } - final semantic = dec.sublist(0, 4); - final version = isPublicKey - ? NetworkInfo.networkFromXPublicPrefix(semantic) - : NetworkInfo.networkFromXPrivePrefix(semantic); - if (version == null) { - throw ArgumentError("invalid network"); - } - final networkPrefix = isPublicKey - ? network.extendPublic[version]! - : network.extendPrivate[version]!; - final prefix = hexToBytes("${networkPrefix}000000000000000000"); - return (bytesListEqual(prefix, dec.sublist(0, prefix.length)), dec); - } - - factory HdWallet.fromXPrivateKey(String xPrivateKey, - {bool? foreRootKey, NetworkInfo network = NetworkInfo.BITCOIN}) { - final check = isRootKey(xPrivateKey, network); - if (foreRootKey != null) { - if (check.$1 != foreRootKey) { - throw ArgumentError( - "is not valid ${foreRootKey ? "rootXPrivateKey" : "xPrivateKey"}"); - } - } - final decode = _decodeXKeys(check.$2); - final chain = decode[4]; - final private = ECPrivate.fromBytes(decode[5]); - final index = intFromBytes(decode[3], Endian.big); - final deph = intFromBytes(decode[1], Endian.big); - return HdWallet._fromPrivateKey( - privateKey: private, - chainCode: chain, - depth: deph, - fingerPrint: decode[2], - index: index); - } - - static List _decodeXKeys(Uint8List xKey, {bool isPublic = false}) { - return [ - xKey.sublist(0, 4), - xKey.sublist(4, 5), - xKey.sublist(5, 9), - xKey.sublist(9, 13), - xKey.sublist(13, 45), - xKey.sublist(isPublic ? 45 : 46) - ]; - } - - /// return xpub as base58 - String toXpublicKey( - {AddressType semantic = AddressType.p2pkh, - NetworkInfo network = NetworkInfo.BITCOIN}) { - final version = hexToBytes(network.extendPublic[semantic]!); - final depthBytes = Uint8List.fromList([depth]); - final fingerPrintBytes = _fingerPrint; - final indexBytes = packUint32BE(index); - final data = publicKey.toCompressedBytes(); - final result = Uint8List.fromList([ - ...version, - ...depthBytes, - ...fingerPrintBytes, - ...indexBytes, - ..._chainCode, - ...data - ]); - final check = bs.base58.encodeCheck(result); - return check; - } - - /// return root or not root public wallet from xPublicKey - factory HdWallet.fromXpublicKey(String xPublicKey, - {bool? forceRootKey, NetworkInfo network = NetworkInfo.BITCOIN}) { - final check = isRootKey(xPublicKey, network, isPublicKey: true); - if (forceRootKey != null) { - if (check.$1 != forceRootKey) { - throw ArgumentError( - "is not valid ${forceRootKey ? "rootPublicKey" : "publicKey"}"); - } - } - final decode = _decodeXKeys(check.$2, isPublic: true); - final chain = decode[4]; - final publicKey = ECPublic.fromBytes(decode[5]); - final index = intFromBytes(decode[3], Endian.big); - final deph = intFromBytes(decode[1], Endian.big); - return HdWallet._fromPublicKey( - public: publicKey, - chainCode: chain, - depth: deph, - fingerPrint: decode[2], - index: index); - } - - String toXpriveKey( - {AddressType semantic = AddressType.p2pkh, - NetworkInfo network = NetworkInfo.BITCOIN}) { - if (_fromXpub) { - throw ArgumentError("connot access private from publicKey wallet"); - } - final version = hexToBytes(network.extendPrivate[semantic]!); - final depthBytes = Uint8List.fromList([depth]); - final fingerPrintBytes = _fingerPrint; - final indexBytes = packUint32BE(index); - final data = Uint8List.fromList([0, ..._private.toBytes()]); - final result = Uint8List.fromList([ - ...version, - ...depthBytes, - ...fingerPrintBytes, - ...indexBytes, - ..._chainCode, - ...data - ]); - final check = bs.base58.encodeCheck(result); - return check; - } - - static HdWallet drivePath(HdWallet masterWallet, String path) { - if (!isValidPath(path)) throw ArgumentError("invalid BIP32 Path"); - List splitPath = path.split("/"); - if (splitPath[0] == "m") { - splitPath = splitPath.sublist(1); - } - return splitPath.fold(masterWallet, (HdWallet prevHd, String indexStr) { - int index; - if (indexStr.substring(indexStr.length - 1) == "'") { - index = int.parse(indexStr.substring(0, indexStr.length - 1)); - if (index > _maxUint31 || index < 0) { - throw ArgumentError("Wrong index"); - } - final newDrive = prevHd._addDrive(index + _highBit); - return newDrive; - } else { - index = int.parse(indexStr); - final newDrive = prevHd._addDrive(index); - return newDrive; - } - }); - } -} diff --git a/lib/src/models/network.dart b/lib/src/models/network.dart index 275b931..cbd298f 100644 --- a/lib/src/models/network.dart +++ b/lib/src/models/network.dart @@ -2,16 +2,21 @@ import 'dart:typed_data'; import 'package:bitcoin_base/src/bitcoin/address/core.dart'; import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; +enum BtcNetwork { mainnet, testnet } + class NetworkInfo { final String messagePrefix; final String bech32; final int p2pkhPrefix; final int p2shPrefix; final int wif; + final BtcNetwork network; final Map extendPrivate; final Map extendPublic; + bool get isMainnet => network == BtcNetwork.mainnet; // ignore: constant_identifier_names static const BITCOIN = NetworkInfo( + network: BtcNetwork.mainnet, messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'bc', p2pkhPrefix: 0x00, @@ -19,7 +24,7 @@ class NetworkInfo { wif: 0x80, extendPrivate: { AddressType.p2pkh: "0x0488ade4", - AddressType.p2sh: "0x0488ade4", + AddressType.p2pkhInP2sh: "0x0488ade4", AddressType.p2wpkh: "0x04b2430c", AddressType.p2wpkhInP2sh: "0x049d7878", AddressType.p2wsh: "0x02aa7a99", @@ -27,7 +32,7 @@ class NetworkInfo { }, extendPublic: { AddressType.p2pkh: "0x0488b21e", - AddressType.p2sh: "0x0488b21e", + AddressType.p2pkhInP2sh: "0x0488b21e", AddressType.p2wpkh: "0x04b24746", AddressType.p2wpkhInP2sh: "0x049d7cb2", AddressType.p2wsh: "0x02aa7ed3", @@ -36,6 +41,7 @@ class NetworkInfo { // ignore: constant_identifier_names static const TESTNET = NetworkInfo( + network: BtcNetwork.testnet, messagePrefix: '\x18Bitcoin Signed Message:\n', bech32: 'tb', p2pkhPrefix: 0x6f, @@ -43,7 +49,7 @@ class NetworkInfo { wif: 0xef, extendPrivate: { AddressType.p2pkh: "0x04358394", - AddressType.p2sh: "0x04358394", + AddressType.p2pkhInP2sh: "0x04358394", AddressType.p2wpkh: "0x045f18bc", AddressType.p2wpkhInP2sh: "0x044a4e28", AddressType.p2wsh: "0x02575048", @@ -51,7 +57,7 @@ class NetworkInfo { }, extendPublic: { AddressType.p2pkh: "0x043587cf", - AddressType.p2sh: "0x043587cf", + AddressType.p2pkhInP2sh: "0x043587cf", AddressType.p2wpkh: "0x045f1cf6", AddressType.p2wpkhInP2sh: "0x044a5262", AddressType.p2wsh: "0x02575483", @@ -99,5 +105,6 @@ class NetworkInfo { required this.p2shPrefix, required this.wif, required this.extendPrivate, - required this.extendPublic}); + required this.extendPublic, + required this.network}); } diff --git a/lib/src/provider/api_provider.dart b/lib/src/provider/api_provider.dart new file mode 100644 index 0000000..1f53005 --- /dev/null +++ b/lib/src/provider/api_provider.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:bitcoin_base/src/models/network.dart'; +import 'package:bitcoin_base/src/provider/block_cypher_models.dart'; +import 'package:bitcoin_base/src/provider/fee_rate.dart'; +import 'package:bitcoin_base/src/provider/mempol_models.dart'; +import 'package:bitcoin_base/src/provider/provider.dart'; +import 'package:bitcoin_base/src/provider/utxo_details.dart'; +import 'package:http/http.dart' as http; + +class ApiProviderException implements Exception { + ApiProviderException({required this.status, this.message, this.data}); + final Map? data; + final int status; + final String? message; + + @override + String toString() { + return data?.toString() ?? message ?? "statusCode: $status"; + } +} + +class ApiProvider { + ApiProvider( + {required this.api, Map? header, http.Client? client}) + : _header = header ?? {"Content-Type": "application/json"}, + client = client ?? http.Client(); + factory ApiProvider.fromMempl(NetworkInfo networkInfo, + {Map? header, http.Client? client}) { + final api = APIConfig.mempool(networkInfo); + return ApiProvider(api: api, header: header, client: client); + } + factory ApiProvider.fromBlocCypher(NetworkInfo networkInfo, + {Map? header, http.Client? client}) { + final api = APIConfig.fromBlockCypher(networkInfo); + return ApiProvider(api: api, header: header, client: client); + } + final APIConfig api; + final http.Client client; + final Map _header; + + void _getException(int status, Uint8List data) { + if (data.isEmpty) throw ApiProviderException(status: status); + String message = utf8.decode(data); + Map? error; + try { + error = json.decode(message); + // ignore: empty_catches + } catch (e) {} + throw ApiProviderException( + status: status, message: error != null ? null : message, data: error); + } + + Future _getRequest(String url) async { + final response = await client.get(Uri.parse(url)); + if (response.statusCode != 200) { + _getException(response.statusCode, response.bodyBytes); + } + switch (T) { + case String: + return utf8.decode(response.bodyBytes) as T; + default: + return json.decode(utf8.decode(response.bodyBytes)) as T; + } + } + + Future _postReqiest(String url, Object? data) async { + final response = + await client.post(Uri.parse(url), body: data, headers: _header); + if (response.statusCode != 200) { + _getException(response.statusCode, response.bodyBytes); + } + switch (T) { + case String: + return utf8.decode(response.bodyBytes) as T; + default: + return json.decode(utf8.decode(response.bodyBytes)) as T; + } + } + + Future> testmempool(List params) async { + final Map data = { + "jsonrpc": "2.0", + "method": "testmempoolaccept", + "id": DateTime.now().millisecondsSinceEpoch.toString(), + "params": params + }; + final response = await _postReqiest>( + "https://btc.getblock.io/786c97b8-f53f-427b-80f7-9af7bd5bdb84/testnet/", + json.encode(data)); + return response; + } + + Future> getAccountUtxo(UtxoOwnerDetails owner, + {String Function(String)? tokenize}) async { + final apiUrl = api.getUtxoUrl(owner.address.toAddress(api.network)); + final url = tokenize?.call(apiUrl) ?? apiUrl; + final response = await _getRequest(url); + switch (api.apiType) { + case APIType.MempoolApi: + final utxos = + (response as List).map((e) => MempolUtxo.fromJson(e)).toList(); + return utxos.toUtxoWithOwnerList(owner); + default: + final blockCypherUtxo = BlockCypherUtxo.fromJson(response); + return blockCypherUtxo.toUtxoWithOwner(owner); + } + } + + Future sendRawTransaction(String txDigest, + {String Function(String)? tokenize}) async { + final apiUrl = api.sendTransaction; + final url = tokenize?.call(apiUrl) ?? apiUrl; + + switch (api.apiType) { + case APIType.MempoolApi: + final response = await _postReqiest(url, txDigest); + return response; + default: + final Map digestData = {"tx": txDigest}; + final response = await _postReqiest>( + url, json.encode(digestData)); + final blockCypherUtxo = BlockCypherTransaction.fromJson(response); + return blockCypherUtxo.hash; + } + } + + Future getNetworkFeeRate( + {String Function(String)? tokenize}) async { + final apiUrl = api.getFeeApiUrl(); + final url = tokenize?.call(apiUrl) ?? apiUrl; + final response = await _getRequest>(url); + switch (api.apiType) { + case APIType.MempoolApi: + return BitcoinFeeRate.fromMempool(response); + default: + return BitcoinFeeRate.fromBlockCypher(response); + } + } + + Future getTransaction(String transactionId, + {String Function(String)? tokenize}) async { + final apiUrl = api.getTransactionUrl(transactionId); + final url = tokenize?.call(apiUrl) ?? apiUrl; + final response = await _getRequest>(url); + switch (api.apiType) { + case APIType.MempoolApi: + return MempoolTransaction.fromJson(response) as T; + default: + return BlockCypherTransaction.fromJson(response) as T; + } + } + + Future> getAccountTransactions(String address, + {String Function(String)? tokenize}) async { + final apiUrl = api.getTransactionsUrl(address); + final url = tokenize?.call(apiUrl) ?? apiUrl; + final response = await _getRequest(url); + switch (api.apiType) { + case APIType.MempoolApi: + final transactions = (response as List) + .map((e) => MempoolTransaction.fromJson(e) as T) + .toList(); + return transactions; + default: + final transactions = (response as List) + .map((e) => BlockCypherTransaction.fromJson(e) as T) + .toList(); + return transactions; + } + } +} diff --git a/lib/src/provider/block_cypher_models.dart b/lib/src/provider/block_cypher_models.dart new file mode 100644 index 0000000..538b90b --- /dev/null +++ b/lib/src/provider/block_cypher_models.dart @@ -0,0 +1,287 @@ +import 'package:bitcoin_base/src/provider/utxo_details.dart'; + +class TxRef { + final String txHash; + final int blockHeight; + final int txInputN; + final int txOutputN; + final BigInt value; + final int refBalance; + final bool spent; + final int confirmations; + final DateTime confirmed; + final String script; + + TxRef({ + required this.txHash, + required this.blockHeight, + required this.txInputN, + required this.txOutputN, + required this.value, + required this.refBalance, + required this.spent, + required this.confirmations, + required this.confirmed, + required this.script, + }); + + factory TxRef.fromJson(Map json) { + return TxRef( + txHash: json['tx_hash'], + blockHeight: json['block_height'], + txInputN: json['tx_input_n'], + txOutputN: json['tx_output_n'], + value: BigInt.from(json['value']), + refBalance: json['ref_balance'], + spent: json['spent'], + confirmations: json['confirmations'], + confirmed: DateTime.parse(json['confirmed']), + script: json['script'], + ); + } +} + +class BlockCypherUtxo { + final String address; + final int totalReceived; + final int totalSent; + final int balance; + final int unconfirmedBalance; + final int finalBalance; + final int nTx; + final int unconfirmedNTx; + final int finalNTx; + final List txRefs; + final String txURL; + + BlockCypherUtxo({ + required this.address, + required this.totalReceived, + required this.totalSent, + required this.balance, + required this.unconfirmedBalance, + required this.finalBalance, + required this.nTx, + required this.unconfirmedNTx, + required this.finalNTx, + required this.txRefs, + required this.txURL, + }); + + factory BlockCypherUtxo.fromJson(Map json) { + return BlockCypherUtxo( + address: json['address'], + totalReceived: json['total_received'], + totalSent: json['total_sent'], + balance: json['balance'], + unconfirmedBalance: json['unconfirmed_balance'], + finalBalance: json['final_balance'], + nTx: json['n_tx'], + unconfirmedNTx: json['unconfirmed_n_tx'], + finalNTx: json['final_n_tx'], + txRefs: (json['txrefs'] as List) + .map((ref) => TxRef.fromJson(ref)) + .toList(), + txURL: json['tx_url'], + ); + } + + List toUtxoWithOwner(UtxoOwnerDetails owner) { + List utxos = txRefs.map((ref) { + return UtxoWithOwner( + utxo: BitcoinUtxo( + txHash: ref.txHash, + value: ref.value, + vout: ref.txOutputN, + scriptType: owner.address.type, + blockHeight: 1, + ), + ownerDetails: owner, + ); + }).toList(); + return utxos; + } +} + +class BlockCypherTransactionInput { + final String prevHash; + final int outputIndex; + final int outputValue; + final int sequence; + final List addresses; + final String scriptType; + final int age; + final List? witness; + + BlockCypherTransactionInput({ + required this.prevHash, + required this.outputIndex, + required this.outputValue, + required this.sequence, + required this.addresses, + required this.scriptType, + required this.age, + required this.witness, + }); + + factory BlockCypherTransactionInput.fromJson(Map json) { + return BlockCypherTransactionInput( + prevHash: json['prev_hash'], + outputIndex: json['output_index'], + outputValue: json['output_value'], + sequence: json['sequence'], + addresses: List.from(json['addresses']), + scriptType: json['script_type'], + age: json['age'], + witness: (json['witness'] as List?)?.cast(), + ); + } +} + +class BlockCypherTransactionOutput { + final int value; + final String script; + final List addresses; + final String scriptType; + final String? dataHex; + final String? dataString; + + BlockCypherTransactionOutput({ + required this.value, + required this.script, + required this.addresses, + required this.scriptType, + required this.dataHex, + required this.dataString, + }); + + factory BlockCypherTransactionOutput.fromJson(Map json) { + return BlockCypherTransactionOutput( + value: json['value'], + script: json['script'], + addresses: List.from(json['addresses'] ?? []), + scriptType: json['script_type'], + dataHex: json['data_hex'], + dataString: json['data_string'], + ); + } +} + +class BlockCypherTransaction { + final int blockHeight; + final int blockIndex; + final String hash; + final List addresses; + final int total; + final int fees; + final int size; + final int vSize; + final String preference; + final String? relayedBy; + final DateTime? received; + final int ver; + final bool doubleSpend; + final int vinSz; + final int voutSz; + final bool? optInRBF; + final String? dataProtocol; + final int confirmations; + final List inputs; + final List outputs; + + BlockCypherTransaction({ + required this.blockHeight, + required this.blockIndex, + required this.hash, + required this.addresses, + required this.total, + required this.fees, + required this.size, + required this.vSize, + required this.preference, + required this.relayedBy, + required this.received, + required this.ver, + required this.doubleSpend, + required this.vinSz, + required this.voutSz, + required this.optInRBF, + required this.dataProtocol, + required this.confirmations, + required this.inputs, + required this.outputs, + }); + + factory BlockCypherTransaction.fromJson(Map json) { + return BlockCypherTransaction( + blockHeight: json['block_height'], + blockIndex: json['block_index'], + hash: json['hash'], + addresses: List.from(json['addresses']), + total: json['total'], + fees: json['fees'], + size: json['size'], + vSize: json['vsize'], + preference: json['preference'], + relayedBy: json['relayed_by'], + received: + json['received'] == null ? null : DateTime.parse(json['received']), + ver: json['ver'], + doubleSpend: json['double_spend'], + vinSz: json['vin_sz'], + voutSz: json['vout_sz'], + optInRBF: json['opt_in_rbf'], + dataProtocol: json['data_protocol'], + confirmations: json['confirmations'], + inputs: (json['inputs'] as List) + .map((input) => BlockCypherTransactionInput.fromJson(input)) + .toList(), + outputs: (json['outputs'] as List) + .map((output) => BlockCypherTransactionOutput.fromJson(output)) + .toList(), + ); + } +} + +class BlockCypherAddressInfo { + final String address; + final int totalReceived; + final int totalSent; + final int balance; + final int unconfirmedBalance; + final int finalBalance; + final int numTransactions; + final int unconfirmedNumTx; + final int finalNumTx; + final List txs; + + BlockCypherAddressInfo({ + required this.address, + required this.totalReceived, + required this.totalSent, + required this.balance, + required this.unconfirmedBalance, + required this.finalBalance, + required this.numTransactions, + required this.unconfirmedNumTx, + required this.finalNumTx, + required this.txs, + }); + + factory BlockCypherAddressInfo.fromJson(Map json) { + return BlockCypherAddressInfo( + address: json['address'], + totalReceived: json['total_received'], + totalSent: json['total_sent'], + balance: json['balance'], + unconfirmedBalance: json['unconfirmed_balance'], + finalBalance: json['final_balance'], + numTransactions: json['n_tx'], + unconfirmedNumTx: json['unconfirmed_n_tx'], + finalNumTx: json['final_n_tx'], + txs: (json['txs'] as List) + .map((transaction) => BlockCypherTransaction.fromJson(transaction)) + .toList(), + ); + } +} diff --git a/lib/src/provider/fee_rate.dart b/lib/src/provider/fee_rate.dart new file mode 100644 index 0000000..eb98826 --- /dev/null +++ b/lib/src/provider/fee_rate.dart @@ -0,0 +1,63 @@ +class BitcoinFeeRate { + BitcoinFeeRate._( + {required this.high, required this.medium, required this.low}); + BigInt high; // High fee rate in satoshis per byte + BigInt medium; // Medium fee rate in satoshis per byte + BigInt low; // Low fee rate in satoshis per byte + + // GetEstimate calculates the estimated fee in satoshis for a given transaction size + // and fee rate (in satoshis per kilobyte) using the formula: + // + // EstimatedFee = (TransactionSize * FeeRate) / 1024 + // + // Parameters: + // - trSize: An integer representing the transaction size in bytes. + // - feeRate: A BigInt representing the fee rate in satoshis per kilobyte. + // + // Returns: + // - BigInt: A BigInt containing the estimated fee in satoshis. + BigInt getEstimate(int trSize, {BigInt? feeRate}) { + final trSizeBigInt = BigInt.from(trSize); + return (trSizeBigInt * (feeRate ?? medium)) ~/ BigInt.from(1024); + } + + @override + String toString() { + return 'high: ${high.toString()} medium: ${medium.toString()} low: ${low.toString()}'; + } + +// NewBitcoinFeeRateFromMempool creates a BitcoinFeeRate structure from JSON data retrieved +// from a mempool API response. The function parses the JSON map and extracts fee rate +// information for high, medium, and low fee levels. + factory BitcoinFeeRate.fromMempool(Map json) { + return BitcoinFeeRate._( + high: parseMempoolFees(json['fastestFee'])!, + medium: parseMempoolFees(json['halfHourFee'])!, + low: parseMempoolFees(json['minimumFee'])!); + } +// NewBitcoinFeeRateFromBlockCypher creates a BitcoinFeeRate structure from JSON data retrieved +// from a BlockCypher API response. The function parses the JSON map and extracts fee rate +// information for high, medium, and low fee levels. + factory BitcoinFeeRate.fromBlockCypher(Map json) { + return BitcoinFeeRate._( + high: BigInt.from((json['high_fee_per_kb'] as int)), + medium: BigInt.from((json['medium_fee_per_kb'] as int)), + low: BigInt.from((json['low_fee_per_kb'] as int))); + } +} + +// ParseMempoolFees takes a data dynamic and converts it to a BigInt representing +// mempool fees in satoshis per kilobyte (sat/KB). The function performs the conversion +// based on the type of the input data, which can be either a double (floating-point +// fee rate) or an int (integer fee rate in satoshis per byte). +BigInt? parseMempoolFees(dynamic data) { + const kb = 1024; + + if (data is double) { + return BigInt.from((data * kb).toInt()); + } else if (data is int) { + return BigInt.from((data * kb)); + } else { + return null; + } +} diff --git a/lib/src/provider/mempol_models.dart b/lib/src/provider/mempol_models.dart new file mode 100644 index 0000000..9b83c46 --- /dev/null +++ b/lib/src/provider/mempol_models.dart @@ -0,0 +1,192 @@ +import 'package:bitcoin_base/src/provider/utxo_details.dart'; + +class MempoolPrevOut { + final String scriptPubKey; + final String scriptPubKeyAsm; + final String scriptPubKeyType; + final String scriptPubKeyAddress; + final int value; + + MempoolPrevOut({ + required this.scriptPubKey, + required this.scriptPubKeyAsm, + required this.scriptPubKeyType, + required this.scriptPubKeyAddress, + required this.value, + }); + + factory MempoolPrevOut.fromJson(Map json) { + return MempoolPrevOut( + scriptPubKey: json['scriptpubkey'], + scriptPubKeyAsm: json['scriptpubkey_asm'], + scriptPubKeyType: json['scriptpubkey_type'], + scriptPubKeyAddress: json['scriptpubkey_address'], + value: json['value'], + ); + } +} + +class MempoolVin { + final String txID; + final int vout; + final MempoolPrevOut prevOut; + final String scriptSig; + final String scriptSigAsm; + final List witness; + final bool isCoinbase; + final int sequence; + + MempoolVin({ + required this.txID, + required this.vout, + required this.prevOut, + required this.scriptSig, + required this.scriptSigAsm, + required this.witness, + required this.isCoinbase, + required this.sequence, + }); + + factory MempoolVin.fromJson(Map json) { + return MempoolVin( + txID: json['txid'], + vout: json['vout'], + prevOut: MempoolPrevOut.fromJson(json['prevout']), + scriptSig: json['scriptsig'], + scriptSigAsm: json['scriptsig_asm'], + witness: List.from(json['witness']), + isCoinbase: json['is_coinbase'], + sequence: json['sequence'], + ); + } +} + +class MempoolVout { + final String scriptPubKey; + final String scriptPubKeyAsm; + final String scriptPubKeyType; + final String? scriptPubKeyAddress; + final int value; + + MempoolVout({ + required this.scriptPubKey, + required this.scriptPubKeyAsm, + required this.scriptPubKeyType, + required this.scriptPubKeyAddress, + required this.value, + }); + + factory MempoolVout.fromJson(Map json) { + return MempoolVout( + scriptPubKey: json['scriptpubkey'], + scriptPubKeyAsm: json['scriptpubkey_asm'], + scriptPubKeyType: json['scriptpubkey_type'], + scriptPubKeyAddress: json['scriptpubkey_address'], + value: json['value'], + ); + } +} + +class MempoolStatus { + final bool confirmed; + final int? blockHeight; + final String? blockHash; + final int? blockTime; + + MempoolStatus({ + required this.confirmed, + required this.blockHeight, + required this.blockHash, + required this.blockTime, + }); + + factory MempoolStatus.fromJson(Map json) { + return MempoolStatus( + confirmed: json['confirmed'], + blockHeight: json['block_height'], + blockHash: json['block_hash'], + blockTime: json['block_time'], + ); + } +} + +class MempoolTransaction { + final String txID; + final int version; + final int locktime; + final List vin; + final List vout; + final int size; + final int weight; + final int fee; + final MempoolStatus status; + + MempoolTransaction({ + required this.txID, + required this.version, + required this.locktime, + required this.vin, + required this.vout, + required this.size, + required this.weight, + required this.fee, + required this.status, + }); + + factory MempoolTransaction.fromJson(Map json) { + return MempoolTransaction( + txID: json['txid'], + version: json['version'], + locktime: json['locktime'], + vin: + List.from(json['vin'].map((x) => MempoolVin.fromJson(x))), + vout: List.from( + json['vout'].map((x) => MempoolVout.fromJson(x))), + size: json['size'], + weight: json['weight'], + fee: json['fee'], + status: MempoolStatus.fromJson(json['status']), + ); + } +} + +class MempolUtxo { + final String txid; + final int vout; + final MempoolStatus status; + final BigInt value; + + MempolUtxo({ + required this.txid, + required this.vout, + required this.status, + required this.value, + }); + + factory MempolUtxo.fromJson(Map json) { + return MempolUtxo( + txid: json['txid'], + vout: json['vout'], + status: MempoolStatus.fromJson(json['status']), + value: BigInt.parse( + json['value'].toString()), // Parse the value as a BigInt. + ); + } +} + +extension MempoolUtxoExtentions on List { + List toUtxoWithOwnerList(UtxoOwnerDetails owner) { + List utxos = map((e) => UtxoWithOwner( + utxo: BitcoinUtxo( + txHash: e.txid, + value: e.value, + vout: e.vout, + scriptType: owner.address.type, + blockHeight: 1, + ), + ownerDetails: owner, + )).toList(); + + return utxos; + } +} diff --git a/lib/src/provider/multisig_script.dart b/lib/src/provider/multisig_script.dart new file mode 100644 index 0000000..4bbdeaa --- /dev/null +++ b/lib/src/provider/multisig_script.dart @@ -0,0 +1,117 @@ +import 'package:bitcoin_base/bitcoin.dart'; +import 'package:bitcoin_base/src/bitcoin/address/core.dart'; + +// MultiSignatureSigner is an interface that defines methods required for representing +// 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); + // PublicKey returns the public key associated with the signer. + final String publicKey; + + // Weight returns the weight or significance of the signer within the multi-signature scheme. + // The weight is used to determine the number of signatures required for a valid transaction. + final int weight; + // 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); + } +} + +// MultiSignatureAddress represents a multi-signature Bitcoin address configuration, including +// information about the required signers, threshold, the address itself, +// and the script details used for multi-signature transactions. +class MultiSignatureAddress { + // Signers is a collection of signers participating in the multi-signature scheme. + final List signers; + + // Threshold is the minimum number of signatures required to spend the bitcoins associated + // with this address. + final int threshold; + + // Address represents the Bitcoin address associated with this multi-signature configuration. + final BitcoinAddress address; + + // ScriptDetails provides details about the multi-signature script used in transactions, + // including "OP_M", compressed public keys, "OP_N", and "OP_CHECKMULTISIG." + final String scriptDetails; + + MultiSignatureAddress._({ + required this.signers, + required this.threshold, + required this.address, + required this.scriptDetails, + }); + + // 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, + required AddressType addressType, + }) { + final sumWeight = signers.fold(0, (sum, signer) => sum + signer.weight); + if (threshold > 16 || threshold < 1) { + throw Exception('The threshold should be between 1 and 16'); + } + if (sumWeight > 16) { + throw Exception('The total weight of the owners should not exceed 16'); + } + if (sumWeight < threshold) { + throw Exception( + 'The total weight of the signatories should reach the threshold'); + } + final multiSigScript = ['OP_$threshold']; + for (final signer in signers) { + for (var w = 0; w < signer.weight; w++) { + multiSigScript.add(signer.publicKey); + } + } + multiSigScript.addAll(['OP_$sumWeight', 'OP_CHECKMULTISIG']); + final script = Script(script: multiSigScript); + final p2wsh = P2wshAddress(script: script); + switch (addressType) { + case AddressType.p2wsh: + { + return MultiSignatureAddress._( + signers: signers, + threshold: threshold, + address: p2wsh, + scriptDetails: script.toHex(), + ); + } + case AddressType.p2wshInP2sh: + { + final addr = P2shAddress.fromScript( + script: p2wsh.toScriptPubKey(), type: AddressType.p2wshInP2sh); + return MultiSignatureAddress._( + signers: signers, + threshold: threshold, + address: addr, + scriptDetails: script.toHex(), + ); + } + default: + { + throw Exception('addressType should be P2WSH or P2WSHInP2SH'); + } + } + } + + List showScript() { + final sumWeight = signers.fold(0, (sum, signer) => sum + signer.weight); + final multiSigScript = ['OP_$threshold']; + for (final signer in signers) { + for (var w = 0; w < signer.weight; w++) { + multiSigScript.add(signer.publicKey); + } + } + multiSigScript.addAll(['OP_$sumWeight', 'OP_CHECKMULTISIG']); + return multiSigScript; + } +} diff --git a/lib/src/provider/provider.dart b/lib/src/provider/provider.dart new file mode 100644 index 0000000..f7c2268 --- /dev/null +++ b/lib/src/provider/provider.dart @@ -0,0 +1,93 @@ +// ignore_for_file: constant_identifier_names + +import 'package:bitcoin_base/src/models/network.dart'; + +enum APIType { + MempoolApi, + BlockCypherApi, +} + +class APIConfig { + final String url; + final String feeRate; + final String transaction; + final String transactions; + final String sendTransaction; + final APIType apiType; + final NetworkInfo network; + + factory APIConfig.selectApi(APIType apiType, NetworkInfo network) { + switch (apiType) { + case APIType.MempoolApi: + return APIConfig.mempool(network); + default: + return APIConfig.mempool(network); + } + } + + String getUtxoUrl(String address) { + String baseUrl = url; + return baseUrl.replaceAll("###", address); + } + + String getFeeApiUrl() { + return feeRate; + } + + String getTransactionUrl(String transactionId) { + String baseUrl = transaction; + return baseUrl.replaceAll("###", transactionId); + } + + String getTransactionsUrl(String address) { + String baseUrl = transactions; + return baseUrl.replaceAll("###", address); + } + + factory APIConfig.fromBlockCypher(NetworkInfo network) { + String baseUrl = + network.isMainnet ? blockCypherMainBaseURL : blockCypherBaseURL; + + return APIConfig( + url: "$baseUrl/addrs/###/?unspentOnly=true&includeScript=true&limit=2000", + feeRate: baseUrl, + transaction: "$baseUrl/txs/###", + sendTransaction: "$baseUrl/txs/push", + apiType: APIType.BlockCypherApi, + transactions: "$baseUrl/addrs/###/full?limit=200", + network: network, + ); + } + + factory APIConfig.mempool(NetworkInfo network) { + String baseUrl = network.isMainnet ? mempoolMainBaseURL : mempoolBaseURL; + + return APIConfig( + url: "$baseUrl/address/###/utxo", + feeRate: "$baseUrl/v1/fees/recommended", + transaction: "$baseUrl/tx/###", + sendTransaction: "$baseUrl/tx", + apiType: APIType.MempoolApi, + transactions: "$baseUrl/address/###/txs", + network: network, + ); + } + + APIConfig({ + required this.url, + required this.feeRate, + required this.transaction, + required this.transactions, + required this.sendTransaction, + required this.apiType, + required this.network, + }); +} + +const String blockCypherBaseURL = "https://api.blockcypher.com/v1/btc/test3"; +const String mempoolBaseURL = "https://mempool.space/testnet/api"; +const String blockstreamBaseURL = "https://blockstream.info/testnet/api"; + +const String blockCypherMainBaseURL = "https://api.blockcypher.com/v1/btc/main"; +const String mempoolMainBaseURL = "https://mempool.space/api"; +const String blockstreamMainBaseURL = "https://blockstream.info/api"; diff --git a/lib/src/provider/transaction_builder.dart b/lib/src/provider/transaction_builder.dart new file mode 100644 index 0000000..a7b299e --- /dev/null +++ b/lib/src/provider/transaction_builder.dart @@ -0,0 +1,528 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:bitcoin_base/bitcoin.dart'; +import 'package:bitcoin_base/src/bitcoin/address/core.dart'; +import 'package:bitcoin_base/src/formating/bytes_num_formating.dart'; +import 'package:bitcoin_base/src/models/network.dart'; +import 'package:bitcoin_base/src/provider/utxo_details.dart'; + +typedef BitcoinSignerCallBack = String Function( + Uint8List trDigest, UtxoWithOwner utxo, String publicKey); + +class BitcoinTransactionBuilder { + final List outPuts; + final BigInt fee; + final NetworkInfo network; + final List utxos; + final String? memo; + final bool enableRBF; + final bool isFakeTransaction; + BitcoinTransactionBuilder({ + required this.outPuts, + required this.fee, + required this.network, + required this.utxos, + this.memo = '', + this.enableRBF = false, + this.isFakeTransaction = false, + }); + + // This method is used to create a dummy transaction, + // allowing us to obtain the size of the original transaction + // before conducting the actual transaction. This helps us estimate the transaction cost + static int estimateTransactionSize( + {required List utxos, + required List outputs, + required NetworkInfo network, + String? memo, + bool enableRBF = false}) { + final sum = utxos.sumOfUtxosValue(); + + // We consider the total amount for the output because, + // in all cases, the size of the amount is 8 bytes. + final outs = outputs + .map((e) => BitcoinOutputDetails(address: e, value: sum)) + .toList(); + final transactionBuilder = BitcoinTransactionBuilder( + // Now, we provide the UTXOs we want to spend. + utxos: utxos, + // We select transaction outputs + outPuts: outs, + /* + Transaction fee + Ensure that you have accurately calculated the amounts. + If the sum of the outputs, including the transaction fee, + does not match the total amount of UTXOs, + it will result in an error. Please double-check your calculations. + */ + fee: BigInt.from(0), + // network, testnet, mainnet + network: network, + // If you like the note write something else and leave it blank + memo: memo, + /* + RBF, or Replace-By-Fee, is a feature in Bitcoin that allows you to increase the fee of an unconfirmed + transaction that you've broadcasted to the network. + This feature is useful when you want to speed up a + transaction that is taking longer than expected to get confirmed due to low transaction fees. + */ + enableRBF: true, + // We consider the transaction to be fake so that it doesn't check the amounts + // and doesn't generate errors when determining the transaction size. + isFakeTransaction: true, + ); + ECPrivate? fakePrivate; + final transaction = transactionBuilder + .buildTransaction((trDigest, utxo, multiSigPublicKey) { + fakePrivate ??= ECPrivate.random(); + if (utxo.utxo.isP2tr()) { + return fakePrivate!.signTapRoot(trDigest); + } else { + return fakePrivate!.signInput(trDigest); + } + }); + + // Now we need the size of the transaction. If the transaction is a SegWit transaction, + // we use the getVSize method; otherwise, we use the getSize method to obtain the transaction size + final size = + transaction.hasSegwit ? transaction.getVSize() : transaction.getSize(); + + return size; + } + +// HasSegwit checks whether any of the unspent transaction outputs (UTXOs) in the BitcoinTransactionBuilder's +// Utxos list are Segregated Witness (SegWit) UTXOs. It iterates through the Utxos list and returns true if it +// finds any UTXO with a SegWit script type; otherwise, it returns false. +// +// Returns: +// - bool: True if at least one UTXO in the list is a SegWit UTXO, false otherwise. + bool hasSegwit() { + for (final element in utxos) { + if (element.utxo.isSegwit()) { + return true; + } + } + return false; + } + +// HasTaproot checks whether any of the unspent transaction outputs (UTXOs) in the BitcoinTransactionBuilder's +// Utxos list are Pay-to-Taproot (P2TR) UTXOs. It iterates through the Utxos list and returns true if it finds +// any UTXO with a Taproot script type; otherwise, it returns false. +// +// Returns: +// - bool: True if at least one UTXO in the list is a P2TR UTXO, false otherwise. + bool hasTaproot() { + for (final element in utxos) { + if (element.utxo.isP2tr()) { + return true; + } + } + return false; + } + +// It is used to make the appropriate scriptSig + Script buildInputScriptPubKeys(UtxoWithOwner utxo, bool isTaproot) { + if (utxo.isMultiSig()) { + final script = Script.fromRaw( + hexData: utxo.ownerDetails.multiSigAddress!.scriptDetails, + hasSegwit: true); + switch (utxo.ownerDetails.multiSigAddress!.address.type) { + case AddressType.p2wshInP2sh: + if (isTaproot) { + return utxo.ownerDetails.multiSigAddress!.address.toScriptPubKey(); + } + return script; + case AddressType.p2wsh: + if (isTaproot) { + return utxo.ownerDetails.multiSigAddress!.address.toScriptPubKey(); + } + return script; + default: + return const Script(script: []); + } + } + + final senderPub = utxo.public(); + switch (utxo.utxo.scriptType) { + case AddressType.p2pk: + return senderPub.toRedeemScript(); + case AddressType.p2wsh: + if (isTaproot) { + return senderPub.toP2wshAddress().toScriptPubKey(); + } + return senderPub.toP2wshScript(); + case AddressType.p2pkh: + return senderPub.toAddress().toScriptPubKey(); + case AddressType.p2wpkh: + if (isTaproot) { + return senderPub.toSegwitAddress().toScriptPubKey(); + } + return senderPub.toAddress().toScriptPubKey(); + case AddressType.p2tr: + return senderPub.toTaprootAddress().toScriptPubKey(); + case AddressType.p2pkhInP2sh: + if (isTaproot) { + return senderPub.toP2pkhInP2sh().toScriptPubKey(); + } + return senderPub.toAddress().toScriptPubKey(); + case AddressType.p2wpkhInP2sh: + if (isTaproot) { + return senderPub.toP2wpkhInP2sh().toScriptPubKey(); + } + return senderPub.toAddress().toScriptPubKey(); + case AddressType.p2wshInP2sh: + if (isTaproot) { + return senderPub.toP2wshInP2sh().toScriptPubKey(); + } + return senderPub.toP2wshScript(); + case AddressType.p2pkInP2sh: + if (isTaproot) { + return senderPub.toP2pkInP2sh().toScriptPubKey(); + } + return senderPub.toRedeemScript(); + default: + return const Script(script: []); + } + } + +// generateTransactionDigest generates and returns a transaction digest for a given input in the context of a Bitcoin +// transaction. The digest is used for signing the transaction input. The function takes into account whether the +// associated UTXO is Segregated Witness (SegWit) or Pay-to-Taproot (P2TR), and it computes the appropriate digest +// based on these conditions. +// +// Parameters: +// - scriptPubKeys: representing the scriptPubKey for the transaction output being spent. +// - input: An integer indicating the index of the input being processed within the transaction. +// - utox: A UtxoWithOwner instance representing the unspent transaction output (UTXO) associated with the input. +// - transaction: A BtcTransaction representing the Bitcoin transaction being constructed. +// - taprootAmounts: A List of BigInt containing taproot-specific amounts for P2TR inputs (ignored for non-P2TR inputs). +// - tapRootPubKeys: A List of of Script representing taproot public keys for P2TR inputs (ignored for non-P2TR inputs). +// +// Returns: +// - Uint8List: representing the transaction digest to be used for signing the input. + Uint8List generateTransactionDigest( + Script scriptPubKeys, + int input, + UtxoWithOwner utox, + BtcTransaction transaction, + List taprootAmounts, + List