From 6a8cbb32e2d0b3e1b2d8e68d727b1642006e2d31 Mon Sep 17 00:00:00 2001 From: Gabriel Cardona Date: Thu, 1 Aug 2019 10:18:59 -0700 Subject: [PATCH 001/106] Fix last name typo. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a77bcd..006a5d5 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,6 @@ Optionally between step 1) and 2), send some balance to either testnet or mainne ## Acknowledgments -This is a port of the original JS-based Bitbox library by Gabriel Cordana and Bitcoin.com, so first of all huge thanks to Gabriel and the whole Bitcoin.com team for doing so much for the BCH ecosystem. +This is a port of the original JS-based Bitbox library by Gabriel Cardona and Bitcoin.com, so first of all huge thanks to Gabriel and the whole Bitcoin.com team for doing so much for the BCH ecosystem. Also I either re-used a lot of code originally wrote for Bitcoin or called some libraries (bip39 and bip32) by [anicdh](https://github.com/anicdh), so Thanks big time to him. Without that it would take me many more weeks! From dad85bb6c1f74d01f8ae65a6997d64ceaaa342a4 Mon Sep 17 00:00:00 2001 From: tomas Date: Sun, 6 Oct 2019 20:30:50 +0200 Subject: [PATCH 002/106] - taken some code analysis hints into account - moved test/readmemdtest.dart code to example/main.dart to align it with dart pub standard --- .gitignore | 1 - README.md | 9 +- create_test_data.js | 2 +- example/main.dart | 111 +++++++++++++++ lib/bitbox.dart | 2 +- lib/src/account.dart | 2 +- lib/src/address.dart | 9 +- lib/src/bitbox.dart | 4 +- lib/src/bitcoincash.dart | 2 +- lib/src/crypto/crypto.dart | 2 +- lib/src/crypto/ecurve.dart | 38 ++--- lib/src/ecpair.dart | 6 +- lib/src/rawtransactions.dart | 4 +- lib/src/transaction.dart | 19 ++- lib/src/transactionbuilder.dart | 13 +- lib/src/utils/check_types.dart | 5 +- lib/src/utils/network.dart | 11 +- lib/src/utils/opcodes.dart | 238 ++++++++++++++++---------------- lib/src/utils/pushdata.dart | 26 ++-- lib/src/utils/rest_api.dart | 2 +- lib/src/utils/script.dart | 8 +- lib/src/varuint.dart | 3 +- pubspec.lock | 4 +- pubspec.yaml | 7 +- test/bitbox_test.dart | 2 +- test/readmemdtest.dart | 113 --------------- 26 files changed, 322 insertions(+), 321 deletions(-) create mode 100644 example/main.dart delete mode 100644 test/readmemdtest.dart diff --git a/.gitignore b/.gitignore index 9d7edcf..dc1e7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ .vscode/ # Flutter/Dart/Pub related -**/doc/api/ .dart_tool/ .flutter-plugins .packages diff --git a/README.md b/README.md index 9a77bcd..21d9ba3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,14 @@ Works with mainnet and testnet. ## Getting Started ### 1) Depend on it -After you download the repository, add a local dependency into the pubspec.yaml of your testing or development projet: +If you just want to get this from Dart's public package directory: + +``` +dependencies: + bitbox: ^0.0.1 +``` + +If you checked this out from Github, add a local dependency into the pubspec.yaml of your testing or development projet: ``` dependencies: diff --git a/create_test_data.js b/create_test_data.js index dbac9a1..4f9e3d9 100644 --- a/create_test_data.js +++ b/create_test_data.js @@ -27,7 +27,7 @@ function createTestData(mnemonic, testnet) { "master_xpriv": bitbox.HDNode.toXPriv(masterNode), "master_xpub": bitbox.HDNode.toXPub(masterNode), "account_xpriv": bitbox.HDNode.toXPriv(accountNode), - "account_xpub": bitbox.HDNode.toXPub(accountNode), + "account_xpub": bitbox.HDNode.tdoXPub(accountNode), "child_nodes" : [] }; diff --git a/example/main.dart b/example/main.dart new file mode 100644 index 0000000..cdcc5f7 --- /dev/null +++ b/example/main.dart @@ -0,0 +1,111 @@ +import 'package:bitbox/bitbox.dart' as Bitbox; + +void main() async { + // set this to false to use mainnet + final testnet = false; + + // After running the code for the first time, depositing an amount to the address displayed in the console, + // and waiting for confirmation, paste the generated mnemonic here, + // so the code continues below with address withdrawal + String mnemonic = "leaf tackle snap liar core motion material live camp quote mercy void"; + + if (mnemonic == "") { + // generate 12-word (128bit) mnemonic + mnemonic = Bitbox.Mnemonic.generate(); + + print(mnemonic); + } + + // generate a seed from mnemonic + final seed = Bitbox.Mnemonic.toSeed(mnemonic); + + // create an instance of Bitbox.HDNode for mainnet + final masterNode = Bitbox.HDNode.fromSeed(seed, testnet); + + // This format is compatible with Bitcoin.com wallet. + // Other wallets use Change to m/44'/145'/0'/0 + final accountDerivationPath = "m/44'/0'/0'/0"; + + // create an account node using the provided derivation path + final accountNode = masterNode.derivePath(accountDerivationPath); + + // get account's extended private key + final accountXPriv = accountNode.toXPriv(); + + // create a Bitbox.HDNode instance of the first child in this account + final childNode = accountNode.derive(0); + + // get an address of the child + final address = childNode.toCashAddress(); + + // if you are using testnet, set the appropriate rest api url before making + // any API calls (like getting address or transaction details or broadcasting a transaction + if (testnet) { + Bitbox.Bitbox.setRestUrl(restUrl: Bitbox.Bitbox.trestUrl); + } + + // get address details + final addressDetails = await Bitbox.Address.details(address); + + print(addressDetails); + + // If there is a confirmed balance, attempt to withdraw it to the address defined below + if (addressDetails["balance"] > 0) { + final builder = Bitbox.Bitbox.transactionBuilder(testnet: testnet); + + // retrieve address' utxos from the rest api + final utxos = await Bitbox.Address.utxo(address) as List; + + // placeholder for input signatures + final signatures = []; + + // placeholder for total input balance + int totalBalance = 0; + + // iterate through the list of address utxos and use them as inputs for the withdrawal transaction + utxos.forEach((Bitbox.Utxo utxo) { + // add the utxo as an input for the transaction + builder.addInput(utxo.txid, utxo.vout); + + // add a signature to the list to be used later + signatures.add({ + "vin": signatures.length, + "key_pair": childNode.keyPair, + "original_amount": utxo.satoshis + }); + + totalBalance += utxo.satoshis; + }); + + // set an address to send the remaining balance to + final outputAddress = "bitcoincash:qq4vzza5uhgr42ntkl28x67qzda4af5hpgap6z0ntx"; + + // if there is an unspent balance, create a spending transaction + if (totalBalance > 0 && outputAddress != "") { + // calculate the fee based on number of inputs and one expected output + final fee = Bitbox.BitcoinCash.getByteCount(signatures.length, 1); + + // calculate how much balance will be left over to spend after the fee + final sendAmount = totalBalance - fee; + + // add the output based on the address provided in the testing data + builder.addOutput(outputAddress, sendAmount); + + // sign all inputs + signatures.forEach((signature) { + builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); + }); + + // build the transaction + final tx = builder.build(); + + // broadcast the transaction + final txid = await Bitbox.RawTransactions.sendRawTransaction(tx.toHex()); + + // Yatta! + print("Transaction broadcasted: $txid"); + } else if (totalBalance > 0) { + print("Enter an output address to test withdrawal transaction"); + } + } +} diff --git a/lib/bitbox.dart b/lib/bitbox.dart index b8704b1..226fc36 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -10,4 +10,4 @@ export 'src/mnemonic.dart'; export 'src/rawtransactions.dart'; export 'src/transaction.dart'; export 'src/transactionbuilder.dart'; -export 'src/varuint.dart'; \ No newline at end of file +export 'src/varuint.dart'; diff --git a/lib/src/account.dart b/lib/src/account.dart index ed20836..f9ac976 100644 --- a/lib/src/account.dart +++ b/lib/src/account.dart @@ -25,4 +25,4 @@ class Account { return accountNode.derive(++currentChild).toCashAddress(); } } -} \ No newline at end of file +} diff --git a/lib/src/address.dart b/lib/src/address.dart index 7c4d162..45c5104 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -4,12 +4,8 @@ import 'utils/rest_api.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'utils/network.dart'; -import 'utils/opcodes.dart'; -import 'utils/script.dart' as bscript; import 'package:fixnum/fixnum.dart'; -import 'hdnode.dart'; - /// Works with both legacy and cashAddr formats of the address /// /// There is no reason to instanciate this class. All constants, functions, and methods are static. @@ -158,6 +154,7 @@ class Address { return bs58check.encode(payload); } + /* static Uint8List _toOutputScript(address, network) { return bscript.compile([ Opcodes.OP_DUP, @@ -166,7 +163,7 @@ class Address { Opcodes.OP_EQUALVERIFY, Opcodes.OP_CHECKSIG ]); - } + }*/ /// Encodes a hash from a given type into a Bitcoin Cash address with the given prefix. /// [prefix] - Network prefix. E.g.: 'bitcoincash'. @@ -249,6 +246,8 @@ class Address { case 7: return 512; } + + return -1; } /// Decodes the given address into: diff --git a/lib/src/bitbox.dart b/lib/src/bitbox.dart index 61d185a..f7caf62 100644 --- a/lib/src/bitbox.dart +++ b/lib/src/bitbox.dart @@ -16,6 +16,6 @@ class Bitbox { /// /// It is possible to call [TransactionBuilder] directly and pass [Network] parameter, this just makes it easier static TransactionBuilder transactionBuilder({testnet: false}) => - TransactionBuilder(network: testnet ? Network.bitcoinCashTest() : Network.bitcoinCash()); + TransactionBuilder( + network: testnet ? Network.bitcoinCashTest() : Network.bitcoinCash()); } - diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index 4d040c6..10dc2e2 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -14,4 +14,4 @@ class BitcoinCash { static int getByteCount(int inputs, int outputs) { return ((inputs * 148 * 4 + 34 * 4 * outputs + 10 * 4) / 4).ceil(); } -} \ No newline at end of file +} diff --git a/lib/src/crypto/crypto.dart b/lib/src/crypto/crypto.dart index 9ea97c3..354b947 100644 --- a/lib/src/crypto/crypto.dart +++ b/lib/src/crypto/crypto.dart @@ -18,4 +18,4 @@ Uint8List hmacSHA512(Uint8List key, Uint8List data) { Uint8List hash256(Uint8List buffer) { Uint8List _tmp = new SHA256Digest().process(buffer); return new SHA256Digest().process(_tmp); -} \ No newline at end of file +} diff --git a/lib/src/crypto/ecurve.dart b/lib/src/crypto/ecurve.dart index 5c66169..23f1e54 100644 --- a/lib/src/crypto/ecurve.dart +++ b/lib/src/crypto/ecurve.dart @@ -8,12 +8,13 @@ class ECurve { static final secp256k1 = new ECCurve_secp256k1(); static final n = secp256k1.n; static final G = secp256k1.G; - static final ZERO32 = Uint8List.fromList(List.generate(32, (index) => 0)); - static final EC_GROUP_ORDER = HEX.decode("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); - static final EC_P = HEX.decode("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + static final zero32 = Uint8List.fromList(List.generate(32, (index) => 0)); + static final ecGroupOrder = HEX.decode( + "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); + static final ecP = HEX.decode( + "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); - - static Uint8List privateAdd (Uint8List d,Uint8List tweak) { + static Uint8List privateAdd(Uint8List d, Uint8List tweak) { // if (!isPrivate(d)) throw new ArgumentError(THROW_BAD_PRIVATE); // if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK); BigInt dd = decodeBigInt(d); @@ -23,13 +24,13 @@ class ECurve { return dt; } - static bool isPrivate (Uint8List x) { + static bool isPrivate(Uint8List x) { if (!isScalar(x)) return false; - return _compare(x, ZERO32) > 0 && // > 0 - _compare(x, EC_GROUP_ORDER) < 0; // < G + return _compare(x, zero32) > 0 && // > 0 + _compare(x, ecGroupOrder) < 0; // < G } - static bool isScalar (Uint8List x) { + static bool isScalar(Uint8List x) { return x.length == 32; } @@ -41,12 +42,13 @@ class ECurve { return pp.getEncoded(_compressed); } - static Uint8List pointAddScalar(Uint8List p,Uint8List tweak, bool _compressed) { + static Uint8List pointAddScalar( + Uint8List p, Uint8List tweak, bool _compressed) { // if (!isPoint(p)) throw new ArgumentError(THROW_BAD_POINT); // if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK); bool compressed = assumeCompression(_compressed, 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; ECPoint uu = pp + qq; @@ -75,25 +77,25 @@ class ECurve { var t = p[0]; var x = p.sublist(1, 33); - if (_compare(x, ZERO32) == 0) { + if (_compare(x, zero32) == 0) { return false; } - if (_compare(x, EC_P) == 1) { + if (_compare(x, ecP) == 1) { return false; } try { decodeFrom(p); - } catch(err) { + } catch (err) { return false; } if ((t == 0x02 || t == 0x03) && p.length == 33) { return true; } var y = p.sublist(33); - if (_compare(y, ZERO32) == 0) { + if (_compare(y, zero32) == 0) { return false; } - if (_compare(y, EC_P) == 1) { + if (_compare(y, ecP) == 1) { return false; } if (t == 0x04 && p.length == 65) { @@ -102,9 +104,9 @@ class ECurve { return false; } - static bool _isPointCompressed (Uint8List p) { + static bool _isPointCompressed(Uint8List p) { return p[0] != 0x04; } static ECPoint decodeFrom(Uint8List P) => secp256k1.curve.decodePoint(P); -} \ No newline at end of file +} diff --git a/lib/src/ecpair.dart b/lib/src/ecpair.dart index fc6ee82..8ec0ac8 100644 --- a/lib/src/ecpair.dart +++ b/lib/src/ecpair.dart @@ -10,12 +10,12 @@ import 'utils/network.dart'; /// Stores a keypair and provides various methods and factories for creating it and working with it class ECPair { final Uint8List _d; - final Uint8List _Q; + final Uint8List _q; final Network network; final bool compressed; /// Default constructor. If [network] is not provided, it will assume Bitcoin Cash mainnet - ECPair(this._d, this._Q, {network, this.compressed = true}): + ECPair(this._d, this._q, {network, this.compressed = true}): this.network = network ?? Network.bitcoinCash(); /// Creates a keypair from the private key provided in WIF format @@ -73,7 +73,7 @@ class ECPair { return ECPair.fromPrivateKey(d, network: network, compressed: compressed); } - Uint8List get publicKey => _Q ?? ecc.pointFromScalar(_d, compressed); + Uint8List get publicKey => _q ?? ecc.pointFromScalar(_d, compressed); Uint8List get privateKey => _d; diff --git a/lib/src/rawtransactions.dart b/lib/src/rawtransactions.dart index a45a617..eee3e78 100644 --- a/lib/src/rawtransactions.dart +++ b/lib/src/rawtransactions.dart @@ -5,5 +5,5 @@ class RawTransactions { /// Send raw transaction to the network /// Returns the resulting txid static Future sendRawTransaction(String rawTx) async => - await RestApi.sendGetRequest("rawtransactions/sendRawTransaction", rawTx); -} \ No newline at end of file + await RestApi.sendGetRequest("rawtransactions/sendRawTransaction", rawTx); +} diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 23df692..afb59d5 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -4,7 +4,6 @@ import 'utils/p2pkh.dart' show P2PKH, P2PKHData; import 'crypto/crypto.dart' as bcrypto; import 'utils/script.dart' as bscript; import 'utils/opcodes.dart'; -import 'hdnode.dart'; import 'utils/check_types.dart'; import 'varuint.dart' as varuint; @@ -21,11 +20,11 @@ class Transaction { static const SIGHASH_BITCOINCASHBIP143 = 0x40; static const ADVANCED_TRANSACTION_MARKER = 0x00; static const ADVANCED_TRANSACTION_FLAG = 0x01; - static final EMPTY_SCRIPT = Uint8List.fromList([]); - static final ZERO = HEX.decode('0000000000000000000000000000000000000000000000000000000000000000'); - static final ONE = HEX.decode('0000000000000000000000000000000000000000000000000000000000000001'); - static final VALUE_UINT64_MAX = HEX.decode('ffffffffffffffff'); - static final BLANK_OUTPUT = Output(script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX); + static final emptyScript = Uint8List.fromList([]); + static final zero = HEX.decode('0000000000000000000000000000000000000000000000000000000000000000'); + static final one = HEX.decode('0000000000000000000000000000000000000000000000000000000000000001'); + static final valueUint64Max = HEX.decode('ffffffffffffffff'); + static final blankOutput = Output(script: emptyScript, valueBuffer: valueUint64Max); static const SATOSHI_MAX = 21 * 1e14; int version; @@ -121,7 +120,7 @@ class Transaction { hash: hash, index: index, sequence: sequence ?? DEFAULT_SEQUENCE, - script: scriptSig ?? EMPTY_SCRIPT)); + script: scriptSig ?? emptyScript)); return inputs.length - 1; } @@ -137,7 +136,7 @@ class Transaction { /// Create hash for legacy signature hashForSignature(int inIndex, Uint8List prevOutScript, int hashType) { - if (inIndex >= inputs.length) return ONE; + if (inIndex >= inputs.length) return one; // ignore OP_CODESEPARATOR final ourScript = bscript.compile(bscript.decompile(prevOutScript).where((x) { return x != Opcodes.OP_CODESEPARATOR; @@ -156,14 +155,14 @@ class Transaction { // SIGHASH_SINGLE: ignore all outputs, except at the same index? } else if ((hashType & 0x1f) == SIGHASH_SINGLE) { // https://github.com/bitcoin/bitcoin/blob/master/src/test/sighash_tests.cpp#L60 - if (inIndex >= outputs.length) return ONE; + if (inIndex >= outputs.length) return one; // truncate outputs after txTmp.outputs.length = inIndex + 1; // "blank" outputs before for (var i = 0; i < inIndex; i++) { - txTmp.outputs[i] = BLANK_OUTPUT; + txTmp.outputs[i] = blankOutput; } // ignore sequence numbers (except at inIndex) for (var i = 0; i < txTmp.inputs.length; i++) { diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index ae62634..679f215 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -1,7 +1,6 @@ import 'dart:typed_data'; import 'package:hex/hex.dart'; import 'package:bs58check/bs58check.dart' as bs58check; -import 'hdnode.dart'; import 'address.dart'; import 'crypto/crypto.dart'; import 'utils/network.dart'; @@ -15,12 +14,12 @@ import 'bitcoincash.dart'; /// Toolbox for creating a transaction, that can be broadcasted to BCH network. Works only as an instance created /// through one of the factories or a constructor class TransactionBuilder { - final DEFAULT_SEQUENCE = 0xffffffff; - final SIGHASH_ALL = 0x01; - final SIGHASH_NONE = 0x02; - final SIGHASH_SINGLE = 0x03; - final SIGHASH_ANYONECANPAY = 0x80; - final SIGHASH_BITCOINCASHBIP143 = 0x40; + static const DEFAULT_SEQUENCE = 0xffffffff; + static const SIGHASH_ALL = 0x01; + static const SIGHASH_NONE = 0x02; + static const SIGHASH_SINGLE = 0x03; + static const SIGHASH_ANYONECANPAY = 0x80; + static const SIGHASH_BITCOINCASHBIP143 = 0x40; final Network _network; final int _maximumFeeRate; diff --git a/lib/src/utils/check_types.dart b/lib/src/utils/check_types.dart index 6dab37a..66fca14 100644 --- a/lib/src/utils/check_types.dart +++ b/lib/src/utils/check_types.dart @@ -1,5 +1,6 @@ import 'dart:typed_data'; import 'dart:math'; + const SATOSHI_MAX = 21 * 1e14; bool isSatoshi(int value) { @@ -9,9 +10,11 @@ bool isSatoshi(int value) { bool isUint(int value, int bit) { return (value >= 0 && value <= pow(2, bit) - 1); } + bool isHash160bit(Uint8List value) { return value.length == 20; } + bool isHash256bit(Uint8List value) { return value.length == 32; -} \ No newline at end of file +} diff --git a/lib/src/utils/network.dart b/lib/src/utils/network.dart index cf9956b..d1aa0a3 100644 --- a/lib/src/utils/network.dart +++ b/lib/src/utils/network.dart @@ -16,10 +16,13 @@ class Network { final int private; final int public; - Network(this.bip32Private, this.bip32Public, this.testnet, this.pubKeyHash, this.private, this.public); + Network(this.bip32Private, this.bip32Public, this.testnet, this.pubKeyHash, + this.private, this.public); - factory Network.bitcoinCash() => Network(0x0488ade4, 0x0488b21e, false, 0x00, bchPrivate, bchPublic); - factory Network.bitcoinCashTest() => Network(0x04358394, 0x043587cf, true, 0x6f, bchTestnetPrivate, bchTestnetPublic); + factory Network.bitcoinCash() => + Network(0x0488ade4, 0x0488b21e, false, 0x00, bchPrivate, bchPublic); + factory Network.bitcoinCashTest() => Network( + 0x04358394, 0x043587cf, true, 0x6f, bchTestnetPrivate, bchTestnetPublic); String get prefix => this.testnet ? "bchtest" : "bitcoincash"; -} \ No newline at end of file +} diff --git a/lib/src/utils/opcodes.dart b/lib/src/utils/opcodes.dart index 189c2f7..25a700d 100644 --- a/lib/src/utils/opcodes.dart +++ b/lib/src/utils/opcodes.dart @@ -1,120 +1,120 @@ class Opcodes { - static const OP_FALSE = 0; - static const OP_0 = 0; - static const OP_PUSHDATA1 = 76; - static const OP_PUSHDATA2 = 77; - static const OP_PUSHDATA4 = 78; - static const OP_1NEGATE = 79; - static const OP_RESERVED = 80; - static const OP_TRUE = 81; - static const OP_1 = 81; - static const OP_2 = 82; - static const OP_3 = 83; - static const OP_4 = 84; - static const OP_5 = 85; - static const OP_6 = 86; - static const OP_7 = 87; - static const OP_8 = 88; - static const OP_9 = 89; - static const OP_10 = 90; - static const OP_11 = 91; - static const OP_12 = 92; - static const OP_13 = 93; - static const OP_14 = 94; - static const OP_15 = 95; - static const OP_16 = 96; - static const OP_NOP = 97; - static const OP_VER = 98; - static const OP_IF = 99; - static const OP_NOTIF = 100; - static const OP_VERIF = 101; - static const OP_VERNOTIF = 102; - static const OP_ELSE = 103; - static const OP_ENDIF = 104; - static const OP_VERIFY = 105; - static const OP_RETURN = 106; - static const OP_TOALTSTACK = 107; - static const OP_FROMALTSTACK = 108; - static const OP_2DROP = 109; - static const OP_2DUP = 110; - static const OP_3DUP = 111; - static const OP_2OVER = 112; - static const OP_2ROT = 113; - static const OP_2SWAP = 114; - static const OP_IFDUP = 115; - static const OP_DEPTH = 116; - static const OP_DROP = 117; - static const OP_DUP = 118; - static const OP_NIP = 119; - static const OP_OVER = 120; - static const OP_PICK = 121; - static const OP_ROLL = 122; - static const OP_ROT = 123; - static const OP_SWAP = 124; - static const OP_TUCK = 125; - static const OP_CAT = 126; - static const OP_SPLIT = 127; - static const OP_NUM2BIN = 128; - static const OP_BIN2NUM = 129; - static const OP_SIZE = 130; - static const OP_INVERT = 131; - static const OP_AND = 132; - static const OP_OR = 133; - static const OP_XOR = 134; - static const OP_EQUAL = 135; - static const OP_EQUALVERIFY = 136; - static const OP_RESERVED1 = 137; - static const OP_RESERVED2 = 138; - static const OP_1ADD = 139; - static const OP_1SUB = 140; - static const OP_2MUL = 141; - static const OP_2DIV = 142; - static const OP_NEGATE = 143; - static const OP_ABS = 144; - static const OP_NOT = 145; - static const OP_0NOTEQUAL = 146; - static const OP_ADD = 147; - static const OP_SUB = 148; - static const OP_MUL = 149; - static const OP_DIV = 150; - static const OP_MOD = 151; - static const OP_LSHIFT = 152; - static const OP_RSHIFT = 153; - static const OP_BOOLAND = 154; - static const OP_BOOLOR = 155; - static const OP_NUMEQUAL = 156; - static const OP_NUMEQUALVERIFY = 157; - static const OP_NUMNOTEQUAL = 158; - static const OP_LESSTHAN = 159; - static const OP_GREATERTHAN = 160; - static const OP_LESSTHANOREQUAL = 161; - static const OP_GREATERTHANOREQUAL = 162; - static const OP_MIN = 163; - static const OP_MAX = 164; - static const OP_WITHIN = 165; - static const OP_RIPEMD160 = 166; - static const OP_SHA1 = 167; - static const OP_SHA256 = 168; - static const OP_HASH160 = 169; - static const OP_HASH256 = 170; - static const OP_CODESEPARATOR = 171; - static const OP_CHECKSIG = 172; - static const OP_CHECKSIGVERIFY = 173; - static const OP_CHECKMULTISIG = 174; - static const OP_CHECKMULTISIGVERIFY = 175; - static const OP_NOP1 = 176; - static const OP_NOP2 = 177; - static const OP_CHECKLOCKTIMEVERIFY = 177; - static const OP_NOP3 = 178; - static const OP_CHECKSEQUENCEVERIFY = 178; - static const OP_NOP4 = 179; - static const OP_NOP5 = 180; - static const OP_NOP6 = 181; - static const OP_NOP7 = 182; - static const OP_NOP8 = 183; - static const OP_NOP9 = 184; - static const OP_NOP10 = 185; - static const OP_PUBKEYHASH = 253; - static const OP_PUBKEY = 254; - static const OP_INVALIDOPCODE = 255; -} \ No newline at end of file + static const OP_FALSE = 0; + static const OP_0 = 0; + static const OP_PUSHDATA1 = 76; + static const OP_PUSHDATA2 = 77; + static const OP_PUSHDATA4 = 78; + static const OP_1NEGATE = 79; + static const OP_RESERVED = 80; + static const OP_TRUE = 81; + static const OP_1 = 81; + static const OP_2 = 82; + static const OP_3 = 83; + static const OP_4 = 84; + static const OP_5 = 85; + static const OP_6 = 86; + static const OP_7 = 87; + static const OP_8 = 88; + static const OP_9 = 89; + static const OP_10 = 90; + static const OP_11 = 91; + static const OP_12 = 92; + static const OP_13 = 93; + static const OP_14 = 94; + static const OP_15 = 95; + static const OP_16 = 96; + static const OP_NOP = 97; + static const OP_VER = 98; + static const OP_IF = 99; + static const OP_NOTIF = 100; + static const OP_VERIF = 101; + static const OP_VERNOTIF = 102; + static const OP_ELSE = 103; + static const OP_ENDIF = 104; + static const OP_VERIFY = 105; + static const OP_RETURN = 106; + static const OP_TOALTSTACK = 107; + static const OP_FROMALTSTACK = 108; + static const OP_2DROP = 109; + static const OP_2DUP = 110; + static const OP_3DUP = 111; + static const OP_2OVER = 112; + static const OP_2ROT = 113; + static const OP_2SWAP = 114; + static const OP_IFDUP = 115; + static const OP_DEPTH = 116; + static const OP_DROP = 117; + static const OP_DUP = 118; + static const OP_NIP = 119; + static const OP_OVER = 120; + static const OP_PICK = 121; + static const OP_ROLL = 122; + static const OP_ROT = 123; + static const OP_SWAP = 124; + static const OP_TUCK = 125; + static const OP_CAT = 126; + static const OP_SPLIT = 127; + static const OP_NUM2BIN = 128; + static const OP_BIN2NUM = 129; + static const OP_SIZE = 130; + static const OP_INVERT = 131; + static const OP_AND = 132; + static const OP_OR = 133; + static const OP_XOR = 134; + static const OP_EQUAL = 135; + static const OP_EQUALVERIFY = 136; + static const OP_RESERVED1 = 137; + static const OP_RESERVED2 = 138; + static const OP_1ADD = 139; + static const OP_1SUB = 140; + static const OP_2MUL = 141; + static const OP_2DIV = 142; + static const OP_NEGATE = 143; + static const OP_ABS = 144; + static const OP_NOT = 145; + static const OP_0NOTEQUAL = 146; + static const OP_ADD = 147; + static const OP_SUB = 148; + static const OP_MUL = 149; + static const OP_DIV = 150; + static const OP_MOD = 151; + static const OP_LSHIFT = 152; + static const OP_RSHIFT = 153; + static const OP_BOOLAND = 154; + static const OP_BOOLOR = 155; + static const OP_NUMEQUAL = 156; + static const OP_NUMEQUALVERIFY = 157; + static const OP_NUMNOTEQUAL = 158; + static const OP_LESSTHAN = 159; + static const OP_GREATERTHAN = 160; + static const OP_LESSTHANOREQUAL = 161; + static const OP_GREATERTHANOREQUAL = 162; + static const OP_MIN = 163; + static const OP_MAX = 164; + static const OP_WITHIN = 165; + static const OP_RIPEMD160 = 166; + static const OP_SHA1 = 167; + static const OP_SHA256 = 168; + static const OP_HASH160 = 169; + static const OP_HASH256 = 170; + static const OP_CODESEPARATOR = 171; + static const OP_CHECKSIG = 172; + static const OP_CHECKSIGVERIFY = 173; + static const OP_CHECKMULTISIG = 174; + static const OP_CHECKMULTISIGVERIFY = 175; + static const OP_NOP1 = 176; + static const OP_NOP2 = 177; + static const OP_CHECKLOCKTIMEVERIFY = 177; + static const OP_NOP3 = 178; + static const OP_CHECKSEQUENCEVERIFY = 178; + static const OP_NOP4 = 179; + static const OP_NOP5 = 180; + static const OP_NOP6 = 181; + static const OP_NOP7 = 182; + static const OP_NOP8 = 183; + static const OP_NOP9 = 184; + static const OP_NOP10 = 185; + static const OP_PUBKEYHASH = 253; + static const OP_PUBKEY = 254; + static const OP_INVALIDOPCODE = 255; +} diff --git a/lib/src/utils/pushdata.dart b/lib/src/utils/pushdata.dart index 090a28c..6233982 100644 --- a/lib/src/utils/pushdata.dart +++ b/lib/src/utils/pushdata.dart @@ -13,8 +13,8 @@ class EncodedPushData { Uint8List buffer; EncodedPushData({this.size, this.buffer}); - } + EncodedPushData encode(Uint8List buffer, number, offset) { var size = encodingLength(number); // ~6 bit @@ -37,10 +37,7 @@ EncodedPushData encode(Uint8List buffer, number, offset) { buffer.buffer.asByteData().setUint32(offset + 1, number, Endian.little); } - return new EncodedPushData( - size: size, - buffer: buffer - ); + return new EncodedPushData(size: size, buffer: buffer); } DecodedPushData decode(Uint8List bf, int offset) { @@ -68,21 +65,16 @@ DecodedPushData decode(Uint8List bf, int offset) { // 32 bit } else { if (offset + 5 > buffer.lengthInBytes) return null; - if (opcode != Opcodes.OP_PUSHDATA4) throw new ArgumentError('Unexpected opcode'); + if (opcode != Opcodes.OP_PUSHDATA4) + throw new ArgumentError('Unexpected opcode'); number = buffer.asByteData().getUint32(offset + 1); size = 5; } - return DecodedPushData( - opcode: opcode, - number: number, - size: size - ); + return DecodedPushData(opcode: opcode, number: number, size: size); +} + +int encodingLength(i) { + return i < Opcodes.OP_PUSHDATA1 ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5; } -int encodingLength (i) { - return i < Opcodes.OP_PUSHDATA1 ? 1 - : i <= 0xff ? 2 - : i <= 0xffff ? 3 - : 5; -} \ No newline at end of file diff --git a/lib/src/utils/rest_api.dart b/lib/src/utils/rest_api.dart index 74ed000..741fd5c 100644 --- a/lib/src/utils/rest_api.dart +++ b/lib/src/utils/rest_api.dart @@ -20,7 +20,7 @@ class RestApi { static Future sendPostRequest(String path, String postKey, List data, {String returnKey}) async { final response = await http.post( - "${_restUrl}$path", + "$_restUrl$path", headers: {"content-type": "application/json"}, body: jsonEncode({"addresses" : data}), ); diff --git a/lib/src/utils/script.dart b/lib/src/utils/script.dart index 51a09cf..f0d571b 100644 --- a/lib/src/utils/script.dart +++ b/lib/src/utils/script.dart @@ -5,8 +5,8 @@ import 'pushdata.dart' as pushData; import 'check_types.dart'; //import 'check_types.dart'; //Map REVERSE_OPS = opcodes.map((String string, int number) => new MapEntry(number, string)); -final OP_INT_BASE = Opcodes.OP_RESERVED; -final ZERO = Uint8List.fromList([0]); +const OP_INT_BASE = Opcodes.OP_RESERVED; +final zero = Uint8List.fromList([0]); Uint8List compile(List chunks) { final bufferSize = chunks.fold(0, (acc, chunk) { @@ -193,9 +193,9 @@ Uint8List encodeSignature(Uint8List signature, int hashType) { Uint8List toDER (Uint8List x) { var i = 0; while (x[i] == 0) ++i; - if (i == x.length) return ZERO; + if (i == x.length) return zero; x = x.sublist(i); - List combine = List.from(ZERO); + List combine = List.from(zero); combine.addAll(x); if (x[0] & 0x80 != 0) return Uint8List.fromList(combine); return x; diff --git a/lib/src/varuint.dart b/lib/src/varuint.dart index 4ad58a0..25de476 100644 --- a/lib/src/varuint.dart +++ b/lib/src/varuint.dart @@ -1,9 +1,8 @@ import 'dart:typed_data'; -import 'transaction.dart'; import 'utils/check_types.dart'; Uint8List encode(int number, [Uint8List buffer,int offset]) { - if (!isUint(number, 53)); +// if (!isUint(number, 53)); buffer = buffer ?? new Uint8List(encodingLength(number)); offset = offset ?? 0; diff --git a/pubspec.lock b/pubspec.lock index ec53c86..7ec81bd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -63,7 +63,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.1.3" fixnum: dependency: "direct main" description: @@ -110,7 +110,7 @@ packages: source: hosted version: "0.12.5" meta: - dependency: transitive + dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index f7f9d8b..166453a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,8 @@ name: bitbox -description: A new Flutter package project. +description: "BITBOX SDK lite for Flutter: build native cross-platform mobile apps with Bitcoin Cash" version: 0.0.1 -author: -homepage: +author: Tomas Forgac +homepage: https://github.com/tomasforgacbch/bitbox-flutter environment: sdk: ">=2.1.0 <3.0.0" @@ -17,6 +17,7 @@ dependencies: hex: ^0.1.2 bs58check: ^1.0.1 fixnum: ^0.10.9 + meta: ^1.1.6 dev_dependencies: flutter_test: diff --git a/test/bitbox_test.dart b/test/bitbox_test.dart index 1c755f9..721c51d 100644 --- a/test/bitbox_test.dart +++ b/test/bitbox_test.dart @@ -16,7 +16,7 @@ void main() { // If these are false, the transactions will be only built and compared to the output generated by bitbox js // You can turn these on separately - const BROADCAST_TESTNET_TRANSACTION = false; + const BROADCAST_TESTNET_TRANSACTION = true; const BROADCAST_MAINNET_TRANSACTION = true; // Data generated by the original bitbox library diff --git a/test/readmemdtest.dart b/test/readmemdtest.dart deleted file mode 100644 index 227e36d..0000000 --- a/test/readmemdtest.dart +++ /dev/null @@ -1,113 +0,0 @@ -// This is to make sure the code in readme.MD works - -import 'package:bitbox/bitbox.dart' as Bitbox; - -void main() async { -// set this to false to use mainnet -final testnet = false; - -// After running the code for the first time, depositing an amount to the address displayed in the console, -// and waiting for confirmation, paste the generated mnemonic here, -// so the code continues below with address withdrawal -String mnemonic = ""; - -if (mnemonic == "") { - // generate 12-word (128bit) mnemonic - mnemonic = Bitbox.Mnemonic.generate(); - - print(mnemonic); -} - -// generate a seed from mnemonic -final seed = Bitbox.Mnemonic.toSeed(mnemonic); - -// create an instance of Bitbox.HDNode for mainnet -final masterNode = Bitbox.HDNode.fromSeed(seed, testnet); - -// This format is compatible with Bitcoin.com wallet. -// Other wallets use Change to m/44'/145'/0'/0 -final accountDerivationPath = "m/44'/0'/0'/0"; - -// create an account node using the provided derivation path -final accountNode = masterNode.derivePath(accountDerivationPath); - -// get account's extended private key -final accountXPriv = accountNode.toXPriv(); - -// create a Bitbox.HDNode instance of the first child in this account -final childNode = accountNode.derive(0); - -// get an address of the child -final address = childNode.toCashAddress(); - -// if you are using testnet, set the appropriate rest api url before making -// any API calls (like getting address or transaction details or broadcasting a transaction -if (testnet) { - Bitbox.Bitbox.setRestUrl(restUrl: Bitbox.Bitbox.trestUrl); -} - -// get address details -final addressDetails = await Bitbox.Address.details(address); - -print(addressDetails); - -// If there is a confirmed balance, attempt to withdraw it to the address defined below -if (addressDetails["balance"] > 0) { - final builder = Bitbox.Bitbox.transactionBuilder(testnet: testnet); - - // retrieve address' utxos from the rest api - final utxos = await Bitbox.Address.utxo(address) as List; - - // placeholder for input signatures - final signatures = []; - - // placeholder for total input balance - int totalBalance = 0; - - // iterate through the list of address utxos and use them as inputs for the withdrawal transaction - utxos.forEach((Bitbox.Utxo utxo) { - // add the utxo as an input for the transaction - builder.addInput(utxo.txid, utxo.vout); - - // add a signature to the list to be used later - signatures.add({ - "vin": signatures.length, - "key_pair": childNode.keyPair, - "original_amount": utxo.satoshis - }); - - totalBalance += utxo.satoshis; - }); - - // set an address to send the remaining balance to - final outputAddress = ""; - - // if there is an unspent balance, create a spending transaction - if (totalBalance > 0 && outputAddress != "") { - // calculate the fee based on number of inputs and one expected output - final fee = Bitbox.BitcoinCash.getByteCount(signatures.length, 1); - - // calculate how much balance will be left over to spend after the fee - final sendAmount = totalBalance - fee; - - // add the output based on the address provided in the testing data - builder.addOutput(outputAddress, sendAmount); - - // sign all inputs - signatures.forEach((signature) { - builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); - }); - - // build the transaction - final tx = builder.build(); - - // broadcast the transaction - final txid = await Bitbox.RawTransactions.sendRawTransaction(tx.toHex()); - - // Yatta! - print("Transaction broadcasted: $txid"); - } else if (totalBalance > 0) { - print("Enter an output address to test withdrawal transaction"); - } -} -} From d54525dd58050db9800dd44f0937b6fb9eabd59e Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 28 Nov 2019 20:02:31 +0530 Subject: [PATCH 003/106] implemented bip21 --- lib/src/address.dart | 138 ++++++++++++++++++++++++++------------- lib/src/bitcoincash.dart | 12 ++++ lib/src/utils/bip21.dart | 61 +++++++++++++++++ pubspec.lock | 20 +++--- 4 files changed, 177 insertions(+), 54 deletions(-) create mode 100644 lib/src/utils/bip21.dart diff --git a/lib/src/address.dart b/lib/src/address.dart index 45c5104..c2f78b2 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -16,17 +16,45 @@ class Address { static const _CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; static const _CHARSET_INVERSE_INDEX = { - 'q': 0, 'p': 1, 'z': 2, 'r': 3, 'y': 4, '9': 5, 'x': 6, '8': 7, - 'g': 8, 'f': 9, '2': 10, 't': 11, 'v': 12, 'd': 13, 'w': 14, '0': 15, - 's': 16, '3': 17, 'j': 18, 'n': 19, '5': 20, '4': 21, 'k': 22, 'h': 23, - 'c': 24, 'e': 25, '6': 26, 'm': 27, 'u': 28, 'a': 29, '7': 30, 'l': 31, + 'q': 0, + 'p': 1, + 'z': 2, + 'r': 3, + 'y': 4, + '9': 5, + 'x': 6, + '8': 7, + 'g': 8, + 'f': 9, + '2': 10, + 't': 11, + 'v': 12, + 'd': 13, + 'w': 14, + '0': 15, + 's': 16, + '3': 17, + 'j': 18, + 'n': 19, + '5': 20, + '4': 21, + 'k': 22, + 'h': 23, + 'c': 24, + 'e': 25, + '6': 26, + 'm': 27, + 'u': 28, + 'a': 29, + '7': 30, + 'l': 31, }; /// Returns information about the given Bitcoin Cash address. /// /// See https://developer.bitcoin.com/bitbox/docs/util for details about returned format static Future> validateAddress(String address) async => - await RestApi.sendGetRequest("util/validateAddress", address); + await RestApi.sendGetRequest("util/validateAddress", address); /// Returns details of the provided address or addresses /// @@ -40,7 +68,7 @@ class Address { /// See https://developer.bitcoin.com/bitbox/docs/address#details for details about returned format. However /// note, that processing from array to map is done on the library side static Future details(addresses, [returnAsMap = false]) async => - await _sendRequest("details", addresses, returnAsMap); + await _sendRequest("details", addresses, returnAsMap); /// Returns list of unconfirmed transactions /// @@ -53,7 +81,8 @@ class Address { /// /// See https://developer.bitcoin.com/bitbox/docs/address#unconfirmed for details about the returned format. However /// note, that processing from array to map is done on the library side - static Future getUnconfirmed(addresses, [returnAsMap = false]) async { + static Future getUnconfirmed(addresses, + [returnAsMap = false]) async { final result = await _sendRequest("unconfirmed", addresses); if (result is Map) { @@ -64,9 +93,11 @@ class Address { result.forEach((addressUtxoMap) { if (returnAsMap) { - returnMap[addressUtxoMap["cashAddr"]] = Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); + returnMap[addressUtxoMap["cashAddr"]] = + Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); } else { - addressUtxoMap["utxos"] = Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); + addressUtxoMap["utxos"] = + Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); returnList.add(addressUtxoMap); } }); @@ -95,9 +126,11 @@ class Address { result.forEach((addressUtxoMap) { if (returnAsMap) { - returnMap[addressUtxoMap["cashAddress"]] = Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); + returnMap[addressUtxoMap["cashAddress"]] = + Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); } else { - addressUtxoMap["utxos"] = Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); + addressUtxoMap["utxos"] = + Utxo.convertMapListToUtxos(addressUtxoMap["utxos"]); returnList.add(addressUtxoMap); } }); @@ -109,15 +142,16 @@ class Address { } /// Converts legacy address to cash address - static String toCashAddress(String legacyAddress, [bool includePrefix = true]) { + static String toCashAddress(String legacyAddress, + [bool includePrefix = true]) { final decoded = Address._decodeLegacyAddress(legacyAddress); String prefix = ""; if (includePrefix) { switch (decoded["version"]) { - case Network.bchPublic : + case Network.bchPublic: prefix = "bitcoincash"; break; - case Network.bchTestnetPublic : + case Network.bchTestnetPublic: prefix = "bchtest"; break; default: @@ -172,7 +206,8 @@ class Address { static _encode(String prefix, String type, Uint8List hash) { final prefixData = _prefixToUint5List(prefix) + Uint8List(1); final versionByte = _getHashSizeBits(hash); - final payloadData = _convertBits(Uint8List.fromList([versionByte] + hash), 8, 5); + final payloadData = + _convertBits(Uint8List.fromList([versionByte] + hash), 8, 5); final checksumData = prefixData + payloadData + Uint8List(8); final payload = payloadData + _checksumToUint5Array(_polymod(checksumData)); return "$prefix:" + _base32Encode(payload); @@ -180,14 +215,16 @@ class Address { /// Helper method for sending generic requests to Bitbox API. Accepts [String] or [List] of Strings and optionally /// converts the List returned by Bitbox into [Map], which uses cashAddress as a key - static Future _sendRequest(String path, dynamic addresses, [bool returnAsMap = false]) async { + static Future _sendRequest(String path, dynamic addresses, + [bool returnAsMap = false]) async { assert(addresses is String || addresses is List); if (addresses is String) { return await RestApi.sendGetRequest("address/$path", addresses) as Map; } else if (addresses is List) { - return await RestApi.sendPostRequest("address/$path", "addresses", addresses, - returnKey: returnAsMap ? "cashAddress" : null); + return await RestApi.sendPostRequest( + "address/$path", "addresses", addresses, + returnKey: returnAsMap ? "cashAddress" : null); } else { throw TypeError(); } @@ -274,7 +311,7 @@ class Address { return { "version": buffer.first, "hash": buffer.sublist(1), - "format" : formatLegacy, + "format": formatLegacy, }; } @@ -316,7 +353,8 @@ class Address { continue; } - final payloadData = _fromUint5Array(payload.sublist(0, payload.length - 8)); + final payloadData = + _fromUint5Array(payload.sublist(0, payload.length - 8)); final hash = payloadData.sublist(1); if (_getHashSize(payloadData[0]) != hash.length * 8) { @@ -327,9 +365,9 @@ class Address { // If the loop got all the way here, it means validations went through and the address was decoded. // Return the decoded data return { - "prefix" : prefixes[i], - "hash" : hash, - "format" : formatCashAddr + "prefix": prefixes[i], + "hash": hash, + "format": formatCashAddr }; } @@ -357,8 +395,11 @@ class Address { /// Converts a list of integers made up of 'from' bits into an array of integers made up of 'to' bits. /// The output array is zero-padded if necessary, unless strict mode is true. - static Uint8List _convertBits(List data, int from, int to, [bool strictMode = false]) { - final length = strictMode ? (data.length * from / to).floor() : (data.length * from / to).ceil(); + static Uint8List _convertBits(List data, int from, int to, + [bool strictMode = false]) { + final length = strictMode + ? (data.length * from / to).floor() + : (data.length * from / to).ceil(); int mask = (1 << to) - 1; var result = Uint8List(length); int index = 0; @@ -382,7 +423,8 @@ class Address { } } else { if (bits < from && ((accumulator << (to - bits)) & mask).toInt() != 0) { - throw FormatException("Input cannot be converted to $to bits without padding, but strict mode was used."); + throw FormatException( + "Input cannot be converted to $to bits without padding, but strict mode was used."); } } return result; @@ -391,7 +433,13 @@ class Address { /// Computes a checksum from the given input data as specified for the CashAddr format: // https://github.com/Bitcoin-UAHF/spec/blob/master/cashaddr.md. static int _polymod(List data) { - const GENERATOR = [0x98f2bc8e61, 0x79b76d99e2, 0xf33e5fb3c4, 0xae2eabe2a8, 0x1e4f43e470]; + const GENERATOR = [ + 0x98f2bc8e61, + 0x79b76d99e2, + 0xf33e5fb3c4, + 0xae2eabe2a8, + 0x1e4f43e470 + ]; int checksum = 1; @@ -414,7 +462,8 @@ class Address { final data = Uint8List(string.length); for (int i = 0; i < string.length; i++) { final value = string[i]; - if (!_CHARSET_INVERSE_INDEX.containsKey(value)) throw FormatException("Invalid character '$value'"); + if (!_CHARSET_INVERSE_INDEX.containsKey(value)) + throw FormatException("Invalid character '$value'"); data[i] = _CHARSET_INVERSE_INDEX[string[i]]; } @@ -452,16 +501,17 @@ class Utxo { final int height; final int confirmations; - Utxo(this.txid, this.vout, this.amount, this.satoshis, this.height, this.confirmations); + Utxo(this.txid, this.vout, this.amount, this.satoshis, this.height, + this.confirmations); /// Create [Utxo] instance from utxo [Map] - Utxo.fromMap(Map utxoMap) : - this.txid = utxoMap['txid'], - this.vout = utxoMap['vout'], - this.amount = utxoMap['amount'], - this.satoshis = utxoMap['satoshis'], - this.height = utxoMap.containsKey('height') ? utxoMap['height'] : null, - this.confirmations = utxoMap['confirmations']; + Utxo.fromMap(Map utxoMap) + : this.txid = utxoMap['txid'], + this.vout = utxoMap['vout'], + this.amount = utxoMap['amount'], + this.satoshis = utxoMap['satoshis'], + this.height = utxoMap.containsKey('height') ? utxoMap['height'] : null, + this.confirmations = utxoMap['confirmations']; /// Converts List of utxo maps into a list of [Utxo] objects static List convertMapListToUtxos(List utxoMapList) { @@ -475,11 +525,11 @@ class Utxo { @override String toString() => jsonEncode({ - "txid": txid, - "vout": vout, - "amount": amount, - "satoshis": satoshis, - "height": height, - "confirmations" : confirmations - }); -} \ No newline at end of file + "txid": txid, + "vout": vout, + "amount": amount, + "satoshis": satoshis, + "height": height, + "confirmations": confirmations + }); +} diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index 10dc2e2..eab2b44 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -1,3 +1,5 @@ +import 'utils/bip21.dart'; + /// Bitcoin Cash specific utilities class BitcoinCash { /// Converts Bitcoin Cash units to satoshi units @@ -14,4 +16,14 @@ class BitcoinCash { static int getByteCount(int inputs, int outputs) { return ((inputs * 148 * 4 + 34 * 4 * outputs + 10 * 4) / 4).ceil(); } + + // Converts a [String] bch address and its [Map] options into [String] bip-21 uri + static String encodeBIP21(String address, Map options) { + return Bip21.encode(address, options); + } + + // Converts [String] bip-21 uri into a [Map] of bch address and its options + static Map decodeBIP21(String uri) { + return Bip21.decode(uri); + } } diff --git a/lib/src/utils/bip21.dart b/lib/src/utils/bip21.dart new file mode 100644 index 0000000..b335281 --- /dev/null +++ b/lib/src/utils/bip21.dart @@ -0,0 +1,61 @@ +class Bip21 { + static Map decode(String uri) { + if (uri.indexOf('bitcoincash') != 0 || uri['bitcoincash'.length] != ":") + throw ("Invalid BIP21 URI"); + + int split = uri.indexOf("?"); + Map uriOptions = Uri.parse(uri).queryParameters; + + Map options = Map.from({ + "message": uriOptions["message"], + "label": uriOptions["label"], + }); + + String address = uri.substring(0, split == -1 ? null : split); + + if (uriOptions["amount"] != null) { + if (uriOptions["amount"].indexOf(",") != -1) + throw ("Invalid amount: commas are invalid"); + + double amount = double.tryParse(uriOptions["amount"]); + if (amount == null || amount.isNaN) + throw ("Invalid amount: not a number"); + if (!amount.isFinite) throw ("Invalid amount: not finite"); + if (amount < 0) throw ("Invalid amount: not positive"); + options["amount"] = amount; + } + + return { + 'address': address, + 'options': options, + }; + } + + static String encode(String address, Map options) { + var isCashAddress = address.startsWith('bitcoincash:'); + if (!isCashAddress) { + address = 'bitcoincash:$address'; + } + + String query = ""; + if (options != null && options.isNotEmpty) { + if (options['amount'] != null) { + if (!options['amount'].isFinite) throw ("Invalid amount: not finite"); + if (options['amount'] < 0) throw ("Invalid amount: not positive"); + } + + Map uriOptions = options; + uriOptions.removeWhere((key, value) => value == null); + uriOptions.forEach((key, value) { + uriOptions[key] = value.toString(); + }); + + if (uriOptions.isEmpty) uriOptions = null; + query = Uri(queryParameters: uriOptions).toString(); + // Dart isn't following RFC-3986... + query = query.replaceAll(RegExp(r"\+"), "%20"); + } + + return "$address$query"; + } +} diff --git a/pubspec.lock b/pubspec.lock index 7ec81bd..c795d8b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,5 +1,5 @@ # Generated by pub -# See https://www.dartlang.org/tools/pub/glossary#lockfile +# See https://dart.dev/tools/pub/glossary#lockfile packages: async: dependency: transitive @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.3.0" bip32: dependency: "direct main" description: @@ -28,7 +28,7 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" bs58check: dependency: "direct main" description: @@ -115,21 +115,21 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.1.7" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.6.4" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.5.0" + version: "1.8.0+1" pointycastle: dependency: "direct main" description: @@ -143,7 +143,7 @@ packages: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.5" sky_engine: dependency: transitive description: flutter @@ -176,7 +176,7 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "1.0.5" term_glyph: dependency: transitive description: @@ -190,7 +190,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.4" + version: "0.2.5" typed_data: dependency: transitive description: @@ -206,4 +206,4 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.2.0 <3.0.0" + dart: ">=2.2.2 <3.0.0" From 79a7a428452552e2f6aaeb9e6a9d711d793991dd Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 6 Jan 2020 15:51:23 +0530 Subject: [PATCH 004/106] custom post keys --- lib/src/utils/rest_api.dart | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/src/utils/rest_api.dart b/lib/src/utils/rest_api.dart index 741fd5c..04c5605 100644 --- a/lib/src/utils/rest_api.dart +++ b/lib/src/utils/rest_api.dart @@ -8,7 +8,8 @@ class RestApi { _restUrl = restUrl; } - static Future sendGetRequest(String path, [String parameter = ""]) async { + static Future sendGetRequest(String path, + [String parameter = ""]) async { final response = await http.get("$_restUrl$path/$parameter"); if (response.statusCode == 200) { @@ -18,11 +19,16 @@ class RestApi { } } - static Future sendPostRequest(String path, String postKey, List data, {String returnKey}) async { + static Future sendPostRequest( + String path, String postKey, List data, + {String returnKey}) async { + if (postKey == null || postKey.isEmpty) { + postKey = "addresses"; + } final response = await http.post( "$_restUrl$path", headers: {"content-type": "application/json"}, - body: jsonEncode({"addresses" : data}), + body: jsonEncode({postKey: data}), ); if (response.statusCode != 200) { @@ -35,14 +41,16 @@ class RestApi { final responseData = jsonDecode(response.body); if (!responseData is List || !responseData.first is Map) { - throw FormatException("return data (below) is not List of Maps: \n${response.body}"); + throw FormatException( + "return data (below) is not List of Maps: \n${response.body}"); } Map returnMap = {}; responseData.forEach((Map item) { if (!item.containsKey(returnKey)) { - throw FormatException("return data (below) doesn't contain key $returnKey: $item"); + throw FormatException( + "return data (below) doesn't contain key $returnKey: $item"); } returnMap[item[returnKey]] = item; }); @@ -50,4 +58,4 @@ class RestApi { return returnMap; } } -} \ No newline at end of file +} From b09d8917e6f3745b1783f7d5d79ee2468b7937bc Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 6 Jan 2020 16:28:24 +0530 Subject: [PATCH 005/106] get block details by block height or block hash --- lib/src/block.dart | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 lib/src/block.dart diff --git a/lib/src/block.dart b/lib/src/block.dart new file mode 100644 index 0000000..c200fc1 --- /dev/null +++ b/lib/src/block.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; +import 'utils/rest_api.dart'; +import 'utils/rest_api.dart'; + +// Return details about a Block + +class Block { + // Lookup the block with a block height. + static Future detailsByHeight(number) async { + if (number is String) { + // Single Block + return await RestApi.sendGetRequest( + "block/detailsByHeight", number.toString()); + } else if (number is List) { + // Array of Blocks + return await RestApi.sendPostRequest( + "block/detailsByHeight", "heights", number); + } else + return throw ("Function parameter must be String for single block and List for multiple blocks"); + } + +// Lookup the block with a block hash. + static Future detailsByHash(hash) async { + if (hash is String) { + // Single Block + return await RestApi.sendGetRequest("block/detailsByHash", hash); + } else if (hash is List) { + // Array of Blocks + return await RestApi.sendPostRequest( + "block/detailsByHash", "hashes", hash); + } else + return throw ("Function parameter must be String for single block and List for multiple blocks"); + } +} From 9617c9e29780b9a4950e266e5bc05777814b16cf Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 6 Jan 2020 16:58:20 +0530 Subject: [PATCH 006/106] register and lookup cashaccounts --- lib/src/cashaccounts.dart | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/src/cashaccounts.dart diff --git a/lib/src/cashaccounts.dart b/lib/src/cashaccounts.dart new file mode 100644 index 0000000..091c720 --- /dev/null +++ b/lib/src/cashaccounts.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; +import 'utils/rest_api.dart'; +import 'package:http/http.dart' as http; + +class CashAccounts { + static Future lookup(String account, int number, {int collision}) async { + String col = ""; + if (collision != null) { + col = collision.toString(); + } + final response = await http.get( + "https://rest.bitcoin.com/v2/cashAccounts/lookup/$account/$number/$col"); + return json.decode(response.body); + } + + static Future check(String account, int number) async { + final response = await http + .get("https://rest.bitcoin.com/v2/cashAccounts/check/$account/$number"); + return json.decode(response.body); + } + + static Future reverseLookup(String cashAddress) async { + final response = await http.get( + "https://rest.bitcoin.com/v2/cashAccounts/reverseLookup/$cashAddress"); + return json.decode(response.body); + } + + static Future register(String name, String address) async { + Map register = { + 'name': name, + 'payments': [address] + }; + final response = await http.post('https://api.cashaccount.info/register', + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(register)); + Map data = jsonDecode(response.body); + return data; + } +} From c62576a622d46a33b0c2041134f1dea58192cf6b Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 6 Jan 2020 17:50:12 +0530 Subject: [PATCH 007/106] get or decode raw transactions and scripts --- lib/src/rawtransactions.dart | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/lib/src/rawtransactions.dart b/lib/src/rawtransactions.dart index eee3e78..c914ae0 100644 --- a/lib/src/rawtransactions.dart +++ b/lib/src/rawtransactions.dart @@ -1,4 +1,6 @@ +import 'dart:convert'; import 'utils/rest_api.dart'; +import 'package:http/http.dart' as http; /// Utilities for working raw transactions class RawTransactions { @@ -6,4 +8,48 @@ class RawTransactions { /// Returns the resulting txid static Future sendRawTransaction(String rawTx) async => await RestApi.sendGetRequest("rawtransactions/sendRawTransaction", rawTx); + + /// Send multiple raw transactions to the network + /// Returns the resulting array of txids + static Future sendRawTransactions(List rawTxs) async => + await RestApi.sendPostRequest( + "rawtransactions/sendRawTransaction", "hexes", rawTxs); + + /// Returns a JSON object representing the serialized, hex-encoded transaction + static Future decodeRawTransaction(String hex) async => + await RestApi.sendGetRequest("rawtransactions/decodeRawTransaction", hex); + + /// Returns bulk hex encoded transaction + static Future decodeRawTransactions(List hexes) async => + await RestApi.sendPostRequest( + "rawtransactions/decodeRawTransaction", "hexes", hexes); + + /// Decodes a hex-encoded script + static Future decodeScript(String script) async => + await RestApi.sendGetRequest("rawtransactions/decodeScript", script); + + /// Decodes multiple hex-encoded scripts + static Future decodeScripts(List scripts) async => + await RestApi.sendPostRequest( + "rawtransactions/decodeScript", "hexes", scripts); + + /// Returns the raw transaction data + static Future getRawtransaction(String script, + {bool verbose = true, bool testnet = false}) async { + var _restURL = testnet ? 'trest' : 'rest'; + final response = await http.get( + "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction/$script?verbose=$verbose"); + return jsonDecode(response.body); + } + + /// Returns raw transaction data for multiple transactions + static Future getRawtransactions(List scripts, + {bool verbose = true, bool testnet = false}) async { + var _restURL = testnet ? 'trest' : 'rest'; + final response = await http.post( + "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction", + headers: {"content-type": "application/json"}, + body: jsonEncode({'txids': scripts, "verbose": verbose})); + return jsonDecode(response.body); + } } From 5f60cca70fa5e74873c8b3c590b1938906e1d34b Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 6 Jan 2020 17:50:47 +0530 Subject: [PATCH 008/106] export block and cashaccount files --- lib/bitbox.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/bitbox.dart b/lib/bitbox.dart index 226fc36..add969a 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -4,6 +4,8 @@ export 'src/account.dart'; export 'src/address.dart'; export 'src/bitbox.dart'; export 'src/bitcoincash.dart'; +export 'src/block.dart'; +export 'src/cashaccounts.dart'; export 'src/ecpair.dart'; export 'src/hdnode.dart'; export 'src/mnemonic.dart'; From 916d243db7a47391659dab3e4755257bcea3a6b5 Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 10 Jan 2020 23:04:40 +0530 Subject: [PATCH 009/106] added support p2sh scripts --- lib/src/utils/p2sh.dart | 119 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 lib/src/utils/p2sh.dart diff --git a/lib/src/utils/p2sh.dart b/lib/src/utils/p2sh.dart new file mode 100644 index 0000000..f4be5bb --- /dev/null +++ b/lib/src/utils/p2sh.dart @@ -0,0 +1,119 @@ +import 'dart:typed_data'; +import '../crypto/crypto.dart'; +import '../utils/opcodes.dart'; +import 'package:meta/meta.dart'; +import 'package:bip32/src/utils/ecurve.dart' show isPoint; +import 'package:bs58check/bs58check.dart' as bs58check; +import 'script.dart' as bscript; + +import 'network.dart'; + +/// This is almost exact copy of https://github.com/anicdh/bitcoin_flutter/blob/master/lib/src/payments/p2pkh.dart +/// except using [Opcodes] static members instead of map +class P2SH { + P2SHData data; + Network network; + + P2SH({@required data, network}) { + this.network = network ?? Network.bitcoinCash(); + this.data = data; + _init(); + } + + _init() { + if (data.address != null) { + _getDataFromAddress(data.address); + _getDataFromHash(); + } else if (data.hash != null) { + _getDataFromHash(); + } else if (data.output != null) { + if (!isValidOutput(data.output)) + throw new ArgumentError('Output is invalid'); + data.hash = data.output.sublist(2, 22); + _getDataFromHash(); + } else if (data.scriptHash != null) { + data.hash = hash160(data.scriptHash); + _getDataFromHash(); + _getDataFromChunk(); + } else if (data.input != null) { + List _chunks = bscript.decompile(data.input); + _getDataFromChunk(_chunks); + if (_chunks.length != 2) throw new ArgumentError('Input is invalid'); + if (!bscript.isCanonicalScriptSignature(_chunks[0])) + throw new ArgumentError('Input has invalid signature'); + if (!isPoint(_chunks[1])) + throw new ArgumentError('Input has invalid pubkey'); + } else { + throw new ArgumentError("Not enough data"); + } + } + + void _getDataFromChunk([List _chunks]) { + if (data.scriptHash == null && _chunks != null) { + data.scriptHash = (_chunks[1] is int) + ? new Uint8List.fromList([_chunks[1]]) + : _chunks[1]; + data.hash = hash160(data.scriptHash); + _getDataFromHash(); + } + if (data.signature == null && _chunks != null) + data.signature = (_chunks[0] is int) + ? new Uint8List.fromList([_chunks[0]]) + : _chunks[0]; + if (data.input == null && + data.scriptHash != null && + data.signature != null) { + data.input = bscript.compile([data.signature, data.scriptHash]); + } + } + + void _getDataFromHash() { + if (data.address == null) { + final payload = new Uint8List(21); + payload.buffer.asByteData().setUint8(0, network.scriptHash); + payload.setRange(1, payload.length, data.hash); + data.address = bs58check.encode(payload); + } + if (data.output == null) { + data.output = + bscript.compile([Opcodes.OP_HASH160, data.hash, Opcodes.OP_EQUAL]); + } + } + + void _getDataFromAddress(String address) { + Uint8List payload = bs58check.decode(address); + final version = payload.buffer.asByteData().getUint8(0); + if (version != network.scriptHash) + throw new ArgumentError('Invalid version or Network mismatch'); + data.hash = payload.sublist(1); + if (data.hash.length != 20) throw new ArgumentError('Invalid address'); + } +} + +class P2SHData { + String address; + Uint8List hash; + Uint8List output; + Uint8List signature; + Uint8List scriptHash; + Uint8List input; + P2SHData( + {this.address, + this.hash, + this.output, + this.scriptHash, + this.input, + this.signature}); + + @override + String toString() { + return 'P2PKHData{address: $address, hash: $hash, output: $output, signature: $signature, pubkey: $scriptHash, input: $input}'; + } +} + +isValidOutput(Uint8List data) { + return data.length == 23 && + data[0] == Opcodes.OP_HASH160 && + data[1] == 0x14 && + data[2] == Opcodes.OP_EQUAL; +} From 9997a81a7d06dc519ff49e61174fa8483fddfea5 Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 10 Jan 2020 23:07:37 +0530 Subject: [PATCH 010/106] added p2sh support --- lib/src/utils/network.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/utils/network.dart b/lib/src/utils/network.dart index d1aa0a3..059dd91 100644 --- a/lib/src/utils/network.dart +++ b/lib/src/utils/network.dart @@ -9,20 +9,24 @@ class Network { static const bchPublic = 0x00; static const bchTestnetPublic = 0x6f; + static const bchPublicscriptHash = 0x05; + static const bchTestnetscriptHash = 0xc4; + final int bip32Private; final int bip32Public; final bool testnet; final int pubKeyHash; + final int scriptHash; final int private; final int public; Network(this.bip32Private, this.bip32Public, this.testnet, this.pubKeyHash, - this.private, this.public); + this.scriptHash, this.private, this.public); factory Network.bitcoinCash() => - Network(0x0488ade4, 0x0488b21e, false, 0x00, bchPrivate, bchPublic); - factory Network.bitcoinCashTest() => Network( - 0x04358394, 0x043587cf, true, 0x6f, bchTestnetPrivate, bchTestnetPublic); + Network(0x0488ade4, 0x0488b21e, false, 0x00, 0x05, bchPrivate, bchPublic); + factory Network.bitcoinCashTest() => Network(0x04358394, 0x043587cf, true, + 0x6f, 0xc4, bchTestnetPrivate, bchTestnetPublic); String get prefix => this.testnet ? "bchtest" : "bitcoincash"; } From c92f38df1a83d4479606f602fb86695ac74fcaf6 Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 10 Jan 2020 23:10:06 +0530 Subject: [PATCH 011/106] transaction output supports p2sh address --- lib/src/transactionbuilder.dart | 152 ++++++++++++++++++++------------ 1 file changed, 97 insertions(+), 55 deletions(-) diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index 679f215..25485c7 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -6,6 +6,7 @@ import 'crypto/crypto.dart'; import 'utils/network.dart'; import 'utils/opcodes.dart'; import 'utils/p2pkh.dart'; +import 'utils/p2sh.dart'; import 'utils/script.dart' as bscript; import 'ecpair.dart'; import 'transaction.dart'; @@ -28,14 +29,15 @@ class TransactionBuilder { final Map _prevTxSet = {}; /// Creates an empty transaction builder - TransactionBuilder({Network network, int maximumFeeRate}) : - this._network = network ?? Network.bitcoinCash(), - this._maximumFeeRate = maximumFeeRate ?? 2500, - this._inputs = [], - this._tx = new Transaction(); + TransactionBuilder({Network network, int maximumFeeRate}) + : this._network = network ?? Network.bitcoinCash(), + this._maximumFeeRate = maximumFeeRate ?? 2500, + this._inputs = [], + this._tx = new Transaction(); /// Creates a builder from pre-built transaction - factory TransactionBuilder.fromTransaction(Transaction transaction, [Network network]) { + factory TransactionBuilder.fromTransaction(Transaction transaction, + [Network network]) { final txb = new TransactionBuilder(network: network); // Copy transaction fields txb.setVersion(transaction.version); @@ -49,7 +51,7 @@ class TransactionBuilder { // Copy inputs transaction.inputs.forEach((txIn) { txb._addInputUnsafe(txIn.hash, txIn.index, - new Input(sequence: txIn.sequence, script: txIn.script)); + new Input(sequence: txIn.sequence, script: txIn.script)); }); return txb; @@ -89,8 +91,11 @@ class TransactionBuilder { /// Returns vin of the input /// /// Throws [ArgumentError] if the inputs of this transaction can't be modified or if [txHashOrInstance] is invalid - int addInput(dynamic txHashOrInstance, int vout, [int sequence, Uint8List prevOutScript]) { - assert(txHashOrInstance is String || txHashOrInstance is Uint8List || txHashOrInstance is Transaction); + int addInput(dynamic txHashOrInstance, int vout, + [int sequence, Uint8List prevOutScript]) { + assert(txHashOrInstance is String || + txHashOrInstance is Uint8List || + txHashOrInstance is Transaction); if (!_canModifyInputs()) { throw new ArgumentError('No, this would invalidate signatures'); @@ -110,18 +115,23 @@ class TransactionBuilder { throw ArgumentError('txHash invalid'); } - return _addInputUnsafe(hash, vout, new Input(sequence: sequence, prevOutScript: prevOutScript, value: value)); + return _addInputUnsafe( + hash, + vout, + new Input( + sequence: sequence, prevOutScript: prevOutScript, value: value)); } /// Adds transaction output, which can be provided as: /// * Address as [String] in either _legacy_ or _cashAddr_ format /// * scriptPubKey + /// * scriptPubHash /// /// Returns output id /// /// Throws [ArgumentError] if outputs can't be modified or the output format is invalid int addOutput(dynamic data, int value) { - assert (data is String || data is Uint8List); + assert(data is String || data is Uint8List); Uint8List scriptPubKey; if (data is String) { @@ -146,16 +156,18 @@ class TransactionBuilder { /// indicate, that the developer plans to add change address later based on a result of this calculation /// /// Throws [ArgumentError] if something goes wrong - int getByteCount([bool addChangeOutput = true]) => - BitcoinCash.getByteCount(this._inputs.length, this._tx.outputs.length + (addChangeOutput ? 1 : 0)); + int getByteCount([bool addChangeOutput = true]) => BitcoinCash.getByteCount( + this._inputs.length, this._tx.outputs.length + (addChangeOutput ? 1 : 0)); /// Add signature for the input [vin] using [keyPair] and with a specified [value] /// /// Throws [ArgumentError] if something goes wrong - sign(int vin, ECPair keyPair, int value, [int hashType = Transaction.SIGHASH_ALL]) { + sign(int vin, ECPair keyPair, int value, + [int hashType = Transaction.SIGHASH_ALL]) { hashType = hashType | Transaction.SIGHASH_BITCOINCASHBIP143; - if (keyPair.network != null && keyPair.network.toString().compareTo(_network.toString()) != 0) { + if (keyPair.network != null && + keyPair.network.toString().compareTo(_network.toString()) != 0) { throw ArgumentError('Inconsistent network'); } @@ -171,11 +183,12 @@ class TransactionBuilder { final ourPubKey = keyPair.publicKey; if (!_canSign(input)) { - // Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); + // Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); _prepareInput(input, ourPubKey, value); } - var signatureHash = this._tx.hashForCashSignature(vin, input.signScript, value, hashType); + var signatureHash = + this._tx.hashForCashSignature(vin, input.signScript, value, hashType); // enforce in order signing of public keys var signed = false; @@ -211,8 +224,10 @@ class TransactionBuilder { _build(bool allowIncomplete) { if (!allowIncomplete) { - if (_tx.inputs.length == 0) throw ArgumentError('Transaction has no inputs'); - if (_tx.outputs.length == 0) throw ArgumentError('Transaction has no outputs'); + if (_tx.inputs.length == 0) + throw ArgumentError('Transaction has no inputs'); + if (_tx.outputs.length == 0) + throw ArgumentError('Transaction has no outputs'); } final tx = Transaction.clone(_tx); @@ -226,12 +241,11 @@ class TransactionBuilder { for (var i = 0; i < _inputs.length; i++) { if (_inputs[i].pubkeys != null && - _inputs[i].signatures != null && - _inputs[i].pubkeys.length != 0 && - _inputs[i].signatures.length != 0) { + _inputs[i].signatures != null && + _inputs[i].pubkeys.length != 0 && + _inputs[i].signatures.length != 0) { final result = _buildInput(_inputs[i]); tx.setInputScript(i, result); - } else if (!allowIncomplete) { throw new ArgumentError('Transaction is not complete'); } @@ -285,25 +299,25 @@ class TransactionBuilder { // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs // .build() will fail, but .buildIncomplete() is OK return (this._tx.outputs.length == 0) && - _inputs.map((input) { - if (input.signatures == null || input.signatures.length == 0) - return false; - return input.signatures.map((signature) { - if (signature == null) return false; // no signature, no issue - final hashType = _signatureHashType(signature); - if (hashType & SIGHASH_NONE != 0) - return false; // SIGHASH_NONE doesn't care about outputs - return true; // SIGHASH_* does care + _inputs.map((input) { + if (input.signatures == null || input.signatures.length == 0) + return false; + return input.signatures.map((signature) { + if (signature == null) return false; // no signature, no issue + final hashType = _signatureHashType(signature); + if (hashType & SIGHASH_NONE != 0) + return false; // SIGHASH_NONE doesn't care about outputs + return true; // SIGHASH_* does care + }).contains(true); }).contains(true); - }).contains(true); } bool _canSign(Input input) { return input.pubkeys != null && - input.signScript != null && - input.signatures != null && - input.signatures.length == input.pubkeys.length && - input.pubkeys.length > 0; + input.signScript != null && + input.signatures != null && + input.signatures.length == input.pubkeys.length && + input.pubkeys.length > 0; } _addInputUnsafe(Uint8List hash, int vout, Input options) { @@ -351,7 +365,6 @@ class TransactionBuilder { Opcodes.OP_CHECKSIG ]); - final expanded = _expandOutput(prevOutScript, kpPubKey); input.pubkeys = expanded.pubkeys; @@ -364,10 +377,7 @@ class TransactionBuilder { // returns input script Uint8List _buildInput(Input input) { // this is quite rudimentary for P2PKH purposes - return bscript.compile([ - input.signatures.first, - input.pubkeys.first - ]); + return bscript.compile([input.signatures.first, input.pubkeys.first]); } Output _expandOutput(Uint8List script, Uint8List ourPubKey) { @@ -375,11 +385,12 @@ class TransactionBuilder { //TODO: implement other script types too throw ArgumentError("Unsupport script!"); } - + final scriptChunks = bscript.decompile(script); // does our hash160(pubKey) match the output scripts? - Uint8List pkh1 = scriptChunks[2];//new P2PKH(data: new P2PKHData(output: script)).data.hash; + Uint8List pkh1 = scriptChunks[ + 2]; //new P2PKH(data: new P2PKHData(output: script)).data.hash; Uint8List pkh2 = hash160(ourPubKey); // this check should work, but for some reason doesn't - it returns false even if both lists are the same @@ -394,8 +405,22 @@ class TransactionBuilder { final payload = bs58check.decode(address); if (payload.length < 21) throw ArgumentError(address + ' is too short'); if (payload.length > 21) throw ArgumentError(address + ' is too long'); - final p2pkh = P2PKH(data: P2PKHData(address: address), network: network); - return p2pkh.data.output; + final version = payload.buffer.asByteData().getUint8(0); + var hash = payload.sublist(1); + Uint8List output; + + if (hash.length != 20) throw new ArgumentError('Invalid address'); + + if (version == network.pubKeyHash) { + final p2pkh = P2PKH(data: P2PKHData(address: address), network: network); + output = p2pkh.data.output; + } else if (version == network.scriptHash) { + final p2sh = P2SH(data: P2SHData(address: address), network: network); + output = p2sh.data.output; + } else { + return throw ArgumentError(address + 'does not match a valid script'); + } + return output; } Uint8List _pubkeyToOutputScript(Uint8List pubkey, [Network nw]) { @@ -404,22 +429,38 @@ class TransactionBuilder { return p2pkh.data.output; } - Uint8List _toInputScript(Uint8List pubkey, Uint8List signature, [Network nw]) { + Uint8List _scriptHashToOutputScript(Uint8List scriptHash, [Network nw]) { + final network = nw ?? Network.bitcoinCash(); + final p2shh = + P2SH(data: P2SHData(scriptHash: scriptHash), network: network); + return p2shh.data.output; + } + + Uint8List _toInputScript(Uint8List pubkey, Uint8List signature, + [Network nw]) { final network = nw ?? Network.bitcoinCash(); final p2pkh = P2PKH( - data: P2PKHData(pubkey: pubkey, signature: signature), - network: network); + data: P2PKHData(pubkey: pubkey, signature: signature), + network: network); return p2pkh.data.input; } bool _isP2PKHOutput(script) { final buffer = bscript.compile(script); return buffer.length == 25 && - buffer[0] == Opcodes.OP_DUP && - buffer[1] == Opcodes.OP_HASH160 && - buffer[2] == 0x14 && - buffer[23] == Opcodes.OP_EQUALVERIFY && - buffer[24] == Opcodes.OP_CHECKSIG; + buffer[0] == Opcodes.OP_DUP && + buffer[1] == Opcodes.OP_HASH160 && + buffer[2] == 0x14 && + buffer[23] == Opcodes.OP_EQUALVERIFY && + buffer[24] == Opcodes.OP_CHECKSIG; + } + + bool _isP2SHOutput(script) { + final buffer = bscript.compile(script); + return buffer.length == 23 && + buffer[0] == Opcodes.OP_HASH160 && + buffer[1] == 0x14 && + buffer[2] == Opcodes.OP_EQUAL; } bool _isCoinbaseHash(Uint8List buffer) { @@ -428,4 +469,5 @@ class TransactionBuilder { if (buffer[i] != 0) return false; } return true; - }} \ No newline at end of file + } +} From 1700cb063ccfe7f7d100f8ebed218e39e18e9ed9 Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 11 Jan 2020 00:20:39 +0530 Subject: [PATCH 012/106] p2sh support for toLegacyAddress() & toCashAddress() --- lib/src/address.dart | 116 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 95 insertions(+), 21 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index c2f78b2..9e9615f 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -145,21 +145,13 @@ class Address { static String toCashAddress(String legacyAddress, [bool includePrefix = true]) { final decoded = Address._decodeLegacyAddress(legacyAddress); - String prefix = ""; - if (includePrefix) { - switch (decoded["version"]) { - case Network.bchPublic: - prefix = "bitcoincash"; - break; - case Network.bchTestnetPublic: - prefix = "bchtest"; - break; - default: - throw FormatException("Unsupported address format: $legacyAddress"); - } + + if (!includePrefix) { + decoded['prefix'] = ""; } - final cashAddress = Address._encode(prefix, "P2PKH", decoded["hash"]); + final cashAddress = + Address._encode(decoded['prefix'], decoded['type'], decoded["hash"]); return cashAddress; } @@ -167,8 +159,26 @@ class Address { static String toLegacyAddress(String cashAddress) { final decoded = _decodeCashAddress(cashAddress); final testnet = decoded['prefix'] == "bchtest"; - - final version = !testnet ? Network.bchPublic : Network.bchTestnetPublic; + var version; + if (testnet) { + switch (decoded['type']) { + case "P2PKH": + version = Network.bchTestnetPublic; + break; + case "P2SH": + version = Network.bchTestnetscriptHash; + break; + } + } else { + switch (decoded['type']) { + case "P2PKH": + version = Network.bchPublic; + break; + case "P2SH": + version = Network.bchPublicscriptHash; + break; + } + } return toBase58Check(decoded["hash"], version); } @@ -201,11 +211,11 @@ class Address { /// Encodes a hash from a given type into a Bitcoin Cash address with the given prefix. /// [prefix] - Network prefix. E.g.: 'bitcoincash'. - /// [type] is currently unused - the library works only with _P2PKH_ + /// [type] Type of address to generate. Either 'P2PKH' or 'P2SH'. /// [hash] is the address hash, which can be decode either using [_decodeCashAddress()] or [_decodeLegacyAddress()] static _encode(String prefix, String type, Uint8List hash) { final prefixData = _prefixToUint5List(prefix) + Uint8List(1); - final versionByte = _getHashSizeBits(hash); + final versionByte = _getTypeBits(type) + _getHashSizeBits(hash); final payloadData = _convertBits(Uint8List.fromList([versionByte] + hash), 8, 5); final checksumData = prefixData + payloadData + Uint8List(8); @@ -287,6 +297,29 @@ class Address { return -1; } + static String _getType(versionByte) { + switch (versionByte & 120) { + case 0: + return 'P2PKH'; + case 8: + return 'P2SH'; + default: + throw FormatException( + 'Invalid address type in version byte: ' + versionByte + '.'); + } + } + + static int _getTypeBits(type) { + switch (type) { + case 'P2PKH': + return 0; + case 'P2SH': + return 8; + default: + throw new FormatException('Invalid type: ' + type + '.'); + } + } + /// Decodes the given address into: /// * (for cashAddr): constituting prefix (e.g. _bitcoincash_) /// * (for legacy): version @@ -308,11 +341,48 @@ class Address { static Map _decodeLegacyAddress(String address) { Uint8List buffer = bs58check.decode(address); - return { - "version": buffer.first, - "hash": buffer.sublist(1), - "format": formatLegacy, + var decoded = { + 'prefix': "", + 'type': "", + 'hash': buffer.sublist(1), + 'format': "" }; + + switch (buffer.first) { + case Network.bchPublic: + decoded = { + 'prefix': "bitcoincash", + 'type': "P2PKH", + 'hash': buffer.sublist(1), + 'format': "legacy" + }; + break; + case Network.bchPublicscriptHash: + decoded = { + 'prefix': "bitcoincash", + 'type': "P2SH", + 'hash': buffer.sublist(1), + 'format': "legacy" + }; + break; + case Network.bchTestnetPublic: + decoded = { + 'prefix': "bchtest", + 'type': "P2PKH", + 'hash': buffer.sublist(1), + 'format': "legacy" + }; + break; + case Network.bchTestnetscriptHash: + decoded = { + 'prefix': "bchtest", + 'type': "P2SH", + 'hash': buffer.sublist(1), + 'format': "legacy" + }; + break; + } + return decoded; } /// Decodes the given address into its constituting prefix, type and hash @@ -355,6 +425,7 @@ class Address { final payloadData = _fromUint5Array(payload.sublist(0, payload.length - 8)); + var versionByte = payloadData[0]; final hash = payloadData.sublist(1); if (_getHashSize(payloadData[0]) != hash.length * 8) { @@ -362,10 +433,13 @@ class Address { continue; } + var type = _getType(versionByte); + // If the loop got all the way here, it means validations went through and the address was decoded. // Return the decoded data return { "prefix": prefixes[i], + "type": type, "hash": hash, "format": formatCashAddr }; From eb81c183d593730c7e9b86a2227a4b576453e4cd Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 11 Jan 2020 08:12:42 +0530 Subject: [PATCH 013/106] typo --- .gitignore | 1 + lib/src/transactionbuilder.dart | 2 +- pubspec.lock | 45 +++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index dc1e7b5..c8eed87 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ .vscode/ # Flutter/Dart/Pub related +.pubspec.lock .dart_tool/ .flutter-plugins .packages diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index 25485c7..69b3bbf 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -125,7 +125,7 @@ class TransactionBuilder { /// Adds transaction output, which can be provided as: /// * Address as [String] in either _legacy_ or _cashAddr_ format /// * scriptPubKey - /// * scriptPubHash + /// * scriptHash /// /// Returns output id /// diff --git a/pubspec.lock b/pubspec.lock index c795d8b..54f812e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,13 +1,27 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + archive: + dependency: transitive + description: + name: archive + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.2" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.3.0" + version: "2.4.0" bip32: dependency: "direct main" description: @@ -102,20 +116,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.3" + image: + dependency: transitive + description: + name: image + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.4" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.5" + version: "0.12.6" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.7" + version: "1.1.8" path: dependency: transitive description: @@ -130,6 +151,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" + petitparser: + dependency: transitive + description: + name: petitparser + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.0" pointycastle: dependency: "direct main" description: @@ -190,7 +218,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.5" + version: "0.2.11" typed_data: dependency: transitive description: @@ -205,5 +233,12 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + xml: + dependency: transitive + description: + name: xml + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" sdks: - dart: ">=2.2.2 <3.0.0" + dart: ">=2.4.0 <3.0.0" From e381552b006eb02a687428130683bb8f11916cc3 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Sun, 12 Jan 2020 15:12:54 +0530 Subject: [PATCH 014/106] converted Crypto to a public class --- lib/src/crypto/crypto.dart | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/src/crypto/crypto.dart b/lib/src/crypto/crypto.dart index 354b947..fa9f55f 100644 --- a/lib/src/crypto/crypto.dart +++ b/lib/src/crypto/crypto.dart @@ -5,17 +5,19 @@ import "package:pointycastle/macs/hmac.dart"; import "package:pointycastle/digests/ripemd160.dart"; import "package:pointycastle/digests/sha256.dart"; -Uint8List hash160(Uint8List buffer) { - Uint8List _tmp = new SHA256Digest().process(buffer); - return new RIPEMD160Digest().process(_tmp); -} +class Crypto { + static Uint8List hash160(Uint8List buffer) { + Uint8List _tmp = new SHA256Digest().process(buffer); + return new RIPEMD160Digest().process(_tmp); + } -Uint8List hmacSHA512(Uint8List key, Uint8List data) { - final _tmp = new HMac(new SHA512Digest(), 128)..init(new KeyParameter(key)); - return _tmp.process(data); -} + static Uint8List hmacSHA512(Uint8List key, Uint8List data) { + final _tmp = new HMac(new SHA512Digest(), 128)..init(new KeyParameter(key)); + return _tmp.process(data); + } -Uint8List hash256(Uint8List buffer) { - Uint8List _tmp = new SHA256Digest().process(buffer); - return new SHA256Digest().process(_tmp); + static Uint8List hash256(Uint8List buffer) { + Uint8List _tmp = new SHA256Digest().process(buffer); + return new SHA256Digest().process(_tmp); + } } From 98da87278487b25b500ee384b7ac1aed64180c17 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Sun, 12 Jan 2020 15:13:37 +0530 Subject: [PATCH 015/106] export Crypto class --- lib/bitbox.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bitbox.dart b/lib/bitbox.dart index add969a..17034ff 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -6,6 +6,7 @@ export 'src/bitbox.dart'; export 'src/bitcoincash.dart'; export 'src/block.dart'; export 'src/cashaccounts.dart'; +export 'src/crypto/crypto.dart'; export 'src/ecpair.dart'; export 'src/hdnode.dart'; export 'src/mnemonic.dart'; From 18fea5a0e885d6c456ca27cb1190d5c432edc3a6 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Sun, 12 Jan 2020 15:17:14 +0530 Subject: [PATCH 016/106] removed pubspec.lock --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index c8eed87..dc1e7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,6 @@ .vscode/ # Flutter/Dart/Pub related -.pubspec.lock .dart_tool/ .flutter-plugins .packages From 77b9a0c20a8746cab26eab3720bb0ec33987e675 Mon Sep 17 00:00:00 2001 From: Radical Date: Sun, 12 Jan 2020 17:38:13 +0530 Subject: [PATCH 017/106] replaced crypto funcs with crypto class --- lib/src/ecpair.dart | 31 ++++--- lib/src/hdnode.dart | 31 ++++--- lib/src/transaction.dart | 149 +++++++++++++++++--------------- lib/src/transactionbuilder.dart | 4 +- lib/src/utils/p2pkh.dart | 24 ++--- lib/src/utils/p2sh.dart | 4 +- 6 files changed, 135 insertions(+), 108 deletions(-) diff --git a/lib/src/ecpair.dart b/lib/src/ecpair.dart index 8ec0ac8..3044cff 100644 --- a/lib/src/ecpair.dart +++ b/lib/src/ecpair.dart @@ -15,8 +15,8 @@ class ECPair { final bool compressed; /// Default constructor. If [network] is not provided, it will assume Bitcoin Cash mainnet - ECPair(this._d, this._q, {network, this.compressed = true}): - this.network = network ?? Network.bitcoinCash(); + ECPair(this._d, this._q, {network, this.compressed = true}) + : this.network = network ?? Network.bitcoinCash(); /// Creates a keypair from the private key provided in WIF format factory ECPair.fromWIF(String wifPrivateKey, {Network network}) { @@ -37,27 +37,27 @@ class ECPair { } } return ECPair.fromPrivateKey(decoded.privateKey, - compressed: decoded.compressed, network: nw); + compressed: decoded.compressed, network: nw); } /// Creates a keypair from [publicKey. The returned keypair will contain [null] private key - factory ECPair.fromPublicKey(Uint8List publicKey, {Network network, bool compressed}) { + factory ECPair.fromPublicKey(Uint8List publicKey, + {Network network, bool compressed}) { if (!ecc.isPoint(publicKey)) { throw ArgumentError("Point is not on the curve"); } - return ECPair(null, publicKey, - network: network, compressed: compressed); + return ECPair(null, publicKey, network: network, compressed: compressed); } /// Creates a keypair from [privateKey] - factory ECPair.fromPrivateKey(Uint8List privateKey, {Network network, bool compressed}) { + factory ECPair.fromPrivateKey(Uint8List privateKey, + {Network network, bool compressed}) { if (privateKey.length != 32) throw ArgumentError( - "Expected property privateKey of type Buffer(Length: 32)"); + "Expected property privateKey of type Buffer(Length: 32)"); if (!ecc.isPrivate(privateKey)) throw ArgumentError("Private key not in range [1, n)"); - return ECPair(privateKey, null, - network: network, compressed: compressed); + return ECPair(privateKey, null, network: network, compressed: compressed); } /// Creates a random keypair @@ -73,11 +73,12 @@ class ECPair { return ECPair.fromPrivateKey(d, network: network, compressed: compressed); } - Uint8List get publicKey => _q ?? ecc.pointFromScalar(_d, compressed); + Uint8List get publicKey => _q ?? ecc.pointFromScalar(_d, compressed); Uint8List get privateKey => _d; - String get address => Address.toBase58Check(hash160(publicKey), network.pubKeyHash); + String get address => + Address.toBase58Check(Crypto.hash160(publicKey), network.pubKeyHash); /// Returns the private key in WIF format String toWIF() { @@ -85,7 +86,9 @@ class ECPair { throw ArgumentError("Missing private key"); } return wif.encode(wif.WIF( - version: network.private, privateKey: privateKey, compressed: compressed)); + version: network.private, + privateKey: privateKey, + compressed: compressed)); } /// Signs the provided [hash] with the private key @@ -107,4 +110,4 @@ Uint8List _randomBytes(int size) { bytes[i] = rng.nextInt(_SIZE_BYTE); } return bytes; -} \ No newline at end of file +} diff --git a/lib/src/hdnode.dart b/lib/src/hdnode.dart index 6f8f945..b0292a3 100644 --- a/lib/src/hdnode.dart +++ b/lib/src/hdnode.dart @@ -35,7 +35,7 @@ class HDNode { int index = 0; int parentFingerprint = 0x00000000; - Uint8List get identifier => hash160(publicKeyList); + Uint8List get identifier => Crypto.hash160(publicKeyList); Uint8List get fingerprint => identifier.sublist(0, 4); Uint8List get publicKeyList => _keyPair.publicKey; @@ -57,7 +57,7 @@ class HDNode { final key = utf8.encode('Bitcoin seed'); - final I = hmacSHA512(key, seed); + final I = Crypto.hmacSHA512(key, seed); final keyPair = ECPair(I.sublist(0, 32), null, network: network); @@ -68,7 +68,8 @@ class HDNode { /// Creates [HDNode] from extended public key factory HDNode.fromXPub(String xPub) { - final network = xPub[0] == "x" ? Network.bitcoinCash() : Network.bitcoinCashTest(); + final network = + xPub[0] == "x" ? Network.bitcoinCash() : Network.bitcoinCashTest(); HDNode hdNode = HDNode._fromBase58(xPub, network); return hdNode; @@ -76,13 +77,13 @@ class HDNode { /// Creates [HDNode] from extended private key factory HDNode.fromXPriv(String xPriv) { - final network = xPriv[0] == "x" ? Network.bitcoinCash() : Network.bitcoinCashTest(); + final network = + xPriv[0] == "x" ? Network.bitcoinCash() : Network.bitcoinCashTest(); HDNode hdNode = HDNode._fromBase58(xPriv, network); return hdNode; } - factory HDNode._fromBase58(String string, Network network) { Uint8List buffer = bs58check.decode(string); if (buffer.length != 78) throw new ArgumentError("Invalid buffer length"); @@ -99,7 +100,8 @@ class HDNode { // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) var parentFingerprint = bytes.getUint32(5); if (depth == 0) { - if (parentFingerprint != 0x00000000) throw new ArgumentError("Invalid parent fingerprint"); + if (parentFingerprint != 0x00000000) + throw new ArgumentError("Invalid parent fingerprint"); } // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. @@ -113,7 +115,8 @@ class HDNode { ECPair keyPair; // 33 bytes: private key data (0x00 + k) if (version == network.bip32Private) { - if (bytes.getUint8(45) != 0x00) throw new ArgumentError("Invalid private key"); + if (bytes.getUint8(45) != 0x00) + throw new ArgumentError("Invalid private key"); Uint8List d = buffer.sublist(46, 78); keyPair = ECPair(d, null, network: network); // hdNode = HDNode.fromPrivateKey(d, chainCode, network); @@ -171,7 +174,7 @@ class HDNode { data.buffer.asByteData().setUint32(33, index); } - final I = hmacSHA512(_chainCode, data); + final I = Crypto.hmacSHA512(_chainCode, data); final IL = I.sublist(0, 32); final IR = I.sublist(32); // if (!ecc.isPrivate(IL)) { @@ -186,7 +189,7 @@ class HDNode { } else { final ki = ECurve.pointAddScalar(publicKeyList, IL, true); if (ki == null) return derive(index + 1); - + derivedKeyPair = ECPair(null, ki, network: this._keyPair.network); } final hd = HDNode(derivedKeyPair, IR); @@ -214,7 +217,9 @@ class HDNode { } String _toBase58() { - final version = (!_isNeutered()) ? this._keyPair.network.bip32Private : this._keyPair.network.bip32Public; + final version = (!_isNeutered()) + ? this._keyPair.network.bip32Private + : this._keyPair.network.bip32Public; Uint8List buffer = new Uint8List(78); ByteData bytes = buffer.buffer.asByteData(); bytes.setUint32(0, version); @@ -232,7 +237,9 @@ class HDNode { } HDNode _neutered() { - final neutered = HDNode(ECPair(null, this.publicKeyList, network: this._keyPair.network), _chainCode); + final neutered = HDNode( + ECPair(null, this.publicKeyList, network: this._keyPair.network), + _chainCode); neutered.depth = this.depth; neutered.index = this.index; neutered.parentFingerprint = this.parentFingerprint; @@ -242,4 +249,4 @@ class HDNode { bool _isNeutered() { return this._keyPair.privateKey == null; } -} \ No newline at end of file +} diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index afb59d5..2405b2b 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -21,10 +21,13 @@ class Transaction { static const ADVANCED_TRANSACTION_MARKER = 0x00; static const ADVANCED_TRANSACTION_FLAG = 0x01; static final emptyScript = Uint8List.fromList([]); - static final zero = HEX.decode('0000000000000000000000000000000000000000000000000000000000000000'); - static final one = HEX.decode('0000000000000000000000000000000000000000000000000000000000000001'); + static final zero = HEX.decode( + '0000000000000000000000000000000000000000000000000000000000000000'); + static final one = HEX.decode( + '0000000000000000000000000000000000000000000000000000000000000001'); static final valueUint64Max = HEX.decode('ffffffffffffffff'); - static final blankOutput = Output(script: emptyScript, valueBuffer: valueUint64Max); + static final blankOutput = + Output(script: emptyScript, valueBuffer: valueUint64Max); static const SATOSHI_MAX = 21 * 1e14; int version; @@ -33,11 +36,11 @@ class Transaction { List outputs; /// If [inputs] or [outputs] are not defined, empty lists are created for each - Transaction([version = 2, locktime = 0, ins, outs]) : - version = version, - locktime = locktime, - inputs = ins ?? [], - outputs = outs ?? []; + Transaction([version = 2, locktime = 0, ins, outs]) + : version = version, + locktime = locktime, + inputs = ins ?? [], + outputs = outs ?? []; /// Creates transaction from its hex representation factory Transaction.fromHex(String hex) { @@ -86,10 +89,10 @@ class Transaction { final vinLen = readVarInt(); for (var i = 0; i < vinLen; ++i) { tx.inputs.add(new Input( - hash: readSlice(32), - index: readUInt32(), - script: readVarSlice(), - sequence: readUInt32())); + hash: readSlice(32), + index: readUInt32(), + script: readVarSlice(), + sequence: readUInt32())); } final voutLen = readVarInt(); for (var i = 0; i < voutLen; ++i) { @@ -102,7 +105,7 @@ class Transaction { } bool isCoinbaseHash(buffer) { - assert (buffer.length == 32); + assert(buffer.length == 32); for (var i = 0; i < 32; ++i) { if (buffer[i] != 0) return false; @@ -117,10 +120,10 @@ class Transaction { /// Add input to the transaction. If [sequence] is not provided, defaults to [DEFAULT_SEQUENCE] int addInput(Uint8List hash, int index, [int sequence, Uint8List scriptSig]) { inputs.add(new Input( - hash: hash, - index: index, - sequence: sequence ?? DEFAULT_SEQUENCE, - script: scriptSig ?? emptyScript)); + hash: hash, + index: index, + sequence: sequence ?? DEFAULT_SEQUENCE, + script: scriptSig ?? emptyScript)); return inputs.length - 1; } @@ -138,7 +141,8 @@ class Transaction { hashForSignature(int inIndex, Uint8List prevOutScript, int hashType) { if (inIndex >= inputs.length) return one; // ignore OP_CODESEPARATOR - final ourScript = bscript.compile(bscript.decompile(prevOutScript).where((x) { + final ourScript = + bscript.compile(bscript.decompile(prevOutScript).where((x) { return x != Opcodes.OP_CODESEPARATOR; }).toList()); final txTmp = Transaction.clone(this); @@ -177,10 +181,12 @@ class Transaction { /// legacy signature /// /// [amount] must not be null for BCH signatures - hashForCashSignature(int inIndex, Uint8List prevOutScript, int amount, int hashType) { + hashForCashSignature( + int inIndex, Uint8List prevOutScript, int amount, int hashType) { if ((hashType & SIGHASH_BITCOINCASHBIP143) > 0) { if (amount == null) { - throw ArgumentError('Bitcoin Cash sighash requires value of input to be signed.'); + throw ArgumentError( + 'Bitcoin Cash sighash requires value of input to be signed.'); } return _hashForWitnessV0(inIndex, prevOutScript, amount, hashType); @@ -191,10 +197,11 @@ class Transaction { int virtualSize() { return 8 + - varuint.encodingLength(inputs.length) + - varuint.encodingLength(outputs.length) + - inputs.fold(0, (sum, input) => sum + 40 + _varSliceSize(input.script)) + - outputs.fold(0, (sum, output) => sum + 8 + _varSliceSize(output.script)); + varuint.encodingLength(inputs.length) + + varuint.encodingLength(outputs.length) + + inputs.fold(0, (sum, input) => sum + 40 + _varSliceSize(input.script)) + + outputs.fold( + 0, (sum, output) => sum + 8 + _varSliceSize(output.script)); } Uint8List toBuffer([Uint8List buffer, int initialOffset]) { @@ -206,21 +213,24 @@ class Transaction { } Uint8List getHash() { - return bcrypto.hash256(_toBuffer()); + return bcrypto.Crypto.hash256(_toBuffer()); } String getId() { return HEX.encode(getHash().reversed.toList()); } - _hashForWitnessV0(int inIndex, Uint8List prevOutScript, int amount, int hashType) { + _hashForWitnessV0( + int inIndex, Uint8List prevOutScript, int amount, int hashType) { Uint8List tBuffer; int tOffset; void writeSlice(Uint8List slice) { tBuffer.setRange(tOffset, slice.length + tOffset, slice); tOffset += slice.length; - }; + } + + ; void writeUint32(int i) { tBuffer.buffer.asByteData().setUint32(tOffset, i, Endian.little); @@ -255,12 +265,12 @@ class Transaction { writeUint32(txInput.index); }); - hashPrevoutputs = bcrypto.hash256(tBuffer); + hashPrevoutputs = bcrypto.Crypto.hash256(tBuffer); } if ((hashType & SIGHASH_ANYONECANPAY) == 0 && - (hashType & 0x1f) != SIGHASH_SINGLE && - (hashType & 0x1f) != SIGHASH_NONE) { + (hashType & 0x1f) != SIGHASH_SINGLE && + (hashType & 0x1f) != SIGHASH_NONE) { tBuffer = Uint8List(4 * this.inputs.length); tOffset = 0; @@ -268,10 +278,11 @@ class Transaction { writeUint32(txInput.sequence); }); - hashSequence = bcrypto.hash256(tBuffer); + hashSequence = bcrypto.Crypto.hash256(tBuffer); } - if ((hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { + if ((hashType & 0x1f) != SIGHASH_SINGLE && + (hashType & 0x1f) != SIGHASH_NONE) { final txOutputsSize = this.outputs.fold(0, (int sum, Output output) { return sum + 8 + _varSliceSize(output.script); }); @@ -284,8 +295,9 @@ class Transaction { writeVarSlice(output.script); }); - hashOutputs = bcrypto.hash256(tBuffer); - } else if ((hashType & 0x1f) == SIGHASH_SINGLE && (inIndex < this.outputs.length)) { + hashOutputs = bcrypto.Crypto.hash256(tBuffer); + } else if ((hashType & 0x1f) == SIGHASH_SINGLE && + (inIndex < this.outputs.length)) { final output = this.outputs[inIndex]; tBuffer = Uint8List(8 + _varSliceSize(output.script)); @@ -293,7 +305,7 @@ class Transaction { writeUint64(output.value); writeVarSlice(output.script); - hashOutputs = bcrypto.hash256(tBuffer); + hashOutputs = bcrypto.Crypto.hash256(tBuffer); } tBuffer = Uint8List(156 + _varSliceSize(prevOutScript)); @@ -311,7 +323,7 @@ class Transaction { writeSlice(hashOutputs); writeUint32(this.locktime); writeUint32(hashType); - return bcrypto.hash256(tBuffer); + return bcrypto.Crypto.hash256(tBuffer); } _toBuffer([Uint8List buffer, initialOffset]) { @@ -380,11 +392,7 @@ class Transaction { return Output.clone(output); }).toList(); Transaction clonedTx = new Transaction( - originalTx.version, - originalTx.locktime, - inputs, - outputs - ); + originalTx.version, originalTx.locktime, inputs, outputs); return clonedTx; } @@ -407,7 +415,7 @@ class Input { List pubkeys; List signatures; Input( - {this.hash, + {this.hash, this.index, this.script, this.sequence, @@ -431,9 +439,9 @@ class Input { } P2PKH p2pkh = new P2PKH(data: new P2PKHData(input: scriptSig)); return new Input( - prevOutScript: p2pkh.data.output, - pubkeys: [p2pkh.data.pubkey], - signatures: [p2pkh.data.signature]); + prevOutScript: p2pkh.data.output, + pubkeys: [p2pkh.data.pubkey], + signatures: [p2pkh.data.signature]); } factory Input.clone(Input input) { @@ -444,31 +452,31 @@ class Input { sequence: input.sequence, value: input.value, prevOutScript: input.prevOutScript != null - ? Uint8List.fromList(input.prevOutScript) - : null, + ? Uint8List.fromList(input.prevOutScript) + : null, pubkeys: input.pubkeys != null - ? input.pubkeys.map( - (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) - : null, + ? input.pubkeys.map( + (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) + : null, signatures: input.signatures != null - ? input.signatures.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) - : null, + ? input.signatures.map((signature) => + signature != null ? Uint8List.fromList(signature) : null) + : null, ); } @override String toString() { - return 'Input{hash: $hash, index: $index, sequence: $sequence, value: $value, script: $script, ' - + 'signScript: $signScript, prevOutScript: $prevOutScript, pubkeys: $pubkeys, signatures: $signatures}'; + return 'Input{hash: $hash, index: $index, sequence: $sequence, value: $value, script: $script, ' + + 'signScript: $signScript, prevOutScript: $prevOutScript, pubkeys: $pubkeys, signatures: $signatures}'; } static bool _isP2PKHInput(script) { final chunks = bscript.decompile(script); return chunks != null && - chunks.length == 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - bscript.isCanonicalPubKey(chunks[1]); + chunks.length == 2 && + bscript.isCanonicalScriptSignature(chunks[0]) && + bscript.isCanonicalPubKey(chunks[1]); } } @@ -480,7 +488,12 @@ class Output { List pubkeys; List signatures; - Output({this.script, this.value, this.pubkeys, this.signatures, this.valueBuffer}) { + Output( + {this.script, + this.value, + this.pubkeys, + this.signatures, + this.valueBuffer}) { if (value != null && !isSatoshi(value)) throw ArgumentError("Invalid ouput value"); } @@ -500,16 +513,16 @@ class Output { script: output.script != null ? Uint8List.fromList(output.script) : null, value: output.value, valueBuffer: output.valueBuffer != null - ? Uint8List.fromList(output.valueBuffer) - : null, + ? Uint8List.fromList(output.valueBuffer) + : null, pubkeys: output.pubkeys != null - ? output.pubkeys.map( - (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) - : null, + ? output.pubkeys.map( + (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) + : null, signatures: output.signatures != null - ? output.signatures.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) - : null, + ? output.signatures.map((signature) => + signature != null ? Uint8List.fromList(signature) : null) + : null, ); } @@ -517,4 +530,4 @@ class Output { String toString() { return 'Output{script: $script, value: $value, valueBuffer: $valueBuffer, pubkeys: $pubkeys, signatures: $signatures}'; } -} \ No newline at end of file +} diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index 69b3bbf..3d57aa8 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -360,7 +360,7 @@ class TransactionBuilder { final prevOutScript = bscript.compile([ Opcodes.OP_DUP, Opcodes.OP_HASH160, - hash160(kpPubKey), + Crypto.hash160(kpPubKey), Opcodes.OP_EQUALVERIFY, Opcodes.OP_CHECKSIG ]); @@ -391,7 +391,7 @@ class TransactionBuilder { // does our hash160(pubKey) match the output scripts? Uint8List pkh1 = scriptChunks[ 2]; //new P2PKH(data: new P2PKHData(output: script)).data.hash; - Uint8List pkh2 = hash160(ourPubKey); + Uint8List pkh2 = Crypto.hash160(ourPubKey); // this check should work, but for some reason doesn't - it returns false even if both lists are the same // TODO: debug and re-enable this validation diff --git a/lib/src/utils/p2pkh.dart b/lib/src/utils/p2pkh.dart index 13692f3..71dbd3a 100644 --- a/lib/src/utils/p2pkh.dart +++ b/lib/src/utils/p2pkh.dart @@ -32,7 +32,7 @@ class P2PKH { data.hash = data.output.sublist(3, 23); _getDataFromHash(); } else if (data.pubkey != null) { - data.hash = hash160(data.pubkey); + data.hash = Crypto.hash160(data.pubkey); _getDataFromHash(); _getDataFromChunk(); } else if (data.input != null) { @@ -50,12 +50,16 @@ class P2PKH { void _getDataFromChunk([List _chunks]) { if (data.pubkey == null && _chunks != null) { - data.pubkey = (_chunks[1] is int) ? new Uint8List.fromList([_chunks[1]]) : _chunks[1]; - data.hash = hash160(data.pubkey); + data.pubkey = (_chunks[1] is int) + ? new Uint8List.fromList([_chunks[1]]) + : _chunks[1]; + data.hash = Crypto.hash160(data.pubkey); _getDataFromHash(); } if (data.signature == null && _chunks != null) - data.signature = (_chunks[0] is int) ? new Uint8List.fromList([_chunks[0]]) : _chunks[0]; + data.signature = (_chunks[0] is int) + ? new Uint8List.fromList([_chunks[0]]) + : _chunks[0]; if (data.input == null && data.pubkey != null && data.signature != null) { data.input = bscript.compile([data.signature, data.pubkey]); } @@ -97,7 +101,7 @@ class P2PKHData { Uint8List pubkey; Uint8List input; P2PKHData( - {this.address, + {this.address, this.hash, this.output, this.pubkey, @@ -112,9 +116,9 @@ class P2PKHData { isValidOutput(Uint8List data) { return data.length == 25 && - data[0] == Opcodes.OP_DUP && - data[1] == Opcodes.OP_HASH160 && - data[2] == 0x14 && - data[23] == Opcodes.OP_EQUALVERIFY && - data[24] == Opcodes.OP_CHECKSIG; + data[0] == Opcodes.OP_DUP && + data[1] == Opcodes.OP_HASH160 && + data[2] == 0x14 && + data[23] == Opcodes.OP_EQUALVERIFY && + data[24] == Opcodes.OP_CHECKSIG; } diff --git a/lib/src/utils/p2sh.dart b/lib/src/utils/p2sh.dart index f4be5bb..76c7731 100644 --- a/lib/src/utils/p2sh.dart +++ b/lib/src/utils/p2sh.dart @@ -32,7 +32,7 @@ class P2SH { data.hash = data.output.sublist(2, 22); _getDataFromHash(); } else if (data.scriptHash != null) { - data.hash = hash160(data.scriptHash); + data.hash = Crypto.hash160(data.scriptHash); _getDataFromHash(); _getDataFromChunk(); } else if (data.input != null) { @@ -53,7 +53,7 @@ class P2SH { data.scriptHash = (_chunks[1] is int) ? new Uint8List.fromList([_chunks[1]]) : _chunks[1]; - data.hash = hash160(data.scriptHash); + data.hash = Crypto.hash160(data.scriptHash); _getDataFromHash(); } if (data.signature == null && _chunks != null) From e6b4853aebd3e128ea1fe3f500bf90129a6e8bdb Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 5 Feb 2020 19:44:25 +0530 Subject: [PATCH 018/106] support for testnet address --- lib/src/utils/bip21.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/src/utils/bip21.dart b/lib/src/utils/bip21.dart index b335281..aba3b50 100644 --- a/lib/src/utils/bip21.dart +++ b/lib/src/utils/bip21.dart @@ -1,7 +1,8 @@ class Bip21 { static Map decode(String uri) { - if (uri.indexOf('bitcoincash') != 0 || uri['bitcoincash'.length] != ":") - throw ("Invalid BIP21 URI"); + if (uri.indexOf('bitcoincash') != 0 || uri['bitcoincash'.length] != ":") { + if (uri.indexOf('bchtest') != 0) throw ("Invalid BIP21 URI"); + } int split = uri.indexOf("?"); Map uriOptions = Uri.parse(uri).queryParameters; @@ -32,9 +33,12 @@ class Bip21 { } static String encode(String address, Map options) { - var isCashAddress = address.startsWith('bitcoincash:'); - if (!isCashAddress) { + var isMainCashAddress = address.startsWith('bitcoincash:'); + var isTestCashAddress = address.startsWith('bchtest:'); + if (!isMainCashAddress) { address = 'bitcoincash:$address'; + } else if (!isTestCashAddress) { + address = 'bchtest:$address'; } String query = ""; From 78a396d512c88492924016801bb1bf97ef3b8330 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 5 Feb 2020 19:44:45 +0530 Subject: [PATCH 019/106] typo --- lib/src/utils/p2sh.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/utils/p2sh.dart b/lib/src/utils/p2sh.dart index 76c7731..9ebdf3a 100644 --- a/lib/src/utils/p2sh.dart +++ b/lib/src/utils/p2sh.dart @@ -107,7 +107,7 @@ class P2SHData { @override String toString() { - return 'P2PKHData{address: $address, hash: $hash, output: $output, signature: $signature, pubkey: $scriptHash, input: $input}'; + return 'P2SHData{address: $address, hash: $hash, output: $output, signature: $signature, pubkey: $scriptHash, input: $input}'; } } From fa58047351e511a752d9115f55c91daa4aaa4adb Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 23 Mar 2020 11:57:33 +0530 Subject: [PATCH 020/106] removed extra import --- lib/src/utils/script.dart | 41 ++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/lib/src/utils/script.dart b/lib/src/utils/script.dart index f0d571b..5035990 100644 --- a/lib/src/utils/script.dart +++ b/lib/src/utils/script.dart @@ -3,7 +3,7 @@ import '../crypto/ecurve.dart'; import '../utils/opcodes.dart'; import 'pushdata.dart' as pushData; import 'check_types.dart'; -//import 'check_types.dart'; + //Map REVERSE_OPS = opcodes.map((String string, int number) => new MapEntry(number, string)); const OP_INT_BASE = Opcodes.OP_RESERVED; final zero = Uint8List.fromList([0]); @@ -29,7 +29,8 @@ Uint8List compile(List chunks) { offset += 1; return null; } - pushData.EncodedPushData epd = pushData.encode(buffer, chunk.length, offset); + pushData.EncodedPushData epd = + pushData.encode(buffer, chunk.length, offset); offset += epd.size; buffer = epd.buffer; buffer.setRange(offset, offset + chunk.length, chunk); @@ -41,7 +42,8 @@ Uint8List compile(List chunks) { } }); - if (offset != buffer.length) throw new ArgumentError("Could not decode chunks"); + if (offset != buffer.length) + throw new ArgumentError("Could not decode chunks"); return buffer; } @@ -107,25 +109,28 @@ String toASM (List c) { }).join(' '); }*/ -int asMinimalOP (Uint8List buffer) { +int asMinimalOP(Uint8List buffer) { if (buffer.length == 0) return Opcodes.OP_0; if (buffer.length != 1) return null; if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; if (buffer[0] == 0x81) return Opcodes.OP_1NEGATE; return null; } -bool isDefinedHashType (hashType) { + +bool isDefinedHashType(hashType) { final hashTypeMod = hashType & ~0x80; // return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE return hashTypeMod > 0x00 && hashTypeMod < 0x04; } -bool isCanonicalPubKey (Uint8List buffer) => ECurve.isPoint(buffer); -bool isCanonicalScriptSignature (Uint8List buffer) { +bool isCanonicalPubKey(Uint8List buffer) => ECurve.isPoint(buffer); + +bool isCanonicalScriptSignature(Uint8List buffer) { if (!isDefinedHashType(buffer[buffer.length - 1])) return false; return bip66check(buffer.sublist(0, buffer.length - 1)); } -bool bip66check (buffer) { + +bool bip66check(buffer) { if (buffer.length < 8) return false; if (buffer.length > 72) return false; if (buffer[0] != 0x30) return false; @@ -145,7 +150,8 @@ bool bip66check (buffer) { if (lenR > 1 && (buffer[4] == 0x00) && buffer[5] & 0x80 == 0) return false; if (buffer[lenR + 6] & 0x80 != 0) return false; - if (lenS > 1 && (buffer[lenR + 6] == 0x00) && buffer[lenR + 7] & 0x80 == 0) return false; + if (lenS > 1 && (buffer[lenR + 6] == 0x00) && buffer[lenR + 7] & 0x80 == 0) + return false; return true; } @@ -158,8 +164,10 @@ Uint8List bip66encode(r, s) { if (lenS > 33) throw new ArgumentError('S length is too long'); if (r[0] & 0x80 != 0) throw new ArgumentError('R value is negative'); if (s[0] & 0x80 != 0) throw new ArgumentError('S value is negative'); - if (lenR > 1 && (r[0] == 0x00) && r[1] & 0x80 == 0) throw new ArgumentError('R value excessively padded'); - if (lenS > 1 && (s[0] == 0x00) && s[1] & 0x80 == 0) throw new ArgumentError('S value excessively padded'); + if (lenR > 1 && (r[0] == 0x00) && r[1] & 0x80 == 0) + throw new ArgumentError('R value excessively padded'); + if (lenS > 1 && (s[0] == 0x00) && s[1] & 0x80 == 0) + throw new ArgumentError('S value excessively padded'); var signature = new Uint8List(6 + lenR + lenS); @@ -175,12 +183,12 @@ Uint8List bip66encode(r, s) { return signature; } - Uint8List encodeSignature(Uint8List signature, int hashType) { if (!isUint(hashType, 8)) throw ArgumentError("Invalid hasType $hashType"); if (signature.length != 64) throw ArgumentError("Invalid signature"); - final hashTypeMod = hashType & ~0xc0;//0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new ArgumentError('Invalid hashType $hashType'); + final hashTypeMod = hashType & ~0xc0; //0x80; + if (hashTypeMod <= 0 || hashTypeMod >= 4) + throw new ArgumentError('Invalid hashType $hashType'); final hashTypeBuffer = new Uint8List(1); hashTypeBuffer.buffer.asByteData().setUint8(0, hashType); @@ -190,7 +198,8 @@ Uint8List encodeSignature(Uint8List signature, int hashType) { combine.addAll(List.from(hashTypeBuffer)); return Uint8List.fromList(combine); } -Uint8List toDER (Uint8List x) { + +Uint8List toDER(Uint8List x) { var i = 0; while (x[i] == 0) ++i; if (i == x.length) return zero; @@ -199,4 +208,4 @@ Uint8List toDER (Uint8List x) { combine.addAll(x); if (x[0] & 0x80 != 0) return Uint8List.fromList(combine); return x; -} \ No newline at end of file +} From e15c2687556e1cb3b25a1481a95774fa6337ce58 Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 23 Mar 2020 11:57:49 +0530 Subject: [PATCH 021/106] added toSLPAddress and fixed include prefix --- lib/src/address.dart | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index 9e9615f..b9ac9df 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -145,14 +145,13 @@ class Address { static String toCashAddress(String legacyAddress, [bool includePrefix = true]) { final decoded = Address._decodeLegacyAddress(legacyAddress); - - if (!includePrefix) { - decoded['prefix'] = ""; - } - final cashAddress = Address._encode(decoded['prefix'], decoded['type'], decoded["hash"]); - return cashAddress; + if (!includePrefix) { + return cashAddress.split(":")[1]; + } else { + return cashAddress; + } } /// Converts cashAddr format to legacy address @@ -182,6 +181,28 @@ class Address { return toBase58Check(decoded["hash"], version); } + /// Converts legacy or cash address to SLP address + static String toSLPAddress(String address, [bool includePrefix = true]) { + final decoded = Address._decode(address); + switch (decoded["prefix"]) { + case 'bitcoincash': + decoded['prefix'] = "simpleledger"; + break; + case 'bchtest': + decoded['prefix'] = "slptest"; + break; + default: + throw FormatException("Unsupported address format: $address"); + } + final slpAddress = + Address._encode(decoded['prefix'], decoded['type'], decoded["hash"]); + if (!includePrefix) { + return slpAddress.split(":")[1]; + } else { + return slpAddress; + } + } + /// Detects type of the address and returns [formatCashAddr] or [formatLegacy] static int detectFormat(String address) { // decode the address to determine the format From efa7d79a0b63212f54d10afb42db696c46dbd37b Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 23 Mar 2020 11:58:04 +0530 Subject: [PATCH 022/106] added toSLPAddress --- lib/src/hdnode.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/hdnode.dart b/lib/src/hdnode.dart index b0292a3..220016b 100644 --- a/lib/src/hdnode.dart +++ b/lib/src/hdnode.dart @@ -212,6 +212,9 @@ class HDNode { /// Returns HDNode's address in cashAddr format String toCashAddress() => Address.toCashAddress(toLegacyAddress()); + /// Returns HDNode's address in slpAddr format + String toSLPAddress() => Address.toSLPAddress(toLegacyAddress()); + HDNode _deriveHardened(int index) { return derive(index + HIGHEST_BIT); } From c072d9c79e3aa134ad89a751cf069b594542bf9e Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 15 Apr 2020 14:01:01 +0530 Subject: [PATCH 023/106] advanced bch address methods --- lib/src/bchaddress.dart | 214 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 lib/src/bchaddress.dart diff --git a/lib/src/bchaddress.dart b/lib/src/bchaddress.dart new file mode 100644 index 0000000..33d4758 --- /dev/null +++ b/lib/src/bchaddress.dart @@ -0,0 +1,214 @@ +import 'package:bitbox/src/publickey.dart'; + +import 'encoding/base58check.dart' as bs58check; +import 'package:hex/hex.dart'; +import 'encoding/utils.dart'; +import 'dart:convert'; +import 'networks.dart'; +import 'exceptions.dart'; + +//TODO: No support P2SH addresses at the moment. I'll add when I need it. + +/// This class abstracts away the internals of address encoding and provides +/// a convenient means to both encode and decode information from a bitcoin address. +/// +/// Bitcoin addresses are a construct which facilitates interoperability +/// between different wallets. I.e. an agreement amongst wallet providers to have a +/// common means of sharing the hashed public key value needed to send someone bitcoin using one +/// of the standard public-key-based transaction types. +/// +/// The Address does not contain a public key, only a hashed value of a public key +/// which is derived as explained below. +/// +/// Bitcoin addresses are not part of the consensus rules of bitcoin. +/// +/// Bitcoin addresses are encoded as follows +/// * 1st byte - indicates the network type which is either MAINNET or TESTNET +/// * next 20 bytes - the hash value computed by taking the `ripemd160(sha256(PUBLIC_KEY))` +/// * last 4 bytes - a checksum value taken from the first four bytes of sha256(sha256(previous_21_bytes)) + +class BCHAddress { + List _networkTypes; + + String _publicKeyHash; + AddressType _addressType; + NetworkType _networkType; + int _version; + + /// Constructs a new Address object + /// + /// [address] is the base58encoded bitcoin address. + /// + BCHAddress(String address) { + _fromBase58(address); + } + + /// Constructs a new Address object from a public key. + /// + /// [hexPubKey] is the hexadecimal encoding of a public key. + /// + /// [networkType] is used to distinguish between MAINNET and TESTNET. + /// + /// Also see [NetworkType] + BCHAddress.fromHex(String hexPubKey, NetworkType networkType) { + _createFromHex(hexPubKey, networkType); + } + + /// Constructs a new Address object from the public key + /// + /// [pubKey] - The public key + /// + /// [networkType] is used to distinguish between MAINNET and TESTNET. + /// + /// Also see [NetworkType] + BCHAddress.fromPublicKey(BCHPublicKey pubKey, NetworkType networkType) { + _createFromHex(pubKey.toHex(), networkType); + } + + /// Constructs a new Address object from a compressed public key value + /// + /// [pubkeyBytes] is a byte-buffer of a public key + /// + /// [networkType] is used to distinguish between MAINNET and TESTNET. + /// + /// Also see [NetworkType] + BCHAddress.fromCompressedPubKey( + List pubkeyBytes, NetworkType networkType) { + _createFromHex(HEX.encode(pubkeyBytes), networkType); + _publicKeyHash = HEX.encode(hash160(pubkeyBytes)); + } + + /// Constructs a new Address object from a base58-encoded string. + /// + /// Base58-encoded strings are the "standard" means of sharing bitoin addresses amongst + /// wallets. This is typically done either using the string of directly, or by using a + /// QR-encoded form of this string. + /// + /// Typically, if someone is sharing their bitcoin address with you, this is the method + /// you would use to instantiate an [Address] object for use with [Transaction] objects. + /// + BCHAddress.fromBase58(String base58Address) { + if (base58Address.length != 25) { + throw AddressFormatException( + 'Address should be 25 bytes long. Only [${base58Address.length}] bytes long.'); + } + + _fromBase58(base58Address); + } + + /// Serialise this address object to a base58-encoded string + /// + /// Base58-encoded strings are the "standard" means of sharing bitoin addresses amongst + /// wallets. This is typically done either using the string directly, or by using a + /// QR-encoded form of this string. + /// + /// When sharing a bitcoin address with an external party either as a QR-code or via + /// email etc., this would typically be the form in which you share the address. + /// + String toBase58() { + // A stringified buffer is: + // 1 byte version + data bytes + 4 bytes check code (a truncated hash) + var rawHash = + HEX.decode(_publicKeyHash).map((elem) => elem.toSigned(8)).toList(); + + return _getEncoded(rawHash); + } + + /// Serialise this address object to a base58-encoded string. + /// This method is an alias for the [toBase58()] method + @override + String toString() { + return toBase58(); + } + + /// Returns the public key hash `ripemd160(sha256(public_key))` encoded as a hexadecimal string + String toHex() { + return _publicKeyHash; + } + + String _getEncoded(List hashAddress) { + var addressBytes = List(1 + hashAddress.length + 4); + addressBytes[0] = _version; + + //copy all of raw address content, taking care not to + //overwrite the version byte at start + addressBytes.fillRange(1, addressBytes.length, 0); + addressBytes.setRange(1, hashAddress.length + 1, hashAddress); + + //checksum calculation... + //doubleSha everything except the last four checksum bytes + var doubleShaAddr = + sha256Twice(addressBytes.sublist(0, hashAddress.length + 1)); + var checksum = + doubleShaAddr.sublist(0, 4).map((elem) => elem.toSigned(8)).toList(); + + addressBytes.setRange( + hashAddress.length + 1, addressBytes.length, checksum); + var encoded = bs58check.encode(addressBytes); + var utf8Decoded = utf8.decode(encoded); + + return utf8Decoded; + } + + void _fromBase58(String address) { + address = address.trim(); + + var versionAndDataBytes = bs58check.decodeChecked(address); + var versionByte = versionAndDataBytes[0].toUnsigned(8); + + _version = versionByte & 0xFF; + _networkTypes = Networks.getNetworkTypes(_version); + _addressType = Networks.getAddressType(_version); + _networkType = Networks.getNetworkTypes(_version)[0]; + var stripVersion = + versionAndDataBytes.sublist(1, versionAndDataBytes.length); + _publicKeyHash = + HEX.encode(stripVersion.map((elem) => elem.toUnsigned(8)).toList()); + } + + void _createFromHex(String hexPubKey, NetworkType networkType) { + //make an assumption about PKH vs PSH for naked address generation + var versionByte; + if (networkType == NetworkType.MAIN) { + versionByte = Networks.getNetworkVersion(NetworkAddressType.MAIN_PKH); + } else { + versionByte = Networks.getNetworkVersion(NetworkAddressType.TEST_PKH); + } + + _version = versionByte & 0XFF; + _publicKeyHash = HEX.encode(hash160(HEX.decode(hexPubKey))); + _addressType = Networks.getAddressType(_version); + _networkType = networkType; + } + + /// Returns a hash of the Public Key + /// + /// The sha256 digest of the public key is computed, and the result of that + /// computation is then passed to the `ripemd160()` digest function. + /// + /// The returned value is HEX-encoded + String get address => _publicKeyHash; + + /// An alias for the [address] property + String get pubkeyHash160 => _publicKeyHash; + + /// Returns a list of network types supported by this address + /// + /// This is only really needed because BCH has two different test networks + /// which technically share the same integer value when encoded, but for + /// which it is useful to have a type-level distinction during development + List get networkTypes => _networkTypes; + + /// Returns the specific Network Type that this Address is compatible with + NetworkType get networkType => _networkType; + + /// Returns the type of "standard transaction" this Address is meant to be used for. + /// + /// Addresses are not part of the consensus rules of bitcoin. However with the introduction + /// of "standard transaction types" wallets have fallen in line with providing + /// address types that distinguish the types of transactions the coins are meant + /// to be associated with. + /// + /// See documentation for [Transaction] + AddressType get addressType => _addressType; +} From 0f9eaff1065816e14758e61e38b21dbbe2bb5429 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 15 Apr 2020 14:03:00 +0530 Subject: [PATCH 024/106] update dependencies --- pubspec.yaml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 166453a..839c3e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,13 +11,23 @@ dependencies: flutter: sdk: flutter http: ^0.12.0+1 - bip39: ^1.0.3 - pointycastle: ^1.0.1 - bip32: ^1.0.5 + bip39: + git: + url: https://github.com/RomitRadical/bip39 + ref: master + pointycastle: + git: + url: https://github.com/jbdtky/pc-dart.git + ref: master + bip32: + git: + url: https://github.com/jbdtky/bip32-dart + ref: master hex: ^0.1.2 bs58check: ^1.0.1 fixnum: ^0.10.9 meta: ^1.1.6 + buffer: ^1.0.6 dev_dependencies: flutter_test: @@ -28,7 +38,6 @@ dev_dependencies: # The following section is specific to Flutter. flutter: - # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg @@ -39,7 +48,6 @@ flutter: # # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. - # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a From 943eab623427c67288b9e3164c54e747adaa2b71 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 15 Apr 2020 14:03:16 +0530 Subject: [PATCH 025/106] import more files --- lib/bitbox.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/bitbox.dart b/lib/bitbox.dart index 17034ff..dddef25 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -2,6 +2,7 @@ library bitbox; export 'src/account.dart'; export 'src/address.dart'; +export 'src/bchaddress.dart'; export 'src/bitbox.dart'; export 'src/bitcoincash.dart'; export 'src/block.dart'; @@ -10,6 +11,9 @@ export 'src/crypto/crypto.dart'; export 'src/ecpair.dart'; export 'src/hdnode.dart'; export 'src/mnemonic.dart'; +export 'src/networks.dart'; +export 'src/privatekey.dart'; +export 'src/publickey.dart'; export 'src/rawtransactions.dart'; export 'src/transaction.dart'; export 'src/transactionbuilder.dart'; From 33c2414162545d20e85ef47ebdd6aefe22524d9d Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 15 Apr 2020 14:03:41 +0530 Subject: [PATCH 026/106] add new files --- lib/src/encoding/base58check.dart | 137 +++++++++++++ lib/src/encoding/utils.dart | 327 ++++++++++++++++++++++++++++++ lib/src/exceptions.dart | 119 +++++++++++ lib/src/networks.dart | 113 +++++++++++ lib/src/privatekey.dart | 266 ++++++++++++++++++++++++ lib/src/publickey.dart | 318 +++++++++++++++++++++++++++++ lib/src/utils/bip21.dart | 6 +- pubspec.lock | 84 +++----- 8 files changed, 1314 insertions(+), 56 deletions(-) create mode 100644 lib/src/encoding/base58check.dart create mode 100644 lib/src/encoding/utils.dart create mode 100644 lib/src/exceptions.dart create mode 100644 lib/src/networks.dart create mode 100644 lib/src/privatekey.dart create mode 100644 lib/src/publickey.dart diff --git a/lib/src/encoding/base58check.dart b/lib/src/encoding/base58check.dart new file mode 100644 index 0000000..1476a51 --- /dev/null +++ b/lib/src/encoding/base58check.dart @@ -0,0 +1,137 @@ +import 'dart:typed_data'; +import 'dart:convert'; +import 'utils.dart'; +import 'package:collection/collection.dart'; +import '../exceptions.dart'; + +var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +List decode(String input) { + if (input.isEmpty) { + return List(); + } + + var encodedInput = utf8.encode(input); + var uintAlphabet = utf8.encode(ALPHABET); + + List INDEXES = List(128)..fillRange(0, 128, -1); + for (int i = 0; i < ALPHABET.length; i++) { + INDEXES[uintAlphabet[i]] = i; + } + + // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). + List input58 = List(encodedInput.length); + input58.fillRange(0, input58.length, 0); + for (int i = 0; i < encodedInput.length; ++i) { + var c = encodedInput[i]; + var digit = c < 128 ? INDEXES[c] : -1; + if (digit < 0) { + var buff = List(1)..add(c); + var invalidChar = utf8.decode(buff); + throw new AddressFormatException( + "Illegal character " + invalidChar + " at position " + i.toString()); + } + input58[i] = digit; + } + + // Count leading zeros. + int zeros = 0; + while (zeros < input58.length && input58[zeros] == 0) { + ++zeros; + } + + // Convert base-58 digits to base-256 digits. + var decoded = List(encodedInput.length); + decoded.fillRange(0, decoded.length, 0); + int outputStart = decoded.length; + for (int inputStart = zeros; inputStart < input58.length;) { + decoded[--outputStart] = divmod(input58, inputStart, 58, 256); + if (input58[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + + // Ignore extra leading zeroes that were added during the calculation. + while (outputStart < decoded.length && decoded[outputStart] == 0) { + ++outputStart; + } + + // Return decoded data (including original number of leading zeros). + return decoded.sublist(outputStart - zeros, decoded.length); +} + +/** + * Divides a number, represented as an array of bytes each containing a single digit + * in the specified base, by the given divisor. The given number is modified in-place + * to contain the quotient, and the return value is the remainder. + */ +divmod(List number, int firstDigit, int base, int divisor) { +// this is just long division which accounts for the base of the input digits + int remainder = 0; + for (int i = firstDigit; i < number.length; i++) { + int digit = number[i] & 0xFF; + int temp = remainder * base + digit; + number[i] = (temp / divisor).toInt(); + remainder = temp % divisor; + } + + return remainder.toSigned(8); +} + +/** + * Encodes the given bytes as a base58 string (no checksum is appended). + */ +Uint8List encode(List encodedInput) { + var uintAlphabet = utf8.encode(ALPHABET); + var ENCODED_ZERO = uintAlphabet[0]; + +// var encodedInput = utf8.encode(input); + + if (encodedInput.isEmpty) { + return List(); + } + + // Count leading zeros. + int zeros = 0; + while (zeros < encodedInput.length && encodedInput[zeros] == 0) { + ++zeros; + } + + // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) + //input = Arrays.copyOf(input, input.length); // since we modify it in-place + Uint8List encoded = + Uint8List(encodedInput.length * 2); // upper bound <----- ??? + int outputStart = encoded.length; + for (int inputStart = zeros; inputStart < encodedInput.length;) { + encoded[--outputStart] = + uintAlphabet[divmod(encodedInput, inputStart, 256, 58)]; + if (encodedInput[inputStart] == 0) { + ++inputStart; // optimization - skip leading zeros + } + } + // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input. + while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) { + ++outputStart; + } + while (--zeros >= 0) { + encoded[--outputStart] = ENCODED_ZERO; + } + // Return encoded string (including encoded leading zeros). + return encoded.sublist(outputStart, encoded.length); +} + +List decodeChecked(String input) { + List decoded = decode(input); + if (decoded.length < 4) throw new AddressFormatException("Input too short"); + + List data = decoded.sublist(0, decoded.length - 4); + List checksum = decoded.sublist(decoded.length - 4, decoded.length); + List actualChecksum = sha256Twice(data).sublist(0, 4); + + var byteConverted = actualChecksum + .map((elem) => elem.toSigned(8)); //convert unsigned list back to signed + if (!IterableEquality().equals(checksum, byteConverted)) + throw new BadChecksumException("Checksum does not validate"); + + return data; +} diff --git a/lib/src/encoding/utils.dart b/lib/src/encoding/utils.dart new file mode 100644 index 0000000..acbbb23 --- /dev/null +++ b/lib/src/encoding/utils.dart @@ -0,0 +1,327 @@ +import 'package:bitbox/src/exceptions.dart'; +import 'package:hex/hex.dart'; +import 'package:pointycastle/digests/ripemd160.dart'; +import 'package:pointycastle/digests/sha256.dart'; +import 'dart:typed_data'; +import 'package:buffer/buffer.dart'; +import 'dart:math'; + +//import 'package:pointycastle/src/utils.dart'; +import 'package:pointycastle/export.dart'; + +List sha256Twice(List bytes) { + var first = new SHA256Digest().process(Uint8List.fromList(bytes)); + var second = new SHA256Digest().process(first); + return second.toList(); +} + +List sha256(List bytes) { + return new SHA256Digest().process(Uint8List.fromList(bytes)).toList(); +} + +List sha1(List bytes) { + return new SHA1Digest().process(Uint8List.fromList(bytes)).toList(); +} + +List hash160(List bytes) { + List shaHash = new SHA256Digest().process(Uint8List.fromList(bytes)); + var ripeHash = new RIPEMD160Digest().process(shaHash); + return ripeHash.toList(); +} + +List ripemd160(List bytes) { + var ripeHash = new RIPEMD160Digest().process(Uint8List.fromList(bytes)); + return ripeHash.toList(); +} + +int hexToUint16(List hexBuffer) { + return int.parse(HEX.encode(hexBuffer), radix: 16).toUnsigned(16); +} + +int hexToInt32(List hexBuffer) { + return int.parse(HEX.encode(hexBuffer), radix: 16).toSigned(32); +} + +int hexToUint32(List hexBuffer) { + return int.parse(HEX.encode(hexBuffer), radix: 16).toUnsigned(32); +} + +int hexToInt64(List hexBuffer) { + return int.parse(HEX.encode(hexBuffer), radix: 16).toSigned(64); +} + +BigInt hexToUint64(List hexBuffer) { + return BigInt.parse(HEX.encode(hexBuffer), radix: 16).toUnsigned(64); +} + +List varintBufNum(n) { +// List buf ; + ByteDataWriter writer = ByteDataWriter(); + if (n < 253) { + writer.writeUint8(n); + } else if (n < 0x10000) { + writer.writeUint8(253); + writer.writeUint16(n, Endian.little); + } else if (n < 0x100000000) { + writer.writeUint8(254); + writer.writeUint32(n, Endian.little); + } else { + writer.writeUint8(255); + writer.writeInt32(n & -1, Endian.little); + writer.writeUint32((n / 0x100000000).floor(), Endian.little); + } + return writer.toBytes().toList(); +} + +Uint8List varIntWriter(int length) { + ByteDataWriter writer = ByteDataWriter(); + + if (length == null) { + return writer.toBytes(); + } + + if (length < 0xFD) { + writer.writeUint8(length); + return writer.toBytes(); + } + + if (length < 0xFFFF) { +// return HEX.decode("FD" + length.toRadixString(16)); + writer.writeUint8(253); + writer.writeUint16(length, Endian.little); + return writer.toBytes(); + } + + if (length < 0xFFFFFFFF) { +// return HEX.decode("FE" + length.toRadixString(16)); + + writer.writeUint8(254); + writer.writeUint32(length, Endian.little); + return writer.toBytes(); + } + + if (length < 0xFFFFFFFFFFFFFFFF) { +// return HEX.decode("FF" + length.toRadixString(16)); + + writer.writeUint8(255); + writer.writeInt32(length & -1, Endian.little); + writer.writeUint32((length / 0x100000000).floor(), Endian.little); + return writer.toBytes(); + } + + return writer.toBytes(); +} + +List calcVarInt(int length) { + if (length == null) return Uint8List(0); + + if (length < 0xFD) return HEX.decode(length.toRadixString(16)); + + if (length < 0xFFFF) return HEX.decode("FD" + length.toRadixString(16)); + + if (length < 0xFFFFFFFF) return HEX.decode("FE" + length.toRadixString(16)); + + if (length < 0xFFFFFFFFFFFFFFFF) + return HEX.decode("FF" + length.toRadixString(16)); + + return Uint8List(0); +} + +int readVarIntNum(ByteDataReader reader) { + var first = reader.readUint8(); + switch (first) { + case 0xFD: + return reader.readUint16(Endian.little); + break; + case 0xFE: + return reader.readUint32(Endian.little); + break; + case 0xFF: + var bn = BigInt.from(reader.readUint64(Endian.little)); + var n = bn.toInt(); + if (n <= pow(2, 53)) { + return n; + } else { + throw new Exception( + 'number too large to retain precision - use readVarintBN'); + } + break; + default: + return first; + } +} + +//FIXME: Should probably have two versions of this function. One for BigInt, one for Int +BigInt readVarInt(Uint8List buffer) { + var first = + int.parse(HEX.encode(buffer.sublist(0, 1)), radix: 16).toUnsigned(8); + + switch (first) { + case 0xFD: + return BigInt.from( + hexToUint16(buffer.sublist(1, 3))); //2 bytes == Uint16 + + case 0xFE: + return BigInt.from(hexToUint32(buffer.sublist(1, 5))); //4 bytes == Uint32 + + case 0xFF: + return hexToUint64(buffer.sublist(1, 9)); //8 bytes == Uint64 + + default: + return BigInt.from(first); + } +} + +int getBufferOffset(int count) { + if (count < 0xFD) return 1; + + if (count == 0xFD) return 3; //2 bytes == Uint16 + + if (count == 0xFE) return 5; //4 bytes == Uint32 + + if (count == 0xFF) return 9; +} + +/// Decode a BigInt from bytes in big-endian encoding. +BigInt decodeBigInt(List bytes) { + BigInt result = new BigInt.from(0); + + for (int i = 0; i < bytes.length; i++) { + result += new BigInt.from(bytes[bytes.length - i - 1]) << (8 * i); + } + + return result; +} + +var _byteMask = new BigInt.from(0xff); + +/// Encode a BigInt into bytes using big-endian encoding. +/// Provide a minimum byte length `minByteLength` +Uint8List encodeBigInt(BigInt number, {int minByteLength = 0}) { + int size = (number.bitLength + 7) >> 3; + size = minByteLength > size ? minByteLength : size; + + var result = Uint8List(size); + for (int i = 0; i < size; i++) { + result[size - i - 1] = (number & _byteMask).toInt(); + number = number >> 8; + } + + return result; +} + +toScriptNumBuffer(BigInt value) { + return toSM(value, endian: Endian.little); +} + +BigInt fromScriptNumBuffer(Uint8List buf, bool fRequireMinimal, + {int nMaxNumSize = 4}) { + if (!(buf.length <= nMaxNumSize)) { + throw new ScriptException('script number overflow'); + } + + if (fRequireMinimal && buf.length > 0) { + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if ((buf[buf.length - 1] & 0x7f) == 0) { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if (buf.length <= 1 || (buf[buf.length - 2] & 0x80) == 0) { + throw new Exception('non-minimally encoded script number'); + } + } + } + return fromSM(buf, endian: Endian.little); +} + +toSM(BigInt value, {Endian endian = Endian.big}) { + var buf = toSMBigEndian(value); + + if (endian == Endian.little) { + buf = buf.reversed.toList(); + } + return buf; +} + +List toSMBigEndian(BigInt value) { + List buf = []; + if (value.compareTo(BigInt.zero) == -1) { + buf = toBuffer(-value); + if (buf[0] & 0x80 != 0) { + buf = [0x80] + buf; + } else { + buf[0] = buf[0] | 0x80; + } + } else { + buf = toBuffer(value); + if (buf[0] & 0x80 != 0) { + buf = [0x00] + buf; + } + } + + if (buf.length == 1 && buf[0] == 0) { + buf = []; + } + return buf; +} + +BigInt fromSM(Uint8List buf, {Endian endian = Endian.big}) { + BigInt ret; + List localBuffer = buf.toList(); + if (localBuffer.length == 0) { + return decodeBigInt([0]); + } + + if (endian == Endian.little) { + localBuffer = buf.reversed.toList(); + } + + if (localBuffer[0] & 0x80 != 0) { + localBuffer[0] = localBuffer[0] & 0x7f; + ret = decodeBigInt(localBuffer); + ret = (-ret); + } else { + ret = decodeBigInt(localBuffer); + } + + return ret; +} + +//FIXME: New implementation. Untested +List toBuffer(BigInt value, {int size = 0, Endian endian = Endian.big}) { + String hex; + List buf = []; + if (size != 0) { + hex = value.toRadixString(16); + int natlen = (hex.length / 2) as int; + buf = HEX.decode(hex); + + if (natlen == size) { + // buf = buf + } else if (natlen > size) { + buf = buf.sublist(natlen - buf.length, buf.length); +// buf = BN.trim(buf, natlen); + } else if (natlen < size) { + List padding = [size]; + padding.fillRange(0, size, 0); + buf.insertAll(0, padding); +// buf = BN.pad(buf, natlen, opts.size) + } + } else { + hex = value.toRadixString(16); + buf = HEX.decode(hex); + } + + if (endian == Endian.little) { + buf = buf.reversed.toList(); + } + + return buf; +} diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart new file mode 100644 index 0000000..aaa981a --- /dev/null +++ b/lib/src/exceptions.dart @@ -0,0 +1,119 @@ +class AddressFormatException implements Exception { + String cause; + + AddressFormatException(this.cause); +} + +class BadChecksumException implements AddressFormatException { + String cause; + + BadChecksumException(this.cause); +} + +class BadParameterException implements Exception { + String cause; + + BadParameterException(this.cause); +} + +class InvalidPointException implements Exception { + String cause; + + InvalidPointException(this.cause); +} + +class InvalidNetworkException implements Exception { + String cause; + + InvalidNetworkException(this.cause); +} + +class InvalidKeyException implements Exception { + String cause; + + InvalidKeyException(this.cause); +} + +class IllegalArgumentException implements Exception { + String cause; + + IllegalArgumentException(this.cause); +} + +class DerivationException implements Exception { + String cause; + + DerivationException(this.cause); +} + +class InvalidPathException implements Exception { + String cause; + + InvalidPathException(this.cause); +} + +class UTXOException implements Exception { + String cause; + + UTXOException(this.cause); +} + +class TransactionAmountException implements Exception { + String cause; + + TransactionAmountException(this.cause); +} + +class ScriptException implements Exception { + String cause; + + ScriptException(this.cause); +} + +class SignatureException implements Exception { + String cause; + + SignatureException(this.cause); +} + +class TransactionFeeException implements Exception { + String cause; + + TransactionFeeException(this.cause); +} + +class InputScriptException implements Exception { + String cause; + + InputScriptException(this.cause); +} + +class TransactionException implements Exception { + String cause; + + TransactionException(this.cause); +} + +class LockTimeException implements Exception { + String cause; + + LockTimeException(this.cause); +} + +class InterpreterException implements Exception { + String cause; + + InterpreterException(this.cause); +} + +class BlockException implements Exception { + String cause; + + BlockException(this.cause); +} + +class MerkleTreeException implements Exception { + String cause; + + MerkleTreeException(this.cause); +} diff --git a/lib/src/networks.dart b/lib/src/networks.dart new file mode 100644 index 0000000..4f5512d --- /dev/null +++ b/lib/src/networks.dart @@ -0,0 +1,113 @@ +import 'exceptions.dart'; +import 'dart:typed_data'; +import 'package:hex/hex.dart'; + +enum NetworkType { MAIN, TEST, REGTEST, SCALINGTEST } + +enum AddressType { PUBKEY_HASH, SCRIPT_HASH } + +enum KeyType { PUBLIC, PRIVATE } + +enum NetworkAddressType { MAIN_PKH, MAIN_P2SH, TEST_PKH, TEST_P2SH } + +/// Utility class used to inject strong typing into the [Address] class' representation of network and address types. +/// +/// NOTE: The network type and address type fields are overloaded in the way that wallet's interpret them. +/// This is not consensus-level stuff. Used only in Address representations by wallets. +/// +/// MAINNET +/// ------- +/// Address Header = 0; +/// P2SH Header = 5; +/// +/// TESTNET / REG_TESTNET / SCALING_TESTNET +/// ---------------------------------------- +/// Address Header = 111; +/// P2SH Header = 196; +class Networks { + /// Retrieve the list of network types corresponding to the version byte + /// + /// [version] - The version byte from the head of a serialized [Address] + /// + /// Returns a list of possible network types for the corresponding version byte + static List getNetworkTypes(int version) { + switch (version) { + case 0: + return [NetworkType.MAIN]; + case 111: + return [NetworkType.TEST, NetworkType.REGTEST, NetworkType.SCALINGTEST]; + case 5: + return [NetworkType.MAIN]; + case 196: + return [NetworkType.TEST, NetworkType.REGTEST, NetworkType.SCALINGTEST]; + + default: + throw new AddressFormatException( + '[$version] is not a valid network type.'); + break; + } + } + + /// Retrieve the address type corresponding to a specific version. + /// + /// [version] - The version byte from the head of a serialized [Address] + /// + /// Returns the address type corresponding to a specific [Address] version byte. + static AddressType getAddressType(int version) { + switch (version) { + case 0: + return AddressType.PUBKEY_HASH; + case 111: + return AddressType.PUBKEY_HASH; + case 5: + return AddressType.SCRIPT_HASH; + case 196: + return AddressType.SCRIPT_HASH; + + default: + throw new AddressFormatException( + '[$version] is not a valid address type.'); + break; + } + } + + /// This method retrieves the version byte corresponding to the NetworkAddressType + /// + /// [type] - The network address type + /// + /// Returns the version byte to prepend to a serialized [Address] + static int getNetworkVersion(NetworkAddressType type) { + switch (type) { + case NetworkAddressType.MAIN_P2SH: + return 5; + case NetworkAddressType.MAIN_PKH: + return 0; + case NetworkAddressType.TEST_P2SH: + return 196; + case NetworkAddressType.TEST_PKH: + return 111; + default: + return 0; + } + } + + /// Given an address' version byte this method retrieves the corresponding network type. + /// + /// [versionByte] - The version byte at the head of a wallet address + /// + /// Returns the network address type + static NetworkAddressType getNetworkAddressType(int versionByte) { + switch (versionByte) { + case 5: + return NetworkAddressType.MAIN_P2SH; + case 0: + return NetworkAddressType.MAIN_PKH; + case 196: + return NetworkAddressType.TEST_P2SH; + case 111: + return NetworkAddressType.TEST_PKH; + default: + return NetworkAddressType.MAIN_PKH; + } + } +} diff --git a/lib/src/privatekey.dart b/lib/src/privatekey.dart new file mode 100644 index 0000000..4753b12 --- /dev/null +++ b/lib/src/privatekey.dart @@ -0,0 +1,266 @@ +import 'package:bitbox/src/bchaddress.dart'; +import 'package:bitbox/src/exceptions.dart'; +import 'package:bitbox/src/networks.dart'; +import 'package:bitbox/src/publickey.dart'; +import 'package:pointycastle/pointycastle.dart'; +import 'package:pointycastle/random/fortuna_random.dart'; +import 'encoding/base58check.dart' as bs58check; +import 'package:hex/hex.dart'; +import 'dart:typed_data'; +import 'dart:convert'; +import 'dart:math'; +import 'encoding/utils.dart'; +import 'package:pointycastle/key_generators/ec_key_generator.dart'; +import 'package:pointycastle/ecc/curves/secp256k1.dart'; +import 'package:pointycastle/api.dart'; + +/// Manages an ECDSA private key. +/// +/// Bitcoin uses ECDSA for it's public/private key cryptography. +/// Specifically it uses the `secp256k1` elliptic curve. +/// +/// This class wraps cryptographic operations related to ECDSA from the +/// [PointyCastle](https://pub.dev/packages/pointycastle) library/package. +/// +/// You can read a good primer on Elliptic Curve Cryptography at [This Cloudflare blog post](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/) +/// +/// +class BCHPrivateKey { + final _domainParams = ECDomainParameters('secp256k1'); + final _secureRandom = FortunaRandom(); + + var _hasCompressedPubKey = false; + var _networkType = NetworkType.MAIN; //Mainnet by default + + var random = Random.secure(); + + BigInt _d; + ECPrivateKey _ecPrivateKey; + BCHPublicKey _bchPublicKey; + + /// Constructs a random private key. + /// + /// [networkType] - Optional network type. Defaults to mainnet. The network type is only + /// used when serialising the Private Key in *WIF* format. See [toWIF()]. + /// + BCHPrivateKey({networkType = NetworkType.MAIN}) { + var keyParams = ECKeyGeneratorParameters(ECCurve_secp256k1()); + _secureRandom.seed(KeyParameter(_seed())); + + var generator = ECKeyGenerator(); + generator.init(ParametersWithRandom(keyParams, _secureRandom)); + + var keypair = generator.generateKeyPair(); + + _hasCompressedPubKey = true; + _networkType = networkType; + + _ecPrivateKey = keypair.privateKey; + _d = _ecPrivateKey.d; + _bchPublicKey = BCHPublicKey.fromPrivateKey(this); + } + + /// Constructs a Private Key from a Big Integer. + /// + /// [privateKey] - The private key as a Big Integer value. Remember that in + /// ECDSA we compute the public key (Q) as `Q = d * G` + BCHPrivateKey.fromBigInt(BigInt privateKey) { + _ecPrivateKey = _privateKeyFromBigInt(privateKey); + _d = privateKey; + _hasCompressedPubKey = true; + _bchPublicKey = BCHPublicKey.fromPrivateKey(this); + } + + /// Construct a Private Key from the hexadecimal value representing the + /// BigInt value of (d) in ` Q = d * G ` + /// + /// [privhex] - The BigInt representation of the private key as a hexadecimal string + /// + /// [networkType] - The network type we intend to use to corresponding WIF representation on. + BCHPrivateKey.fromHex(String privhex, NetworkType networkType) { + var d = BigInt.parse(privhex, radix: 16); + + _hasCompressedPubKey = true; + _networkType = networkType; + _ecPrivateKey = _privateKeyFromBigInt(d); + _d = d; + _bchPublicKey = BCHPublicKey.fromPrivateKey(this); + } + + /// Construct a Private Key from the WIF encoded format. + /// + /// WIF is an abbreviation for Wallet Import Format. It is a format based on base58-encoding + /// a private key so as to make it resistant to accidental user error in copying it. A wallet + /// should be able to verify that the WIF format represents a valid private key. + /// + /// [wifKey] - The private key in WIF-encoded format. See [this bitcoin wiki entry](https://en.bitcoin.it/wiki/Wallet_import_format) + /// + BCHPrivateKey.fromWIF(String wifKey) { + if (wifKey.length != 51 && wifKey.length != 52) { + throw InvalidKeyException( + 'Valid keys are either 51 or 52 bytes in length'); + } + + //decode from base58 + var versionAndDataBytes = bs58check.decodeChecked(wifKey); + + switch (wifKey[0]) { + case '5': + { + if (wifKey.length != 51) { + throw InvalidKeyException( + 'Uncompressed private keys have a length of 51 bytes'); + } + + _hasCompressedPubKey = false; + _networkType = NetworkType.MAIN; + break; + } + case '9': + { + if (wifKey.length != 51) { + throw InvalidKeyException( + 'Uncompressed private keys have a length of 51 bytes'); + } + + _hasCompressedPubKey = false; + _networkType = NetworkType.TEST; + break; + } + case 'L': + case 'K': + { + if (wifKey.length != 52) { + throw InvalidKeyException( + 'Compressed private keys have a length of 52 bytes'); + } + + _networkType = NetworkType.MAIN; + _hasCompressedPubKey = true; + break; + } + case 'c': + { + if (wifKey.length != 52) { + throw InvalidKeyException( + 'Compressed private keys have a length of 52 bytes'); + } + + _networkType = NetworkType.TEST; + _hasCompressedPubKey = true; + break; + } + default: + { + throw InvalidNetworkException( + 'Address WIF format must start with either [5] or [9]'); + } + } + + //strip first byte + var versionStripped = + versionAndDataBytes.sublist(1, versionAndDataBytes.length); + + if (versionStripped.length == 33) { + //drop last byte + //throw error if last byte is not 0x01 to indicate compression + if (versionStripped[32] != 0x01) { + throw InvalidKeyException( + "Compressed keys must have last byte set as 0x01. Yours is [${versionStripped[32]}]"); + } + + versionStripped = versionStripped.sublist(0, 32); + _hasCompressedPubKey = true; + } else { + _hasCompressedPubKey = false; + } + + var strippedHex = + HEX.encode(versionStripped.map((elem) => elem.toUnsigned(8)).toList()); + + var d = BigInt.parse(strippedHex, radix: 16); + + _ecPrivateKey = _privateKeyFromBigInt(d); + _d = d; + + _bchPublicKey = BCHPublicKey.fromPrivateKey(this); + } + + /// Returns this Private Key in WIF format. See [toWIF()]. + String toWIF() { + //convert private key _d to a hex string + var wifKey = _d.toRadixString(16); + + if (_networkType == NetworkType.MAIN) { + wifKey = HEX.encode([0x80]) + wifKey; + } else if (_networkType == NetworkType.TEST || + _networkType == NetworkType.REGTEST) { + wifKey = HEX.encode([0xef]) + wifKey; + } + + if (_hasCompressedPubKey) { + wifKey = wifKey + HEX.encode([0x01]); + } + + var shaWif = sha256Twice(HEX.decode(wifKey)); + var checksum = shaWif.sublist(0, 4); + + wifKey = wifKey + HEX.encode(checksum); + + var finalWif = bs58check.encode(HEX.decode(wifKey)); + + return utf8.decode(finalWif); + } + + /// Returns the *naked* private key Big Integer value as a hexadecimal string + String toHex() { + return _d.toRadixString(16); + } + + //convenience method to retrieve an address + /// Convenience method that jumps through the hoops of generating and [Address] from this + /// Private Key's corresponding [BCHPublicKey]. + BCHAddress toAddress({networkType = NetworkType.MAIN}) { + //FIXME: set network type to default parameter unless explicitly specified ? + return _bchPublicKey.toAddress(_networkType); + } + + Uint8List _seed() { + var random = Random.secure(); + var seed = List.generate(32, (_) => random.nextInt(256)); + return Uint8List.fromList(seed); + } + + ECPrivateKey _privateKeyFromBigInt(BigInt d) { + if (d == BigInt.zero) { + throw BadParameterException( + 'Zero is a bad value for a private key. Pick something else.'); + } + + return ECPrivateKey(d, _domainParams); + } + + /// Returns the Network Type that we intend to use this private key on. + /// This is also the value encoded in the WIF format representation of this key. + NetworkType get networkType { + return _networkType; + } + + /// Returns the *naked* private key Big Integer value as a Big Integer + BigInt get privateKey { + return _d; + } + + /// Returns the [BCHPublicKey] corresponding to this ECDSA private key. + /// + /// NOTE: `Q = d * G` where *Q* is the public key, *d* is the private key and `G` is the curve's Generator. + BCHPublicKey get publicKey { + return _bchPublicKey; + } + + /// Returns true if the corresponding public key for this private key + /// is in *compressed* format. To read more about compressed public keys see BCHPublicKey().getEncoded()] + bool get isCompressed { + return _hasCompressedPubKey; + } +} diff --git a/lib/src/publickey.dart b/lib/src/publickey.dart new file mode 100644 index 0000000..e061eba --- /dev/null +++ b/lib/src/publickey.dart @@ -0,0 +1,318 @@ +import 'package:bitbox/src/bchaddress.dart'; +import 'package:bitbox/src/networks.dart'; +import 'package:bitbox/src/privatekey.dart'; +import 'package:hex/hex.dart'; +import 'package:pointycastle/pointycastle.dart'; +import 'encoding/utils.dart'; +import 'exceptions.dart'; + +/// Manages an ECDSA public key. +/// +/// Bitcoin uses ECDSA for it's public/private key cryptography. +/// Specifically it uses the `secp256k1` elliptic curve. +/// +/// This class wraps cryptographic operations related to ECDSA from the +/// [PointyCastle](https://pub.dev/packages/pointycastle) library/package. +/// +/// You can read a good primer on Elliptic Curve Cryptography at [This Cloudflare blog post](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/) +/// +/// +class BCHPublicKey { + //We only deal with secp256k1 + final _domainParams = ECDomainParameters('secp256k1'); +// var _curve = ECCurve_secp256k1(); + + ECPoint _point; + +// ECPublicKey _publicKey; + + /// Creates a public key from it's corresponding ECDSA private key. + /// + /// NOTE: public key *Q* is computed as `Q = d * G` where *d* is the private key + /// and *G* is the elliptic curve Generator. + /// + /// [privkey] - The private key who's *d*-value we will use. + BCHPublicKey.fromPrivateKey(BCHPrivateKey privkey) { + var decodedPrivKey = encodeBigInt(privkey.privateKey); + var hexPrivKey = HEX.encode(decodedPrivKey); + + var actualKey = hexPrivKey; + var point = _domainParams.G * BigInt.parse(actualKey, radix: 16); + if (point.x == null && point.y == null) { + throw InvalidPointException( + 'Cannot generate point from private key. Private key greater than N ?'); + } + + //create a point taking into account compression request/indicator of parent private key + var finalPoint = _domainParams.curve.createPoint( + point.x.toBigInteger(), point.y.toBigInteger(), privkey.isCompressed); + + _checkIfOnCurve(finalPoint); // a bit paranoid + + _point = finalPoint; +// _publicKey = ECPublicKey((_point), _domainParams); + } + + /// Creates a public key instance from the ECDSA public key's `x-coordinate` + /// + /// ECDSA has some cool properties. Because we are dealing with an elliptic curve in a plane, + /// the public key *Q* has (x,y) cartesian coordinates. + /// It is possible to reconstruct the full public key from only it's `x-coordinate` + /// *IFF* one knows whether the Y-Value is *odd* or *even*. + /// + /// [xValue] - The Big Integer value of the `x-coordinate` in hexadecimal format + /// + /// [oddYValue] - *true* if the corresponding `y-coordinate` is even, *false* otherwise + BCHPublicKey.fromX(String xValue, bool oddYValue) { + _point = _getPointFromX(xValue, oddYValue); +// _publicKey = ECPublicKey((_point), _domainParams); + } + + /// Creates a public key from it's known *(x,y)* coordinates. + /// + /// [x] - X coordinate of the public key + /// + /// [y] - Y coordinate of the public key + /// + /// [compressed] = Specifies whether we will render this point in it's + /// compressed form by default with [toString()]. See [getEncoded()] to + /// learn more about compressed public keys. + BCHPublicKey.fromXY(BigInt x, BigInt y, {bool compressed = true}) { + //create a compressed point by default + var point = _domainParams.curve.createPoint(x, y, compressed); + + _checkIfOnCurve(point); + + _point = point; + +// _publicKey = ECPublicKey(_point, _domainParams); + } + + /// Reconstructs a public key from a DER-encoding. + /// + /// [buffer] - Byte array containing public key in DER format. + /// + /// [strict] - If *true* then we enforce strict DER encoding rules. Defaults to *true*. + BCHPublicKey.fromDER(List buffer, {bool strict = true}) { + if (buffer.isEmpty) { + throw BadParameterException('Empty compressed DER buffer'); + } + + _point = _transformDER(buffer, strict); + + if (_point.isInfinity) { + throw InvalidPointException( + 'That public key generates point at infinity'); + } + + if (_point.y.toBigInteger() == BigInt.zero) { + throw InvalidPointException('Invalid Y value for this public key'); + } + + _checkIfOnCurve(_point); + +// _publicKey = ECPublicKey(_point, _domainParams); + } + + /// Reconstruct a public key from the hexadecimal format of it's DER-encoding. + /// + /// [pubkey] - The DER-encoded public key as a hexadecimal string + /// + /// [strict] - If *true* then we enforce strict DER encoding rules. Defaults to *true*. + BCHPublicKey.fromHex(String pubkey, {bool strict = true}) { + if (pubkey.trim() == '') { + throw BadParameterException('Empty compressed public key string'); + } + +// _parseHexString(pubkey); + _point = _transformDER(HEX.decode(pubkey), strict); + + if (_point.isInfinity) { + throw InvalidPointException( + 'That public key generates point at infinity'); + } + + if (_point.y.toBigInteger() == BigInt.zero) { + throw InvalidPointException('Invalid Y value for this public key'); + } + + _checkIfOnCurve(_point); + +// _publicKey = ECPublicKey(_point, _domainParams); + } + + /// Validates that the DER-encoded hexadecimal string contains a valid + /// public key. + /// + /// [pubkey] - The DER-encoded public key as a hexadecimal string + /// + /// Returns *true* if the public key is valid, *false* otherwise. + static bool isValid(String pubkey) { + try { + BCHPublicKey.fromHex(pubkey); + } catch (err) { + return false; + } + + return true; + } + + /// Returns the *naked* public key value as either an (x,y) coordinate + /// or in a compact format using elliptic-curve point-compression. + /// + /// With EC point compression it is possible to reduce by half the + /// space occupied by a point, by taking advantage of a EC-curve property. + /// Specifically it is possible to recover the `y-coordinate` *IFF* the + /// `x-coordinate` is known *AND* we know whether the `y-coordinate` is + /// *odd* or *even*. + /// + /// [compressed] - If *true* the 'naked' public key value is returned in + /// compact format where the first byte is either 'odd' or 'even' followed + /// by the `x-coordinate`. If *false*, the full *(x,y)* coordinate pair will + /// be returned. + /// + /// NOTE: The first byte will contain either an odd number or an even number, + /// but this number is *NOT* a boolean flag. + String getEncoded(bool compressed) { + return HEX.encode(_point.getEncoded(compressed)); + } + + /// Returns the 'naked' public key value. Point compression is determined by + /// the default parameter in the constructor. If you want to enforce a specific preference + /// for the encoding, you can use the [getEncoded()] function instead. + @override + String toString() { + if (_point == null) { + return ''; + } + + return HEX.encode(_point.getEncoded(_point.isCompressed)); + } + + /// Convenience method that constructs an [Address] instance from this + /// public key. + BCHAddress toAddress(NetworkType nat) { + //generate compressed addresses by default + List buffer = _point.getEncoded(_point.isCompressed); + + if (_point.isCompressed) { + return BCHAddress.fromCompressedPubKey(buffer, nat); + } else { + return BCHAddress.fromHex(HEX.encode(buffer), nat); + } + } + + /// Alias for the [toString()] method. + String toHex() => toString(); + + ECPoint _transformDER(List buf, bool strict) { + BigInt x; + BigInt y; + List xbuf; + List ybuf; + ECPoint point; + + if (buf[0] == 0x04 || (!strict && (buf[0] == 0x06 || buf[0] == 0x07))) { + xbuf = buf.sublist(1, 33); + ybuf = buf.sublist(33, 65); + if (xbuf.length != 32 || ybuf.length != 32 || buf.length != 65) { + throw InvalidPointException('Length of x and y must be 32 bytes'); + } + x = BigInt.parse(HEX.encode(xbuf), radix: 16); + y = BigInt.parse(HEX.encode(ybuf), radix: 16); + + point = _domainParams.curve.createPoint(x, y); + } else if (buf[0] == 0x03 || buf[0] == 0x02) { + xbuf = buf.sublist(1); + x = BigInt.parse(HEX.encode(xbuf), radix: 16); + + var yTilde = buf[0] & 1; + point = _domainParams.curve.decompressPoint(yTilde, x); + } else { + throw InvalidPointException('Invalid DER format public key'); + } + return point; + } + + ECPoint _getPointFromX(String xValue, bool oddYValue) { + var prefixByte; + if (oddYValue) { + prefixByte = 0x03; + } else { + prefixByte = 0x02; + } + + var encoded = HEX.decode(xValue); + + var addressBytes = List(1 + encoded.length); + addressBytes[0] = prefixByte; + addressBytes.setRange(1, addressBytes.length, encoded); + + return _decodePoint(HEX.encode(addressBytes)); + } + + ECPoint _decodePoint(String pkHex) { + if (pkHex.trim() == '') { + throw BadParameterException('Empty compressed public key string'); + } + + var encoded = HEX.decode(pkHex); + try { + var point = _domainParams.curve.decodePoint(encoded); + + if (point.isCompressed && encoded.length != 33) { + throw BadParameterException( + "Compressed public keys must be 33 bytes long. Yours is [${encoded.length}]"); + } else if (!point.isCompressed && encoded.length != 65) { + throw BadParameterException( + "Uncompressed public keys must be 65 bytes long. Yours is [${encoded.length}]"); + } + + _checkIfOnCurve(point); + + return point; + } on ArgumentError catch (err) { + throw InvalidPointException(err.message); + } + } + + String _compressPoint(ECPoint point) { + return HEX.encode(point.getEncoded(true)); + } + + bool _checkIfOnCurve(ECPoint point) { + //a bit of math copied from PointyCastle. ecc/ecc_fp.dart -> decompressPoint() + var x = _domainParams.curve.fromBigInteger(point.x.toBigInteger()); + var alpha = (x * ((x * x) + _domainParams.curve.a)) + _domainParams.curve.b; + ECFieldElement beta = alpha.sqrt(); + + if (beta == null) { + throw InvalidPointException('This point is not on the curve'); + } + + //slight-of-hand. Create compressed point, reconstruct and check Y value. + var compressedPoint = _compressPoint(point); + var checkPoint = + _domainParams.curve.decodePoint(HEX.decode(compressedPoint)); + if (checkPoint.y.toBigInteger() != point.y.toBigInteger()) { + throw InvalidPointException('This point is not on the curve'); + } + + return (point.x.toBigInteger() == BigInt.zero) && + (point.y.toBigInteger() == BigInt.zero); + } + + /// Returns the (x,y) coordinates of this public key as an [ECPoint]. + /// The author dislikes leaking the wrapped PointyCastle implementation, but is too + /// lazy to write his own Point implementation. + ECPoint get point { + return _point; + } + + /// Returns *true* if this public key will render using EC point compression by + /// default when one calls the [toString()] or [toHex()] methods. + /// Returns *false* otherwise. + bool get isCompressed { + return _point.isCompressed; + } +} diff --git a/lib/src/utils/bip21.dart b/lib/src/utils/bip21.dart index aba3b50..93fbedd 100644 --- a/lib/src/utils/bip21.dart +++ b/lib/src/utils/bip21.dart @@ -1,8 +1,8 @@ class Bip21 { static Map decode(String uri) { - if (uri.indexOf('bitcoincash') != 0 || uri['bitcoincash'.length] != ":") { - if (uri.indexOf('bchtest') != 0) throw ("Invalid BIP21 URI"); - } + // if (uri.indexOf('bitcoincash') != 0 || uri['bitcoincash'.length] != ":") { + // if (uri.indexOf('bchtest') != 0) throw ("Invalid BIP21 URI"); + // } int split = uri.indexOf("?"); Map uriOptions = Uri.parse(uri).queryParameters; diff --git a/pubspec.lock b/pubspec.lock index 54f812e..3fed1c1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,40 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - archive: - dependency: transitive - description: - name: archive - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.11" - args: - dependency: transitive - description: - name: args - url: "https://pub.dartlang.org" - source: hosted - version: "1.5.2" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.4.1" bip32: dependency: "direct main" description: - name: bip32 - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: master + resolved-ref: fc0cbf2b7f297fbdd6b5d24f836a23d97c993e47 + url: "https://github.com/jbdtky/bip32-dart" + source: git version: "1.0.5" bip39: dependency: "direct main" description: - name: bip39 - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: master + resolved-ref: "15b0567ce928095d3bcd65a4113359c2e5353dc8" + url: "https://github.com/RomitRadical/bip39" + source: git version: "1.0.3" boolean_selector: dependency: transitive @@ -42,7 +32,7 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" bs58check: dependency: "direct main" description: @@ -50,20 +40,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + buffer: + dependency: "direct main" + description: + name: buffer + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.6" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" convert: dependency: transitive description: @@ -116,13 +113,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.3" - image: - dependency: transitive - description: - name: image - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.4" matcher: dependency: transitive description: @@ -151,27 +141,22 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0+1" - petitparser: - dependency: transitive - description: - name: petitparser - url: "https://pub.dartlang.org" - source: hosted - version: "2.4.0" pointycastle: dependency: "direct main" description: - name: pointycastle - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.1" + path: "." + ref: master + resolved-ref: "276ca77d6d7a3f93d0cfad019bb69da2b9a6324b" + url: "https://github.com/jbdtky/pc-dart.git" + source: git + version: "1.0.2" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.3" sky_engine: dependency: transitive description: flutter @@ -183,7 +168,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" stack_trace: dependency: transitive description: @@ -218,7 +203,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" typed_data: dependency: transitive description: @@ -233,12 +218,5 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" - xml: - dependency: transitive - description: - name: xml - url: "https://pub.dartlang.org" - source: hosted - version: "3.5.0" sdks: - dart: ">=2.4.0 <3.0.0" + dart: ">=2.6.0 <3.0.0" From 868ecc1db5568492080624d5aff810b9d04547d7 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 15 Apr 2020 15:27:39 +0530 Subject: [PATCH 027/106] updated dependency --- README.md | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 4a2184e..8e4e4bb 100644 --- a/README.md +++ b/README.md @@ -6,37 +6,32 @@ Works with mainnet and testnet. ## Getting Started ### 1) Depend on it -If you just want to get this from Dart's public package directory: ``` dependencies: - bitbox: ^0.0.1 + bitbox: + git: + url: https://github.com/RomitRadical/bitbox-flutter + ref: master ``` -If you checked this out from Github, add a local dependency into the pubspec.yaml of your testing or development projet: - -``` -dependencies: - bitbox_plugin: - path: / -``` - ### 2) Import it ``` -// There's a good chance your own project will use similar names as some of the -// classes in this library. A simple way to create some order is to import the +// There's a good chance your own project will use similar names as some of the +// classes in this library. A simple way to create some order is to import the // library with Bitbox prefix: -import 'package:bitbox/bitbox.dart' as Bitbox; +import 'package:bitbox/bitbox.dart' as bitbox; ``` ### 2) Use it + ``` // set this to true to use testnet final testnet = true; -// After running the code for the first time, depositing an amount to the address -// displayed in the console, and waiting for confirmation, paste the generated +// After running the code for the first time, depositing an amount to the address +// displayed in the console, and waiting for confirmation, paste the generated // mnemonic here, so the code continues below with address withdrawal String mnemonic = ""; @@ -93,7 +88,7 @@ if (addressDetails["balance"] > 0) { // placeholder for total input balance int totalBalance = 0; - // iterate through the list of address utxos and use them as inputs for the + // iterate through the list of address utxos and use them as inputs for the // withdrawal transaction utxos.forEach((Bitbox.Utxo utxo) { // add the utxo as an input for the transaction @@ -125,7 +120,7 @@ if (addressDetails["balance"] > 0) { // sign all inputs signatures.forEach((signature) { - builder.sign(signature["vin"], signature["key_pair"], + builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); }); @@ -147,8 +142,9 @@ For further documentation, refer to apidoc of this repository ## Testing -There are some unit tests in test/bitbox_test.dart. They use data generated from the original [Bitbox for JS](https://developer.bitcoin.com/bitbox/) and compare them with the output of this library. +There are some unit tests in test/bitbox_test.dart. They use data generated from the original [Bitbox for JS](https://developer.bitcoin.com/bitbox/) and compare them with the output of this library. The following is tested for both testnet and mainnet: + - Generating the master node from mnemonic and comparing both its XPub and XPriv - Generating an account node and comparing XPub and XPriv - Generating 10 test childs and comparing their private keys and addresses @@ -163,9 +159,8 @@ To run the test: 1. Copy create_test_data.js to a separate directory and download the original Bitbox JS into the directory 2. Generate the testing data by runing create_test_data.js with your local nodeJS engine 3. Update bitbox_test.dart with the path to the generated test_data.json file -4. Run bitbox_test.dart -Optionally between step 1) and 2), send some balance to either testnet or mainnet addresses (or both), wait for confirmations and run create_test_data.js again to update the data and generate testing transactions - +4. Run bitbox_test.dart + Optionally between step 1) and 2), send some balance to either testnet or mainnet addresses (or both), wait for confirmations and run create_test_data.js again to update the data and generate testing transactions ## Acknowledgments From 20d8524856bdd88448025419e6cd65f4af9c8ede Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 18 Apr 2020 11:27:41 +0530 Subject: [PATCH 028/106] export opcodes --- lib/bitbox.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bitbox.dart b/lib/bitbox.dart index dddef25..af1e899 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -18,3 +18,4 @@ export 'src/rawtransactions.dart'; export 'src/transaction.dart'; export 'src/transactionbuilder.dart'; export 'src/varuint.dart'; +export 'src/utils/opcodes.dart'; From abaa67394f12932d7dbb0c099a7e4ee6829a6584 Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 18 Apr 2020 11:43:45 +0530 Subject: [PATCH 029/106] export script --- example/main.dart | 9 ++++++--- lib/bitbox.dart | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/example/main.dart b/example/main.dart index cdcc5f7..3374226 100644 --- a/example/main.dart +++ b/example/main.dart @@ -7,7 +7,8 @@ void main() async { // After running the code for the first time, depositing an amount to the address displayed in the console, // and waiting for confirmation, paste the generated mnemonic here, // so the code continues below with address withdrawal - String mnemonic = "leaf tackle snap liar core motion material live camp quote mercy void"; + String mnemonic = + "leaf tackle snap liar core motion material live camp quote mercy void"; if (mnemonic == "") { // generate 12-word (128bit) mnemonic @@ -78,7 +79,8 @@ void main() async { }); // set an address to send the remaining balance to - final outputAddress = "bitcoincash:qq4vzza5uhgr42ntkl28x67qzda4af5hpgap6z0ntx"; + final outputAddress = + "bitcoincash:qq4vzza5uhgr42ntkl28x67qzda4af5hpgap6z0ntx"; // if there is an unspent balance, create a spending transaction if (totalBalance > 0 && outputAddress != "") { @@ -93,7 +95,8 @@ void main() async { // sign all inputs signatures.forEach((signature) { - builder.sign(signature["vin"], signature["key_pair"], signature["original_amount"]); + builder.sign(signature["vin"], signature["key_pair"], + signature["original_amount"]); }); // build the transaction diff --git a/lib/bitbox.dart b/lib/bitbox.dart index af1e899..32a4c0d 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -19,3 +19,4 @@ export 'src/transaction.dart'; export 'src/transactionbuilder.dart'; export 'src/varuint.dart'; export 'src/utils/opcodes.dart'; +export 'src/utils/script.dart'; From c07126e01c701fd0c8b70d6adf99b3b1ccd7570e Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 22 Apr 2020 11:27:21 +0530 Subject: [PATCH 030/106] added slp parser & slp mdm dep --- pubspec.lock | 37 +++++++++++++++++++++++++++++-------- pubspec.yaml | 2 ++ 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 3fed1c1..5ea780c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -54,6 +54,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.3" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" collection: dependency: transitive description: @@ -75,6 +82,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" fixnum: dependency: "direct main" description: @@ -133,7 +147,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.7.0" pedantic: dependency: transitive description: @@ -150,18 +164,25 @@ packages: url: "https://github.com/jbdtky/pc-dart.git" source: git version: "1.0.2" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + slp_mdm: + dependency: "direct main" + description: + name: slp_mdm + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1" + slp_parser: + dependency: "direct main" + description: + name: slp_parser + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.3" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 839c3e4..e5e0ffd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,8 @@ dependencies: fixnum: ^0.10.9 meta: ^1.1.6 buffer: ^1.0.6 + slp_parser: ^0.0.3 + slp_mdm: ^0.0.1 dev_dependencies: flutter_test: From 2a7cc3b3550c44d938f9dd4d7976ed64bdff558a Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 22 Apr 2020 11:27:40 +0530 Subject: [PATCH 031/106] added send slp feature --- lib/src/slp.dart | 403 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 403 insertions(+) create mode 100644 lib/src/slp.dart diff --git a/lib/src/slp.dart b/lib/src/slp.dart new file mode 100644 index 0000000..6dd5475 --- /dev/null +++ b/lib/src/slp.dart @@ -0,0 +1,403 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:bitbox/bitbox.dart'; +import 'package:hex/hex.dart'; +import 'package:http/http.dart'; +import 'package:slp_mdm/slp_mdm.dart'; +import 'package:slp_parser/slp_parser.dart'; +import 'dart:math' as math; + +class SLP { + getTokenInformation(String tokenID, [bool decimalConversion = false]) async { + Response response; + var res; + try { + response = + await RawTransactions.getRawtransaction(tokenID, verbose: true); + res = jsonDecode(response.body); + if (res.containsKey('error')) { + throw Exception( + "BITBOX response error for 'RawTransactions.getRawTransaction'"); + } + } catch (e) { + throw Exception(e); + } + var slpMsg = + parseSLP(HEX.decode(res['vout'][0]['scriptPubKey']['hex'])).toMap(); + if (decimalConversion) { + Map slpMsgData = slpMsg['data']; + + if (slpMsg['transactionType'] == "GENESIS" || + slpMsg['transactionType'] == "MINT") { + slpMsgData['qty'] = + slpMsgData['qty'] / math.pow(10, slpMsgData['decimals']); + } else { + slpMsgData['amounts'] + .map((o) => o / math.pow(10, slpMsgData['decimals'])); + } + } + + if (slpMsg['transactionType'] == "GENESIS") { + slpMsg['tokenIdHex'] = tokenID; + } + return slpMsg; + } + + getUtxos(String address) async { + // must be a cash addr + var res; + try { + Address.toLegacyAddress(address); + } catch (_) { + throw new Exception( + "Not an a valid address format, must be cashAddr or Legacy address format."); + } + res = await Address.utxo(address) as List; + return res; + } + + mapToSLPUtxoArray(List utxos, String xpriv) { + List utxo = []; + utxos.forEach((txo) => utxo.add({ + 'satoshis': new BigInt.from(txo['satoshis']), + 'xpriv': xpriv, + 'txid': txo['txid'], + 'vout': txo['vout'], + 'slpTransactionDetails': txo['slpTransactionDetails'], + 'slpUtxoJudgement': txo['slpUtxoJudgement'], + 'slpUtxoJudgementAmount': txo['slpUtxoJudgementAmount'], + })); + return utxo; + } + + simpleTokenSend( + {String tokenId, + double sendAmount, + List inputUtxos, + List tokenReceiverAddresseses, + String changeReceiverAddress, + List requiredNonTokenOutputs, + int extraFee = 0, + int type = 0x01}) async { + BigInt amount; + if (tokenId is! String) { + return Exception("Token id should be a String"); + } + tokenReceiverAddresseses.forEach((addr) { + if (addr is! String) { + throw new Exception("Token id should be a String"); + } + }); + try { + if (sendAmount > 0) { + amount = BigInt.from(sendAmount); + } + } catch (e) { + return Exception("Invalid amount"); + } + + // 1 Set the token send amounts, we'll send 100 tokens to a + // new receiver and send token change back to the sender + BigInt totalTokenInputAmount = BigInt.from(0); + inputUtxos.forEach((txo) => + totalTokenInputAmount += preSendSlpJudgementCheck(txo, tokenId)); + + // 2 Compute the token Change amount. + BigInt tokenChangeAmount = totalTokenInputAmount - amount; + + String txHex; + if (tokenChangeAmount > new BigInt.from(0)) { + // 3 Create the Send OP_RETURN message + var sendOpReturn = Send(HEX.decode(tokenId), [amount, tokenChangeAmount]); + // 4 Create the raw Send transaction hex + txHex = await buildRawSendTx( + slpSendOpReturn: sendOpReturn, + inputTokenUtxos: inputUtxos, + tokenReceiverAddress: tokenReceiverAddresseses, + bchChangeReceiverAddress: changeReceiverAddress, + requiredNonTokenOutputs: requiredNonTokenOutputs, + extraFee: extraFee); + } else if (tokenChangeAmount == new BigInt.from(0)) { + // 3 Create the Send OP_RETURN message + var sendOpReturn = Send(HEX.decode(tokenId), [amount]); + // 4 Create the raw Send transaction hex + txHex = await buildRawSendTx( + slpSendOpReturn: sendOpReturn, + inputTokenUtxos: inputUtxos, + tokenReceiverAddress: tokenReceiverAddresseses, + bchChangeReceiverAddress: null, + requiredNonTokenOutputs: requiredNonTokenOutputs, + extraFee: extraFee); + } else { + throw Exception('Token inputs less than the token outputs'); + } + // Return raw hex for this transaction + return txHex; + } + + BigInt preSendSlpJudgementCheck(Map txo, tokenID) { + if (txo['slpUtxoJudgement'] == "undefined" || + txo['slpUtxoJudgement'] == null || + txo['slpUtxoJudgement'] == "UNKNOWN") { + throw Exception( + "There is at least one input UTXO that does not have a proper SLP judgement"); + } + if (txo['slpUtxoJudgement'] == "UNSUPPORTED_TYPE") { + throw Exception( + "There is at least one input UTXO that is an Unsupported SLP type."); + } + if (txo['slpUtxoJudgement'] == "SLP_BATON") { + throw Exception( + "There is at least one input UTXO that is a baton. You can only spend batons in a MINT transaction."); + } + if (txo.containsKey('slpTransactionDetails')) { + if (txo['slpUtxoJudgement'] == "SLP_TOKEN") { + if (!txo.containsKey('slpUtxoJudgementAmount')) { + throw Exception( + "There is at least one input token that does not have the 'slpUtxoJudgementAmount' property set."); + } + if (txo['slpTransactionDetails']['tokenIdHex'] != tokenID) { + throw Exception( + "There is at least one input UTXO that is a different SLP token than the one specified."); + } + if (txo['slpTransactionDetails']['tokenIdHex'] == tokenID) { + return BigInt.from(double.parse(txo['slpUtxoJudgementAmount'])); + } + } + } + return BigInt.from(0); + } + + buildRawSendTx( + {List slpSendOpReturn, + List inputTokenUtxos, + List tokenReceiverAddress, + String bchChangeReceiverAddress, + List requiredNonTokenOutputs, + int extraFee, + type = 0x01}) async { + // Check proper address formats are given + tokenReceiverAddress.forEach((addr) { + if (!addr.startsWith('simpleledger:')) { + throw new Exception("Token receiver address not in SlpAddr format."); + } + }); + + if (bchChangeReceiverAddress != null) { + if (!bchChangeReceiverAddress.startsWith('simpleledger:')) { + throw new Exception( + "Token/BCH change receiver address is not in SLP format."); + } + } + + // Parse the SLP SEND OP_RETURN message + var sendMsg = parseSLP(slpSendOpReturn).toMap(); + Map sendMsgData = sendMsg['data']; + + // Make sure we're not spending inputs from any other token or baton + var tokenInputQty = new BigInt.from(0); + inputTokenUtxos.forEach((txo) { + if (txo['slpUtxoJudgement'] == "NOT_SLP") { + return; + } + if (txo['slpUtxoJudgement'] == "SLP_TOKEN") { + if (txo['slpTransactionDetails']['tokenIdHex'] != + sendMsgData['tokenId']) { + throw Exception("Input UTXOs included a token for another tokenId."); + } + tokenInputQty += + BigInt.from(double.parse(txo['slpUtxoJudgementAmount'])); + return; + } + if (txo['slpUtxoJudgement'] == "SLP_BATON") { + throw Exception("Cannot spend a minting baton."); + } + if (txo['slpUtxoJudgement'] == ['INVALID_TOKEN_DAG'] || + txo['slpUtxoJudgement'] == "INVALID_BATON_DAG") { + throw Exception("Cannot currently spend UTXOs with invalid DAGs."); + } + throw Exception("Cannot spend utxo with no SLP judgement."); + }); + + // Make sure the number of output receivers + // matches the outputs in the OP_RETURN message. + var chgAddr = bchChangeReceiverAddress == null ? 0 : 1; + if (!sendMsgData.containsKey('amounts')) { + throw Exception("OP_RETURN contains no SLP send outputs."); + } + if (tokenReceiverAddress.length + chgAddr != + sendMsgData['amounts'].length) { + throw Exception( + "Number of token receivers in config does not match the OP_RETURN outputs"); + } + + // Make sure token inputs == token outputs + var outputTokenQty = BigInt.from(0); + sendMsgData['amounts'].forEach((a) => outputTokenQty += a); + if (tokenInputQty != outputTokenQty) { + throw Exception("Token input quantity does not match token outputs."); + } + + // Create a transaction builder + var transactionBuilder = Bitbox.transactionBuilder(); + // let sequence = 0xffffffff - 1; + + // Calculate the total input amount & add all inputs to the transaction + + var inputSatoshis = BigInt.from(0); + inputTokenUtxos.forEach((i) { + inputSatoshis += i['satoshis']; + transactionBuilder.addInput(i['txid'], i['vout']); + }); + + // Calculate the amount of outputs set aside for special BCH-only outputs for fee calculation + var bchOnlyCount = + requiredNonTokenOutputs != null ? requiredNonTokenOutputs.length : 0; + BigInt bcOnlyOutputSatoshis = BigInt.from(0); + requiredNonTokenOutputs != null + ? requiredNonTokenOutputs + .forEach((o) => bcOnlyOutputSatoshis += BigInt.from(o['satoshis'])) + : bcOnlyOutputSatoshis = bcOnlyOutputSatoshis; + + // Calculate mining fee cost + int sendCost = calculateSendCost(slpSendOpReturn.length, + inputTokenUtxos.length, tokenReceiverAddress.length + bchOnlyCount, + bchChangeAddress: bchChangeReceiverAddress, + feeRate: extraFee != null ? extraFee : 0); + + // Compute BCH change amount + BigInt bchChangeAfterFeeSatoshis = + inputSatoshis - BigInt.from(sendCost) - bcOnlyOutputSatoshis; + + // Start adding outputs to transaction + // Add SLP SEND OP_RETURN message + transactionBuilder.addOutput(compile(slpSendOpReturn), 0); + + // Add dust outputs associated with tokens + tokenReceiverAddress.forEach((outputAddress) { + outputAddress = Address.toLegacyAddress(outputAddress); + outputAddress = Address.toCashAddress(outputAddress); + transactionBuilder.addOutput(outputAddress, 546); + }); + + // Add BCH-only outputs + var outputAddress; + if (requiredNonTokenOutputs != null) { + if (requiredNonTokenOutputs.length > 0) { + requiredNonTokenOutputs.forEach((output) { + outputAddress = Address.toLegacyAddress(output.receiverAddress); + outputAddress = Address.toCashAddress(outputAddress); + transactionBuilder.addOutput(outputAddress, output.satoshis); + }); + } + } + + // Add change, if any + if (bchChangeAfterFeeSatoshis > new BigInt.from(546)) { + bchChangeReceiverAddress = + Address.toLegacyAddress(bchChangeReceiverAddress); + bchChangeReceiverAddress = + Address.toCashAddress(bchChangeReceiverAddress); + transactionBuilder.addOutput( + bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt()); + } + + // Sign txn and add sig to p2pkh input for convenience if wif is provided, + // otherwise skip signing. + var inp = 0; + inputTokenUtxos.forEach((i) { + if (!i.containsKey('xpriv')) { + return throw Exception("Input doesnt contain a xpriv"); + } + ECPair paymentKeyPair = HDNode.fromXPriv(i['xpriv']).keyPair; + transactionBuilder.sign( + inp, paymentKeyPair, i['satoshis'].toInt(), Transaction.SIGHASH_ALL); + inp++; + }); + + // Build the transaction to hex and return + // warn user if the transaction was not fully signed + String hex = transactionBuilder.build().toHex(); + // Check For Low Fee + int outValue = 0; + transactionBuilder.tx.outputs.forEach((o) => outValue += o.value); + int inValue = 0; + inputTokenUtxos.forEach((i) => inValue += i['satoshis'].toInt()); + if (inValue - outValue < hex.length / 2) { + throw Exception( + "Transaction input BCH amount is too low. Add more BCH inputs to fund this transaction."); + } + var txid = await RawTransactions.sendRawTransaction(hex); + return txid; + } + + int calculateSendCost(int sendOpReturnLength, int inputUtxoSize, int outputs, + {String bchChangeAddress, int feeRate = 1, bool forTokens = true}) { + int nonfeeoutputs = 0; + if (forTokens) { + nonfeeoutputs = outputs * 546; + } + if (bchChangeAddress != null && bchChangeAddress != 'undefined') { + outputs += 1; + } + + int fee = BitcoinCash.getByteCount(inputUtxoSize, outputs); + fee += sendOpReturnLength; + fee += 10; // added to account for OP_RETURN ammount of 0000000000000000 + fee *= feeRate; + //print("SEND cost before outputs: " + fee.toString()); + fee += nonfeeoutputs; + //print("SEND cost after outputs are added: " + fee.toString()); + return fee; + } + + /* + Todo + */ + + createSimpleToken( + {String tokenName, + String tokenTicker, + int tokenAmount, + String documentUri, + Uint8List documentHash, + int decimals, + String tokenReceiverAddress, + String batonReceiverAddress, + String bchChangeReceiverAddress, + List inputUtxos, + int type = 0x01}) async { + int batonVout = batonReceiverAddress.isNotEmpty ? 2 : null; + if (decimals == null) { + throw Exception("Decimals property must be in range 0 to 9"); + } + if (tokenTicker != null && tokenTicker is! String) { + throw Exception("ticker must be a string"); + } + if (tokenName != null && tokenName is! String) { + throw Exception("name must be a string"); + } + + var genesisOpReturn = Genesis(tokenTicker, tokenName, documentUri, + documentHash, decimals, BigInt.from(batonVout), tokenAmount); + if (genesisOpReturn.length > 223) { + throw Exception( + "Script too long, must be less than or equal to 223 bytes."); + } + return genesisOpReturn; + + // var genesisTxHex = buildRawGenesisTx({ + // slpGenesisOpReturn: genesisOpReturn, + // mintReceiverAddress: tokenReceiverAddress, + // batonReceiverAddress: batonReceiverAddress, + // bchChangeReceiverAddress: bchChangeReceiverAddress, + // input_utxos: Utils.mapToUtxoArray(inputUtxos) + // }); + + // return await RawTransactions.sendRawTransaction(genesisTxHex); + } + + //simpleTokenMint() {} + + //simpleTokenBurn() {} +} From 519c7a52ff656bd3561601e664d297cf0c9df02f Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 22 Apr 2020 11:36:03 +0530 Subject: [PATCH 032/106] export slp class --- lib/bitbox.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bitbox.dart b/lib/bitbox.dart index 32a4c0d..99aeb7e 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -14,6 +14,7 @@ export 'src/mnemonic.dart'; export 'src/networks.dart'; export 'src/privatekey.dart'; export 'src/publickey.dart'; +export 'src/slp.dart'; export 'src/rawtransactions.dart'; export 'src/transaction.dart'; export 'src/transactionbuilder.dart'; From 1b4c68707676c37d836de2e3c61cfab1c0501d1e Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 11:11:20 +0530 Subject: [PATCH 033/106] changed token receiver address to string --- lib/src/slp.dart | 88 ++++++++++++++++++------------------------------ 1 file changed, 32 insertions(+), 56 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 6dd5475..155cdf3 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -43,19 +43,6 @@ class SLP { return slpMsg; } - getUtxos(String address) async { - // must be a cash addr - var res; - try { - Address.toLegacyAddress(address); - } catch (_) { - throw new Exception( - "Not an a valid address format, must be cashAddr or Legacy address format."); - } - res = await Address.utxo(address) as List; - return res; - } - mapToSLPUtxoArray(List utxos, String xpriv) { List utxo = []; utxos.forEach((txo) => utxo.add({ @@ -74,7 +61,7 @@ class SLP { {String tokenId, double sendAmount, List inputUtxos, - List tokenReceiverAddresseses, + String tokenReceiverAddress, String changeReceiverAddress, List requiredNonTokenOutputs, int extraFee = 0, @@ -83,11 +70,9 @@ class SLP { if (tokenId is! String) { return Exception("Token id should be a String"); } - tokenReceiverAddresseses.forEach((addr) { - if (addr is! String) { - throw new Exception("Token id should be a String"); - } - }); + if (tokenReceiverAddress is! String) { + throw new Exception("Token address should be a String"); + } try { if (sendAmount > 0) { amount = BigInt.from(sendAmount); @@ -100,42 +85,34 @@ class SLP { // new receiver and send token change back to the sender BigInt totalTokenInputAmount = BigInt.from(0); inputUtxos.forEach((txo) => - totalTokenInputAmount += preSendSlpJudgementCheck(txo, tokenId)); + totalTokenInputAmount += _preSendSlpJudgementCheck(txo, tokenId)); // 2 Compute the token Change amount. BigInt tokenChangeAmount = totalTokenInputAmount - amount; + bool sendChange = tokenChangeAmount > new BigInt.from(0); String txHex; - if (tokenChangeAmount > new BigInt.from(0)) { - // 3 Create the Send OP_RETURN message - var sendOpReturn = Send(HEX.decode(tokenId), [amount, tokenChangeAmount]); - // 4 Create the raw Send transaction hex - txHex = await buildRawSendTx( - slpSendOpReturn: sendOpReturn, - inputTokenUtxos: inputUtxos, - tokenReceiverAddress: tokenReceiverAddresseses, - bchChangeReceiverAddress: changeReceiverAddress, - requiredNonTokenOutputs: requiredNonTokenOutputs, - extraFee: extraFee); - } else if (tokenChangeAmount == new BigInt.from(0)) { - // 3 Create the Send OP_RETURN message - var sendOpReturn = Send(HEX.decode(tokenId), [amount]); - // 4 Create the raw Send transaction hex - txHex = await buildRawSendTx( - slpSendOpReturn: sendOpReturn, - inputTokenUtxos: inputUtxos, - tokenReceiverAddress: tokenReceiverAddresseses, - bchChangeReceiverAddress: null, - requiredNonTokenOutputs: requiredNonTokenOutputs, - extraFee: extraFee); - } else { - throw Exception('Token inputs less than the token outputs'); + if (tokenChangeAmount < new BigInt.from(0)) { + return throw Exception('Token inputs less than the token outputs'); } + // 3 Create the Send OP_RETURN message + var sendOpReturn = Send(HEX.decode(tokenId), [amount, tokenChangeAmount]); + // 4 Create the raw Send transaction hex + txHex = await _buildRawSendTx( + slpSendOpReturn: sendOpReturn, + inputTokenUtxos: inputUtxos, + tokenReceiverAddresses: sendChange + ? [tokenReceiverAddress, changeReceiverAddress] + : [tokenReceiverAddress], + bchChangeReceiverAddress: changeReceiverAddress, + requiredNonTokenOutputs: requiredNonTokenOutputs, + extraFee: extraFee); + // Return raw hex for this transaction return txHex; } - BigInt preSendSlpJudgementCheck(Map txo, tokenID) { + BigInt _preSendSlpJudgementCheck(Map txo, tokenID) { if (txo['slpUtxoJudgement'] == "undefined" || txo['slpUtxoJudgement'] == null || txo['slpUtxoJudgement'] == "UNKNOWN") { @@ -168,16 +145,16 @@ class SLP { return BigInt.from(0); } - buildRawSendTx( + _buildRawSendTx( {List slpSendOpReturn, List inputTokenUtxos, - List tokenReceiverAddress, + List tokenReceiverAddresses, String bchChangeReceiverAddress, List requiredNonTokenOutputs, int extraFee, type = 0x01}) async { // Check proper address formats are given - tokenReceiverAddress.forEach((addr) { + tokenReceiverAddresses.forEach((addr) { if (!addr.startsWith('simpleledger:')) { throw new Exception("Token receiver address not in SlpAddr format."); } @@ -225,7 +202,7 @@ class SLP { if (!sendMsgData.containsKey('amounts')) { throw Exception("OP_RETURN contains no SLP send outputs."); } - if (tokenReceiverAddress.length + chgAddr != + if (tokenReceiverAddresses.length + chgAddr != sendMsgData['amounts'].length) { throw Exception( "Number of token receivers in config does not match the OP_RETURN outputs"); @@ -260,8 +237,8 @@ class SLP { : bcOnlyOutputSatoshis = bcOnlyOutputSatoshis; // Calculate mining fee cost - int sendCost = calculateSendCost(slpSendOpReturn.length, - inputTokenUtxos.length, tokenReceiverAddress.length + bchOnlyCount, + int sendCost = _calculateSendCost(slpSendOpReturn.length, + inputTokenUtxos.length, tokenReceiverAddresses.length + bchOnlyCount, bchChangeAddress: bchChangeReceiverAddress, feeRate: extraFee != null ? extraFee : 0); @@ -274,7 +251,7 @@ class SLP { transactionBuilder.addOutput(compile(slpSendOpReturn), 0); // Add dust outputs associated with tokens - tokenReceiverAddress.forEach((outputAddress) { + tokenReceiverAddresses.forEach((outputAddress) { outputAddress = Address.toLegacyAddress(outputAddress); outputAddress = Address.toCashAddress(outputAddress); transactionBuilder.addOutput(outputAddress, 546); @@ -327,11 +304,10 @@ class SLP { throw Exception( "Transaction input BCH amount is too low. Add more BCH inputs to fund this transaction."); } - var txid = await RawTransactions.sendRawTransaction(hex); - return txid; + return hex; } - int calculateSendCost(int sendOpReturnLength, int inputUtxoSize, int outputs, + int _calculateSendCost(int sendOpReturnLength, int inputUtxoSize, int outputs, {String bchChangeAddress, int feeRate = 1, bool forTokens = true}) { int nonfeeoutputs = 0; if (forTokens) { @@ -355,7 +331,7 @@ class SLP { Todo */ - createSimpleToken( + _createSimpleToken( {String tokenName, String tokenTicker, int tokenAmount, From 5e01a6c4cc67beb5a805a3f0170da4cdeb76c11e Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 11:15:17 +0530 Subject: [PATCH 034/106] changed bch change addr format --- lib/src/slp.dart | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 155cdf3..8f4d83d 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -161,9 +161,9 @@ class SLP { }); if (bchChangeReceiverAddress != null) { - if (!bchChangeReceiverAddress.startsWith('simpleledger:')) { + if (!bchChangeReceiverAddress.startsWith('bitcoincash:')) { throw new Exception( - "Token/BCH change receiver address is not in SLP format."); + "BCH change receiver address is not in Cashaddr format."); } } @@ -198,12 +198,7 @@ class SLP { // Make sure the number of output receivers // matches the outputs in the OP_RETURN message. - var chgAddr = bchChangeReceiverAddress == null ? 0 : 1; - if (!sendMsgData.containsKey('amounts')) { - throw Exception("OP_RETURN contains no SLP send outputs."); - } - if (tokenReceiverAddresses.length + chgAddr != - sendMsgData['amounts'].length) { + if (tokenReceiverAddresses.length != sendMsgData['amounts'].length) { throw Exception( "Number of token receivers in config does not match the OP_RETURN outputs"); } @@ -230,11 +225,11 @@ class SLP { // Calculate the amount of outputs set aside for special BCH-only outputs for fee calculation var bchOnlyCount = requiredNonTokenOutputs != null ? requiredNonTokenOutputs.length : 0; - BigInt bcOnlyOutputSatoshis = BigInt.from(0); + BigInt bchOnlyOutputSatoshis = BigInt.from(0); requiredNonTokenOutputs != null ? requiredNonTokenOutputs - .forEach((o) => bcOnlyOutputSatoshis += BigInt.from(o['satoshis'])) - : bcOnlyOutputSatoshis = bcOnlyOutputSatoshis; + .forEach((o) => bchOnlyOutputSatoshis += BigInt.from(o['satoshis'])) + : bchOnlyOutputSatoshis = bchOnlyOutputSatoshis; // Calculate mining fee cost int sendCost = _calculateSendCost(slpSendOpReturn.length, @@ -244,7 +239,7 @@ class SLP { // Compute BCH change amount BigInt bchChangeAfterFeeSatoshis = - inputSatoshis - BigInt.from(sendCost) - bcOnlyOutputSatoshis; + inputSatoshis - BigInt.from(sendCost) - bchOnlyOutputSatoshis; // Start adding outputs to transaction // Add SLP SEND OP_RETURN message @@ -271,10 +266,6 @@ class SLP { // Add change, if any if (bchChangeAfterFeeSatoshis > new BigInt.from(546)) { - bchChangeReceiverAddress = - Address.toLegacyAddress(bchChangeReceiverAddress); - bchChangeReceiverAddress = - Address.toCashAddress(bchChangeReceiverAddress); transactionBuilder.addOutput( bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt()); } From 8f1d956f5eceba82b705b92a4aa79c4ced24fb7d Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 11:51:20 +0530 Subject: [PATCH 035/106] change bch change receiver format --- lib/src/slp.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 8f4d83d..4cd8385 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -161,9 +161,9 @@ class SLP { }); if (bchChangeReceiverAddress != null) { - if (!bchChangeReceiverAddress.startsWith('bitcoincash:')) { + if (!bchChangeReceiverAddress.startsWith('simpleledger:')) { throw new Exception( - "BCH change receiver address is not in Cashaddr format."); + "BCH/SLP token change receiver address is not in SlpAddr format."); } } From db146d014f3c0307e3bb92e7b022efac2689de1f Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 11:52:40 +0530 Subject: [PATCH 036/106] convert slp to bch addr for output change --- lib/src/slp.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 4cd8385..e7d7229 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -266,8 +266,9 @@ class SLP { // Add change, if any if (bchChangeAfterFeeSatoshis > new BigInt.from(546)) { - transactionBuilder.addOutput( - bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt()); + var legacyaddr = Address.toLegacyAddress(bchChangeReceiverAddress); + var cashaddr = Address.toCashAddress(legacyaddr); + transactionBuilder.addOutput(cashaddr, bchChangeAfterFeeSatoshis.toInt()); } // Sign txn and add sig to p2pkh input for convenience if wif is provided, From 4c7aeafb54c4beb40f7141b9d93f1c728dca7be8 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 13:55:53 +0530 Subject: [PATCH 037/106] changed default extra fee rate to 1 --- lib/src/slp.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index e7d7229..92dee0e 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -235,7 +235,7 @@ class SLP { int sendCost = _calculateSendCost(slpSendOpReturn.length, inputTokenUtxos.length, tokenReceiverAddresses.length + bchOnlyCount, bchChangeAddress: bchChangeReceiverAddress, - feeRate: extraFee != null ? extraFee : 0); + feeRate: extraFee != null ? extraFee : 1); // Compute BCH change amount BigInt bchChangeAfterFeeSatoshis = From 7c52cb734eb34b6ef5dcee9a1bdf34be0dceb7b0 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 14:06:26 +0530 Subject: [PATCH 038/106] updated comments --- lib/src/slp.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 92dee0e..ffcae47 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -81,8 +81,8 @@ class SLP { return Exception("Invalid amount"); } - // 1 Set the token send amounts, we'll send 100 tokens to a - // new receiver and send token change back to the sender + // 1 Set the token send amounts, send tokens to a + // new receiver and send token change back to the sender BigInt totalTokenInputAmount = BigInt.from(0); inputUtxos.forEach((txo) => totalTokenInputAmount += _preSendSlpJudgementCheck(txo, tokenId)); @@ -271,8 +271,7 @@ class SLP { transactionBuilder.addOutput(cashaddr, bchChangeAfterFeeSatoshis.toInt()); } - // Sign txn and add sig to p2pkh input for convenience if wif is provided, - // otherwise skip signing. + // Sign txn and add sig to p2pkh input with xpriv, var inp = 0; inputTokenUtxos.forEach((i) { if (!i.containsKey('xpriv')) { @@ -287,12 +286,16 @@ class SLP { // Build the transaction to hex and return // warn user if the transaction was not fully signed String hex = transactionBuilder.build().toHex(); + // Check For Low Fee int outValue = 0; transactionBuilder.tx.outputs.forEach((o) => outValue += o.value); int inValue = 0; inputTokenUtxos.forEach((i) => inValue += i['satoshis'].toInt()); if (inValue - outValue < hex.length / 2) { + print('inValue: $inValue'); + print('outValue: $outValue'); + print('hex: $hex'); throw Exception( "Transaction input BCH amount is too low. Add more BCH inputs to fund this transaction."); } From fc723bfa87b54ddcb28e03b6e6134f8962f5101b Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 15:42:01 +0530 Subject: [PATCH 039/106] removed default extra fee value --- lib/src/slp.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index ffcae47..f573fcf 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -64,7 +64,7 @@ class SLP { String tokenReceiverAddress, String changeReceiverAddress, List requiredNonTokenOutputs, - int extraFee = 0, + int extraFee, int type = 0x01}) async { BigInt amount; if (tokenId is! String) { From f867c8445cbc753e1c2cafaa4728e8a43257e0a7 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 23 Apr 2020 16:06:40 +0530 Subject: [PATCH 040/106] if token change is lesser than 0 then add just 1 output amount --- lib/src/slp.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index f573fcf..7180e58 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -96,7 +96,11 @@ class SLP { return throw Exception('Token inputs less than the token outputs'); } // 3 Create the Send OP_RETURN message - var sendOpReturn = Send(HEX.decode(tokenId), [amount, tokenChangeAmount]); + var sendOpReturn = Send( + HEX.decode(tokenId), + tokenChangeAmount > BigInt.from(0) + ? [amount, tokenChangeAmount] + : [amount]); // 4 Create the raw Send transaction hex txHex = await _buildRawSendTx( slpSendOpReturn: sendOpReturn, From 5f3282b8b9a6932e43abcba21bf78ce13e3ba72d Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 24 Apr 2020 12:30:11 +0530 Subject: [PATCH 041/106] add inputs from a different bch only address to pay token tx fees --- lib/src/slp.dart | 97 +++++++++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 34 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 7180e58..e0472c4 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -61,8 +61,10 @@ class SLP { {String tokenId, double sendAmount, List inputUtxos, + List bchInputUtxos, String tokenReceiverAddress, - String changeReceiverAddress, + String slpChangeReceiverAddress, + String bchChangeReceiverAddress, List requiredNonTokenOutputs, int extraFee, int type = 0x01}) async { @@ -71,7 +73,13 @@ class SLP { return Exception("Token id should be a String"); } if (tokenReceiverAddress is! String) { - throw new Exception("Token address should be a String"); + throw new Exception("Token receiving address should be a String"); + } + if (slpChangeReceiverAddress is! String) { + throw new Exception("Slp change receiving address should be a String"); + } + if (bchChangeReceiverAddress is! String) { + throw new Exception("Bch change receiving address should be a String"); } try { if (sendAmount > 0) { @@ -105,10 +113,11 @@ class SLP { txHex = await _buildRawSendTx( slpSendOpReturn: sendOpReturn, inputTokenUtxos: inputUtxos, + bchInputUtxos: bchInputUtxos, tokenReceiverAddresses: sendChange - ? [tokenReceiverAddress, changeReceiverAddress] + ? [tokenReceiverAddress, slpChangeReceiverAddress] : [tokenReceiverAddress], - bchChangeReceiverAddress: changeReceiverAddress, + bchChangeReceiverAddress: bchChangeReceiverAddress, requiredNonTokenOutputs: requiredNonTokenOutputs, extraFee: extraFee); @@ -152,6 +161,7 @@ class SLP { _buildRawSendTx( {List slpSendOpReturn, List inputTokenUtxos, + List bchInputUtxos, List tokenReceiverAddresses, String bchChangeReceiverAddress, List requiredNonTokenOutputs, @@ -165,9 +175,9 @@ class SLP { }); if (bchChangeReceiverAddress != null) { - if (!bchChangeReceiverAddress.startsWith('simpleledger:')) { + if (!bchChangeReceiverAddress.startsWith('bitcoincash:')) { throw new Exception( - "BCH/SLP token change receiver address is not in SlpAddr format."); + "BCH change receiver address is not in CashAddr format."); } } @@ -219,31 +229,17 @@ class SLP { // let sequence = 0xffffffff - 1; // Calculate the total input amount & add all inputs to the transaction - var inputSatoshis = BigInt.from(0); inputTokenUtxos.forEach((i) { inputSatoshis += i['satoshis']; transactionBuilder.addInput(i['txid'], i['vout']); }); - // Calculate the amount of outputs set aside for special BCH-only outputs for fee calculation - var bchOnlyCount = - requiredNonTokenOutputs != null ? requiredNonTokenOutputs.length : 0; - BigInt bchOnlyOutputSatoshis = BigInt.from(0); - requiredNonTokenOutputs != null - ? requiredNonTokenOutputs - .forEach((o) => bchOnlyOutputSatoshis += BigInt.from(o['satoshis'])) - : bchOnlyOutputSatoshis = bchOnlyOutputSatoshis; - - // Calculate mining fee cost - int sendCost = _calculateSendCost(slpSendOpReturn.length, - inputTokenUtxos.length, tokenReceiverAddresses.length + bchOnlyCount, - bchChangeAddress: bchChangeReceiverAddress, - feeRate: extraFee != null ? extraFee : 1); - - // Compute BCH change amount - BigInt bchChangeAfterFeeSatoshis = - inputSatoshis - BigInt.from(sendCost) - bchOnlyOutputSatoshis; + // Calculate the total BCH input amount & add all inputs to the transaction + bchInputUtxos.forEach((i) { + inputSatoshis += BigInt.from(i['satoshis']); + transactionBuilder.addInput(i['txid'], i['vout']); + }); // Start adding outputs to transaction // Add SLP SEND OP_RETURN message @@ -256,35 +252,67 @@ class SLP { transactionBuilder.addOutput(outputAddress, 546); }); + // Calculate the amount of outputs set aside for special BCH-only outputs for fee calculation + var bchOnlyCount = + requiredNonTokenOutputs != null ? requiredNonTokenOutputs.length : 0; + BigInt bchOnlyOutputSatoshis = BigInt.from(0); + requiredNonTokenOutputs != null + ? requiredNonTokenOutputs + .forEach((o) => bchOnlyOutputSatoshis += BigInt.from(o['satoshis'])) + : bchOnlyOutputSatoshis = bchOnlyOutputSatoshis; + // Add BCH-only outputs var outputAddress; if (requiredNonTokenOutputs != null) { if (requiredNonTokenOutputs.length > 0) { requiredNonTokenOutputs.forEach((output) { - outputAddress = Address.toLegacyAddress(output.receiverAddress); - outputAddress = Address.toCashAddress(outputAddress); transactionBuilder.addOutput(outputAddress, output.satoshis); }); } } + // Calculate mining fee cost + int sendCost = _calculateSendCost( + slpSendOpReturn.length, + inputTokenUtxos.length + bchInputUtxos.length, + tokenReceiverAddresses.length + bchOnlyCount, + bchChangeAddress: bchChangeReceiverAddress, + feeRate: extraFee != null ? extraFee : 1); + + // Compute BCH change amount + BigInt bchChangeAfterFeeSatoshis = + inputSatoshis - BigInt.from(sendCost) - bchOnlyOutputSatoshis; + if (bchChangeAfterFeeSatoshis < BigInt.from(0)) { + return "Insufficient fees"; + } + // Add change, if any if (bchChangeAfterFeeSatoshis > new BigInt.from(546)) { - var legacyaddr = Address.toLegacyAddress(bchChangeReceiverAddress); - var cashaddr = Address.toCashAddress(legacyaddr); - transactionBuilder.addOutput(cashaddr, bchChangeAfterFeeSatoshis.toInt()); + transactionBuilder.addOutput( + bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt()); } // Sign txn and add sig to p2pkh input with xpriv, - var inp = 0; + int slpIndex = 0; inputTokenUtxos.forEach((i) { if (!i.containsKey('xpriv')) { return throw Exception("Input doesnt contain a xpriv"); } ECPair paymentKeyPair = HDNode.fromXPriv(i['xpriv']).keyPair; - transactionBuilder.sign( - inp, paymentKeyPair, i['satoshis'].toInt(), Transaction.SIGHASH_ALL); - inp++; + transactionBuilder.sign(slpIndex, paymentKeyPair, i['satoshis'].toInt(), + Transaction.SIGHASH_ALL); + slpIndex++; + }); + + int bchIndex = inputTokenUtxos.length; + bchInputUtxos.forEach((i) { + if (!i.containsKey('xpriv')) { + return throw Exception("Input doesnt contain a xpriv"); + } + ECPair paymentKeyPair = HDNode.fromXPriv(i['xpriv']).keyPair; + transactionBuilder.sign(bchIndex, paymentKeyPair, i['satoshis'].toInt(), + Transaction.SIGHASH_ALL); + bchIndex++; }); // Build the transaction to hex and return @@ -296,6 +324,7 @@ class SLP { transactionBuilder.tx.outputs.forEach((o) => outValue += o.value); int inValue = 0; inputTokenUtxos.forEach((i) => inValue += i['satoshis'].toInt()); + bchInputUtxos.forEach((i) => inValue += i['satoshis']); if (inValue - outValue < hex.length / 2) { print('inValue: $inValue'); print('outValue: $outValue'); From 1a60a486a2ec792b39140fb3bf1e67e310417c83 Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 24 Apr 2020 20:41:39 +0530 Subject: [PATCH 042/106] fixed token decimal precision to send amount --- lib/src/slp.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index e0472c4..47b99af 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -83,7 +83,9 @@ class SLP { } try { if (sendAmount > 0) { - amount = BigInt.from(sendAmount); + var tokenInfo = await SLP().getTokenInformation(tokenId); + int decimals = tokenInfo['data']['decimals']; + amount = BigInt.from(sendAmount * math.pow(10, decimals)); } } catch (e) { return Exception("Invalid amount"); From 0f64e1a5cac71e361549458a67f6446c7a31dc2c Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 24 Apr 2020 21:05:22 +0530 Subject: [PATCH 043/106] token info bug --- lib/src/slp.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 47b99af..63c930d 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -83,7 +83,7 @@ class SLP { } try { if (sendAmount > 0) { - var tokenInfo = await SLP().getTokenInformation(tokenId); + var tokenInfo = await getTokenInformation(tokenId); int decimals = tokenInfo['data']['decimals']; amount = BigInt.from(sendAmount * math.pow(10, decimals)); } From 98fcd0a56c6c6f5bb094fdfc6a9765b7d1db108f Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 24 Apr 2020 21:14:35 +0530 Subject: [PATCH 044/106] removed Response for getRawTransaction --- lib/src/slp.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 63c930d..0f9994b 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -9,12 +9,9 @@ import 'dart:math' as math; class SLP { getTokenInformation(String tokenID, [bool decimalConversion = false]) async { - Response response; var res; try { - response = - await RawTransactions.getRawtransaction(tokenID, verbose: true); - res = jsonDecode(response.body); + res = await RawTransactions.getRawtransaction(tokenID, verbose: true); if (res.containsKey('error')) { throw Exception( "BITBOX response error for 'RawTransactions.getRawTransaction'"); From 432044a9a2f360c5418da9dccbac056f5e52da39 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 11 Jun 2020 16:17:20 +0530 Subject: [PATCH 045/106] changed slp amounts to num --- lib/src/slp.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 0f9994b..6abd4fe 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -56,7 +56,7 @@ class SLP { simpleTokenSend( {String tokenId, - double sendAmount, + num sendAmount, List inputUtxos, List bchInputUtxos, String tokenReceiverAddress, From b978d563dd3499f6835c65422759ce76f504099c Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 16 Jun 2020 09:52:21 +0530 Subject: [PATCH 046/106] send slp to multiple addresses --- lib/src/slp.dart | 48 ++++++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 6abd4fe..156f3b3 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -56,22 +56,25 @@ class SLP { simpleTokenSend( {String tokenId, - num sendAmount, + List sendAmounts, List inputUtxos, List bchInputUtxos, - String tokenReceiverAddress, + List tokenReceiverAddresses, String slpChangeReceiverAddress, String bchChangeReceiverAddress, List requiredNonTokenOutputs, int extraFee, int type = 0x01}) async { - BigInt amount; + List amounts; + BigInt totalAmount; if (tokenId is! String) { return Exception("Token id should be a String"); } - if (tokenReceiverAddress is! String) { - throw new Exception("Token receiving address should be a String"); - } + tokenReceiverAddresses.forEach((tokenReceiverAddress) { + if (tokenReceiverAddress is! String) { + throw new Exception("Token receiving address should be a String"); + } + }); if (slpChangeReceiverAddress is! String) { throw new Exception("Slp change receiving address should be a String"); } @@ -79,11 +82,14 @@ class SLP { throw new Exception("Bch change receiving address should be a String"); } try { - if (sendAmount > 0) { - var tokenInfo = await getTokenInformation(tokenId); - int decimals = tokenInfo['data']['decimals']; - amount = BigInt.from(sendAmount * math.pow(10, decimals)); - } + sendAmounts.forEach((sendAmount) async { + if (sendAmount > 0) { + var tokenInfo = await getTokenInformation(tokenId); + int decimals = tokenInfo['data']['decimals']; + totalAmount += BigInt.from(sendAmount * math.pow(10, decimals)); + amounts.add(BigInt.from(sendAmount * math.pow(10, decimals))); + } + }); } catch (e) { return Exception("Invalid amount"); } @@ -95,27 +101,29 @@ class SLP { totalTokenInputAmount += _preSendSlpJudgementCheck(txo, tokenId)); // 2 Compute the token Change amount. - BigInt tokenChangeAmount = totalTokenInputAmount - amount; + BigInt tokenChangeAmount = totalTokenInputAmount - totalAmount; bool sendChange = tokenChangeAmount > new BigInt.from(0); String txHex; if (tokenChangeAmount < new BigInt.from(0)) { return throw Exception('Token inputs less than the token outputs'); } + + if (tokenChangeAmount > BigInt.from(0)) { + amounts.add(tokenChangeAmount); + } + + if (sendChange) { + tokenReceiverAddresses.add(slpChangeReceiverAddress); + } // 3 Create the Send OP_RETURN message - var sendOpReturn = Send( - HEX.decode(tokenId), - tokenChangeAmount > BigInt.from(0) - ? [amount, tokenChangeAmount] - : [amount]); + var sendOpReturn = Send(HEX.decode(tokenId), amounts); // 4 Create the raw Send transaction hex txHex = await _buildRawSendTx( slpSendOpReturn: sendOpReturn, inputTokenUtxos: inputUtxos, bchInputUtxos: bchInputUtxos, - tokenReceiverAddresses: sendChange - ? [tokenReceiverAddress, slpChangeReceiverAddress] - : [tokenReceiverAddress], + tokenReceiverAddresses: tokenReceiverAddresses, bchChangeReceiverAddress: bchChangeReceiverAddress, requiredNonTokenOutputs: requiredNonTokenOutputs, extraFee: extraFee); From 90371fbbe4cc9ad41f6a872451e4a04a37a23c4a Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 16 Jun 2020 21:30:18 +0530 Subject: [PATCH 047/106] set default value of total slpamount to 0 --- lib/src/slp.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 156f3b3..0009067 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -66,7 +66,7 @@ class SLP { int extraFee, int type = 0x01}) async { List amounts; - BigInt totalAmount; + BigInt totalAmount = BigInt.from(0); if (tokenId is! String) { return Exception("Token id should be a String"); } From e410e0f5dc5fc40d8ab6e625250ee6195abf8483 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 16 Jun 2020 22:32:23 +0530 Subject: [PATCH 048/106] return fee data --- lib/src/slp.dart | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 0009067..3abbcfe 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -65,7 +65,7 @@ class SLP { List requiredNonTokenOutputs, int extraFee, int type = 0x01}) async { - List amounts; + List amounts = []; BigInt totalAmount = BigInt.from(0); if (tokenId is! String) { return Exception("Token id should be a String"); @@ -81,18 +81,16 @@ class SLP { if (bchChangeReceiverAddress is! String) { throw new Exception("Bch change receiving address should be a String"); } - try { - sendAmounts.forEach((sendAmount) async { - if (sendAmount > 0) { - var tokenInfo = await getTokenInformation(tokenId); - int decimals = tokenInfo['data']['decimals']; - totalAmount += BigInt.from(sendAmount * math.pow(10, decimals)); - amounts.add(BigInt.from(sendAmount * math.pow(10, decimals))); - } - }); - } catch (e) { - return Exception("Invalid amount"); - } + + var tokenInfo = await getTokenInformation(tokenId); + int decimals = tokenInfo['data']['decimals']; + + sendAmounts.forEach((sendAmount) async { + if (sendAmount > 0) { + totalAmount += BigInt.from(sendAmount * math.pow(10, decimals)); + amounts.add(BigInt.from(sendAmount * math.pow(10, decimals))); + } + }); // 1 Set the token send amounts, send tokens to a // new receiver and send token change back to the sender @@ -108,7 +106,6 @@ class SLP { if (tokenChangeAmount < new BigInt.from(0)) { return throw Exception('Token inputs less than the token outputs'); } - if (tokenChangeAmount > BigInt.from(0)) { amounts.add(tokenChangeAmount); } @@ -290,7 +287,7 @@ class SLP { BigInt bchChangeAfterFeeSatoshis = inputSatoshis - BigInt.from(sendCost) - bchOnlyOutputSatoshis; if (bchChangeAfterFeeSatoshis < BigInt.from(0)) { - return "Insufficient fees"; + return {'hex': null, 'fee': "Insufficient fees"}; } // Add change, if any @@ -333,13 +330,9 @@ class SLP { inputTokenUtxos.forEach((i) => inValue += i['satoshis'].toInt()); bchInputUtxos.forEach((i) => inValue += i['satoshis']); if (inValue - outValue < hex.length / 2) { - print('inValue: $inValue'); - print('outValue: $outValue'); - print('hex: $hex'); - throw Exception( - "Transaction input BCH amount is too low. Add more BCH inputs to fund this transaction."); + return {'hex': null, 'fee': "Insufficient fee"}; } - return hex; + return {'hex': hex, 'fee': sendCost}; } int _calculateSendCost(int sendOpReturnLength, int inputUtxoSize, int outputs, From 7d4db7f8cf020a21768716e22a4a37e473a227c2 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 16 Jun 2020 23:14:08 +0530 Subject: [PATCH 049/106] return hex and fee for token send --- lib/src/slp.dart | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 3abbcfe..645d9a3 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -102,7 +102,6 @@ class SLP { BigInt tokenChangeAmount = totalTokenInputAmount - totalAmount; bool sendChange = tokenChangeAmount > new BigInt.from(0); - String txHex; if (tokenChangeAmount < new BigInt.from(0)) { return throw Exception('Token inputs less than the token outputs'); } @@ -116,7 +115,7 @@ class SLP { // 3 Create the Send OP_RETURN message var sendOpReturn = Send(HEX.decode(tokenId), amounts); // 4 Create the raw Send transaction hex - txHex = await _buildRawSendTx( + Map result = await _buildRawSendTx( slpSendOpReturn: sendOpReturn, inputTokenUtxos: inputUtxos, bchInputUtxos: bchInputUtxos, @@ -124,9 +123,7 @@ class SLP { bchChangeReceiverAddress: bchChangeReceiverAddress, requiredNonTokenOutputs: requiredNonTokenOutputs, extraFee: extraFee); - - // Return raw hex for this transaction - return txHex; + return result; } BigInt _preSendSlpJudgementCheck(Map txo, tokenID) { From 7d3b1f0d2c906a995fe60ee976f26474458c4aaa Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 25 Jun 2020 11:45:31 +0530 Subject: [PATCH 050/106] sign tx with wif --- lib/src/slp.dart | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 645d9a3..4a2894c 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -40,11 +40,12 @@ class SLP { return slpMsg; } - mapToSLPUtxoArray(List utxos, String xpriv) { + mapToSLPUtxoArray(List utxos, String xpriv, [String wif]) { List utxo = []; utxos.forEach((txo) => utxo.add({ 'satoshis': new BigInt.from(txo['satoshis']), 'xpriv': xpriv, + 'wif': wif, 'txid': txo['txid'], 'vout': txo['vout'], 'slpTransactionDetails': txo['slpTransactionDetails'], @@ -296,10 +297,14 @@ class SLP { // Sign txn and add sig to p2pkh input with xpriv, int slpIndex = 0; inputTokenUtxos.forEach((i) { - if (!i.containsKey('xpriv')) { - return throw Exception("Input doesnt contain a xpriv"); + ECPair paymentKeyPair; + String xpriv = i['xpriv']; + String wif = i['wif']; + if (xpriv != null) { + paymentKeyPair = HDNode.fromXPriv(xpriv).keyPair; + } else { + paymentKeyPair = ECPair.fromWIF(wif); } - ECPair paymentKeyPair = HDNode.fromXPriv(i['xpriv']).keyPair; transactionBuilder.sign(slpIndex, paymentKeyPair, i['satoshis'].toInt(), Transaction.SIGHASH_ALL); slpIndex++; @@ -307,10 +312,14 @@ class SLP { int bchIndex = inputTokenUtxos.length; bchInputUtxos.forEach((i) { - if (!i.containsKey('xpriv')) { - return throw Exception("Input doesnt contain a xpriv"); + ECPair paymentKeyPair; + String xpriv = i['xpriv']; + String wif = i['wif']; + if (xpriv != null) { + paymentKeyPair = HDNode.fromXPriv(xpriv).keyPair; + } else { + paymentKeyPair = ECPair.fromWIF(wif); } - ECPair paymentKeyPair = HDNode.fromXPriv(i['xpriv']).keyPair; transactionBuilder.sign(bchIndex, paymentKeyPair, i['satoshis'].toInt(), Transaction.SIGHASH_ALL); bchIndex++; From 1fbee50fdb754343a35801093c42901be5f5b9e1 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 25 Jun 2020 12:58:37 +0530 Subject: [PATCH 051/106] added named params to slp utxo map --- lib/src/slp.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 4a2894c..7935a4a 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -40,7 +40,7 @@ class SLP { return slpMsg; } - mapToSLPUtxoArray(List utxos, String xpriv, [String wif]) { + mapToSLPUtxoArray({List utxos, String xpriv, String wif}) { List utxo = []; utxos.forEach((txo) => utxo.add({ 'satoshis': new BigInt.from(txo['satoshis']), From b28fe2482b912804a3a6ff1b8edd49df1602da62 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 4 Aug 2020 17:58:08 +0530 Subject: [PATCH 052/106] deleted files --- lib/bitbox.dart | 3 - lib/src/bchaddress.dart | 214 --------------------------- lib/src/privatekey.dart | 266 --------------------------------- lib/src/publickey.dart | 318 ---------------------------------------- 4 files changed, 801 deletions(-) delete mode 100644 lib/src/bchaddress.dart delete mode 100644 lib/src/privatekey.dart delete mode 100644 lib/src/publickey.dart diff --git a/lib/bitbox.dart b/lib/bitbox.dart index 99aeb7e..7e5d073 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -2,7 +2,6 @@ library bitbox; export 'src/account.dart'; export 'src/address.dart'; -export 'src/bchaddress.dart'; export 'src/bitbox.dart'; export 'src/bitcoincash.dart'; export 'src/block.dart'; @@ -12,8 +11,6 @@ export 'src/ecpair.dart'; export 'src/hdnode.dart'; export 'src/mnemonic.dart'; export 'src/networks.dart'; -export 'src/privatekey.dart'; -export 'src/publickey.dart'; export 'src/slp.dart'; export 'src/rawtransactions.dart'; export 'src/transaction.dart'; diff --git a/lib/src/bchaddress.dart b/lib/src/bchaddress.dart deleted file mode 100644 index 33d4758..0000000 --- a/lib/src/bchaddress.dart +++ /dev/null @@ -1,214 +0,0 @@ -import 'package:bitbox/src/publickey.dart'; - -import 'encoding/base58check.dart' as bs58check; -import 'package:hex/hex.dart'; -import 'encoding/utils.dart'; -import 'dart:convert'; -import 'networks.dart'; -import 'exceptions.dart'; - -//TODO: No support P2SH addresses at the moment. I'll add when I need it. - -/// This class abstracts away the internals of address encoding and provides -/// a convenient means to both encode and decode information from a bitcoin address. -/// -/// Bitcoin addresses are a construct which facilitates interoperability -/// between different wallets. I.e. an agreement amongst wallet providers to have a -/// common means of sharing the hashed public key value needed to send someone bitcoin using one -/// of the standard public-key-based transaction types. -/// -/// The Address does not contain a public key, only a hashed value of a public key -/// which is derived as explained below. -/// -/// Bitcoin addresses are not part of the consensus rules of bitcoin. -/// -/// Bitcoin addresses are encoded as follows -/// * 1st byte - indicates the network type which is either MAINNET or TESTNET -/// * next 20 bytes - the hash value computed by taking the `ripemd160(sha256(PUBLIC_KEY))` -/// * last 4 bytes - a checksum value taken from the first four bytes of sha256(sha256(previous_21_bytes)) - -class BCHAddress { - List _networkTypes; - - String _publicKeyHash; - AddressType _addressType; - NetworkType _networkType; - int _version; - - /// Constructs a new Address object - /// - /// [address] is the base58encoded bitcoin address. - /// - BCHAddress(String address) { - _fromBase58(address); - } - - /// Constructs a new Address object from a public key. - /// - /// [hexPubKey] is the hexadecimal encoding of a public key. - /// - /// [networkType] is used to distinguish between MAINNET and TESTNET. - /// - /// Also see [NetworkType] - BCHAddress.fromHex(String hexPubKey, NetworkType networkType) { - _createFromHex(hexPubKey, networkType); - } - - /// Constructs a new Address object from the public key - /// - /// [pubKey] - The public key - /// - /// [networkType] is used to distinguish between MAINNET and TESTNET. - /// - /// Also see [NetworkType] - BCHAddress.fromPublicKey(BCHPublicKey pubKey, NetworkType networkType) { - _createFromHex(pubKey.toHex(), networkType); - } - - /// Constructs a new Address object from a compressed public key value - /// - /// [pubkeyBytes] is a byte-buffer of a public key - /// - /// [networkType] is used to distinguish between MAINNET and TESTNET. - /// - /// Also see [NetworkType] - BCHAddress.fromCompressedPubKey( - List pubkeyBytes, NetworkType networkType) { - _createFromHex(HEX.encode(pubkeyBytes), networkType); - _publicKeyHash = HEX.encode(hash160(pubkeyBytes)); - } - - /// Constructs a new Address object from a base58-encoded string. - /// - /// Base58-encoded strings are the "standard" means of sharing bitoin addresses amongst - /// wallets. This is typically done either using the string of directly, or by using a - /// QR-encoded form of this string. - /// - /// Typically, if someone is sharing their bitcoin address with you, this is the method - /// you would use to instantiate an [Address] object for use with [Transaction] objects. - /// - BCHAddress.fromBase58(String base58Address) { - if (base58Address.length != 25) { - throw AddressFormatException( - 'Address should be 25 bytes long. Only [${base58Address.length}] bytes long.'); - } - - _fromBase58(base58Address); - } - - /// Serialise this address object to a base58-encoded string - /// - /// Base58-encoded strings are the "standard" means of sharing bitoin addresses amongst - /// wallets. This is typically done either using the string directly, or by using a - /// QR-encoded form of this string. - /// - /// When sharing a bitcoin address with an external party either as a QR-code or via - /// email etc., this would typically be the form in which you share the address. - /// - String toBase58() { - // A stringified buffer is: - // 1 byte version + data bytes + 4 bytes check code (a truncated hash) - var rawHash = - HEX.decode(_publicKeyHash).map((elem) => elem.toSigned(8)).toList(); - - return _getEncoded(rawHash); - } - - /// Serialise this address object to a base58-encoded string. - /// This method is an alias for the [toBase58()] method - @override - String toString() { - return toBase58(); - } - - /// Returns the public key hash `ripemd160(sha256(public_key))` encoded as a hexadecimal string - String toHex() { - return _publicKeyHash; - } - - String _getEncoded(List hashAddress) { - var addressBytes = List(1 + hashAddress.length + 4); - addressBytes[0] = _version; - - //copy all of raw address content, taking care not to - //overwrite the version byte at start - addressBytes.fillRange(1, addressBytes.length, 0); - addressBytes.setRange(1, hashAddress.length + 1, hashAddress); - - //checksum calculation... - //doubleSha everything except the last four checksum bytes - var doubleShaAddr = - sha256Twice(addressBytes.sublist(0, hashAddress.length + 1)); - var checksum = - doubleShaAddr.sublist(0, 4).map((elem) => elem.toSigned(8)).toList(); - - addressBytes.setRange( - hashAddress.length + 1, addressBytes.length, checksum); - var encoded = bs58check.encode(addressBytes); - var utf8Decoded = utf8.decode(encoded); - - return utf8Decoded; - } - - void _fromBase58(String address) { - address = address.trim(); - - var versionAndDataBytes = bs58check.decodeChecked(address); - var versionByte = versionAndDataBytes[0].toUnsigned(8); - - _version = versionByte & 0xFF; - _networkTypes = Networks.getNetworkTypes(_version); - _addressType = Networks.getAddressType(_version); - _networkType = Networks.getNetworkTypes(_version)[0]; - var stripVersion = - versionAndDataBytes.sublist(1, versionAndDataBytes.length); - _publicKeyHash = - HEX.encode(stripVersion.map((elem) => elem.toUnsigned(8)).toList()); - } - - void _createFromHex(String hexPubKey, NetworkType networkType) { - //make an assumption about PKH vs PSH for naked address generation - var versionByte; - if (networkType == NetworkType.MAIN) { - versionByte = Networks.getNetworkVersion(NetworkAddressType.MAIN_PKH); - } else { - versionByte = Networks.getNetworkVersion(NetworkAddressType.TEST_PKH); - } - - _version = versionByte & 0XFF; - _publicKeyHash = HEX.encode(hash160(HEX.decode(hexPubKey))); - _addressType = Networks.getAddressType(_version); - _networkType = networkType; - } - - /// Returns a hash of the Public Key - /// - /// The sha256 digest of the public key is computed, and the result of that - /// computation is then passed to the `ripemd160()` digest function. - /// - /// The returned value is HEX-encoded - String get address => _publicKeyHash; - - /// An alias for the [address] property - String get pubkeyHash160 => _publicKeyHash; - - /// Returns a list of network types supported by this address - /// - /// This is only really needed because BCH has two different test networks - /// which technically share the same integer value when encoded, but for - /// which it is useful to have a type-level distinction during development - List get networkTypes => _networkTypes; - - /// Returns the specific Network Type that this Address is compatible with - NetworkType get networkType => _networkType; - - /// Returns the type of "standard transaction" this Address is meant to be used for. - /// - /// Addresses are not part of the consensus rules of bitcoin. However with the introduction - /// of "standard transaction types" wallets have fallen in line with providing - /// address types that distinguish the types of transactions the coins are meant - /// to be associated with. - /// - /// See documentation for [Transaction] - AddressType get addressType => _addressType; -} diff --git a/lib/src/privatekey.dart b/lib/src/privatekey.dart deleted file mode 100644 index 4753b12..0000000 --- a/lib/src/privatekey.dart +++ /dev/null @@ -1,266 +0,0 @@ -import 'package:bitbox/src/bchaddress.dart'; -import 'package:bitbox/src/exceptions.dart'; -import 'package:bitbox/src/networks.dart'; -import 'package:bitbox/src/publickey.dart'; -import 'package:pointycastle/pointycastle.dart'; -import 'package:pointycastle/random/fortuna_random.dart'; -import 'encoding/base58check.dart' as bs58check; -import 'package:hex/hex.dart'; -import 'dart:typed_data'; -import 'dart:convert'; -import 'dart:math'; -import 'encoding/utils.dart'; -import 'package:pointycastle/key_generators/ec_key_generator.dart'; -import 'package:pointycastle/ecc/curves/secp256k1.dart'; -import 'package:pointycastle/api.dart'; - -/// Manages an ECDSA private key. -/// -/// Bitcoin uses ECDSA for it's public/private key cryptography. -/// Specifically it uses the `secp256k1` elliptic curve. -/// -/// This class wraps cryptographic operations related to ECDSA from the -/// [PointyCastle](https://pub.dev/packages/pointycastle) library/package. -/// -/// You can read a good primer on Elliptic Curve Cryptography at [This Cloudflare blog post](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/) -/// -/// -class BCHPrivateKey { - final _domainParams = ECDomainParameters('secp256k1'); - final _secureRandom = FortunaRandom(); - - var _hasCompressedPubKey = false; - var _networkType = NetworkType.MAIN; //Mainnet by default - - var random = Random.secure(); - - BigInt _d; - ECPrivateKey _ecPrivateKey; - BCHPublicKey _bchPublicKey; - - /// Constructs a random private key. - /// - /// [networkType] - Optional network type. Defaults to mainnet. The network type is only - /// used when serialising the Private Key in *WIF* format. See [toWIF()]. - /// - BCHPrivateKey({networkType = NetworkType.MAIN}) { - var keyParams = ECKeyGeneratorParameters(ECCurve_secp256k1()); - _secureRandom.seed(KeyParameter(_seed())); - - var generator = ECKeyGenerator(); - generator.init(ParametersWithRandom(keyParams, _secureRandom)); - - var keypair = generator.generateKeyPair(); - - _hasCompressedPubKey = true; - _networkType = networkType; - - _ecPrivateKey = keypair.privateKey; - _d = _ecPrivateKey.d; - _bchPublicKey = BCHPublicKey.fromPrivateKey(this); - } - - /// Constructs a Private Key from a Big Integer. - /// - /// [privateKey] - The private key as a Big Integer value. Remember that in - /// ECDSA we compute the public key (Q) as `Q = d * G` - BCHPrivateKey.fromBigInt(BigInt privateKey) { - _ecPrivateKey = _privateKeyFromBigInt(privateKey); - _d = privateKey; - _hasCompressedPubKey = true; - _bchPublicKey = BCHPublicKey.fromPrivateKey(this); - } - - /// Construct a Private Key from the hexadecimal value representing the - /// BigInt value of (d) in ` Q = d * G ` - /// - /// [privhex] - The BigInt representation of the private key as a hexadecimal string - /// - /// [networkType] - The network type we intend to use to corresponding WIF representation on. - BCHPrivateKey.fromHex(String privhex, NetworkType networkType) { - var d = BigInt.parse(privhex, radix: 16); - - _hasCompressedPubKey = true; - _networkType = networkType; - _ecPrivateKey = _privateKeyFromBigInt(d); - _d = d; - _bchPublicKey = BCHPublicKey.fromPrivateKey(this); - } - - /// Construct a Private Key from the WIF encoded format. - /// - /// WIF is an abbreviation for Wallet Import Format. It is a format based on base58-encoding - /// a private key so as to make it resistant to accidental user error in copying it. A wallet - /// should be able to verify that the WIF format represents a valid private key. - /// - /// [wifKey] - The private key in WIF-encoded format. See [this bitcoin wiki entry](https://en.bitcoin.it/wiki/Wallet_import_format) - /// - BCHPrivateKey.fromWIF(String wifKey) { - if (wifKey.length != 51 && wifKey.length != 52) { - throw InvalidKeyException( - 'Valid keys are either 51 or 52 bytes in length'); - } - - //decode from base58 - var versionAndDataBytes = bs58check.decodeChecked(wifKey); - - switch (wifKey[0]) { - case '5': - { - if (wifKey.length != 51) { - throw InvalidKeyException( - 'Uncompressed private keys have a length of 51 bytes'); - } - - _hasCompressedPubKey = false; - _networkType = NetworkType.MAIN; - break; - } - case '9': - { - if (wifKey.length != 51) { - throw InvalidKeyException( - 'Uncompressed private keys have a length of 51 bytes'); - } - - _hasCompressedPubKey = false; - _networkType = NetworkType.TEST; - break; - } - case 'L': - case 'K': - { - if (wifKey.length != 52) { - throw InvalidKeyException( - 'Compressed private keys have a length of 52 bytes'); - } - - _networkType = NetworkType.MAIN; - _hasCompressedPubKey = true; - break; - } - case 'c': - { - if (wifKey.length != 52) { - throw InvalidKeyException( - 'Compressed private keys have a length of 52 bytes'); - } - - _networkType = NetworkType.TEST; - _hasCompressedPubKey = true; - break; - } - default: - { - throw InvalidNetworkException( - 'Address WIF format must start with either [5] or [9]'); - } - } - - //strip first byte - var versionStripped = - versionAndDataBytes.sublist(1, versionAndDataBytes.length); - - if (versionStripped.length == 33) { - //drop last byte - //throw error if last byte is not 0x01 to indicate compression - if (versionStripped[32] != 0x01) { - throw InvalidKeyException( - "Compressed keys must have last byte set as 0x01. Yours is [${versionStripped[32]}]"); - } - - versionStripped = versionStripped.sublist(0, 32); - _hasCompressedPubKey = true; - } else { - _hasCompressedPubKey = false; - } - - var strippedHex = - HEX.encode(versionStripped.map((elem) => elem.toUnsigned(8)).toList()); - - var d = BigInt.parse(strippedHex, radix: 16); - - _ecPrivateKey = _privateKeyFromBigInt(d); - _d = d; - - _bchPublicKey = BCHPublicKey.fromPrivateKey(this); - } - - /// Returns this Private Key in WIF format. See [toWIF()]. - String toWIF() { - //convert private key _d to a hex string - var wifKey = _d.toRadixString(16); - - if (_networkType == NetworkType.MAIN) { - wifKey = HEX.encode([0x80]) + wifKey; - } else if (_networkType == NetworkType.TEST || - _networkType == NetworkType.REGTEST) { - wifKey = HEX.encode([0xef]) + wifKey; - } - - if (_hasCompressedPubKey) { - wifKey = wifKey + HEX.encode([0x01]); - } - - var shaWif = sha256Twice(HEX.decode(wifKey)); - var checksum = shaWif.sublist(0, 4); - - wifKey = wifKey + HEX.encode(checksum); - - var finalWif = bs58check.encode(HEX.decode(wifKey)); - - return utf8.decode(finalWif); - } - - /// Returns the *naked* private key Big Integer value as a hexadecimal string - String toHex() { - return _d.toRadixString(16); - } - - //convenience method to retrieve an address - /// Convenience method that jumps through the hoops of generating and [Address] from this - /// Private Key's corresponding [BCHPublicKey]. - BCHAddress toAddress({networkType = NetworkType.MAIN}) { - //FIXME: set network type to default parameter unless explicitly specified ? - return _bchPublicKey.toAddress(_networkType); - } - - Uint8List _seed() { - var random = Random.secure(); - var seed = List.generate(32, (_) => random.nextInt(256)); - return Uint8List.fromList(seed); - } - - ECPrivateKey _privateKeyFromBigInt(BigInt d) { - if (d == BigInt.zero) { - throw BadParameterException( - 'Zero is a bad value for a private key. Pick something else.'); - } - - return ECPrivateKey(d, _domainParams); - } - - /// Returns the Network Type that we intend to use this private key on. - /// This is also the value encoded in the WIF format representation of this key. - NetworkType get networkType { - return _networkType; - } - - /// Returns the *naked* private key Big Integer value as a Big Integer - BigInt get privateKey { - return _d; - } - - /// Returns the [BCHPublicKey] corresponding to this ECDSA private key. - /// - /// NOTE: `Q = d * G` where *Q* is the public key, *d* is the private key and `G` is the curve's Generator. - BCHPublicKey get publicKey { - return _bchPublicKey; - } - - /// Returns true if the corresponding public key for this private key - /// is in *compressed* format. To read more about compressed public keys see BCHPublicKey().getEncoded()] - bool get isCompressed { - return _hasCompressedPubKey; - } -} diff --git a/lib/src/publickey.dart b/lib/src/publickey.dart deleted file mode 100644 index e061eba..0000000 --- a/lib/src/publickey.dart +++ /dev/null @@ -1,318 +0,0 @@ -import 'package:bitbox/src/bchaddress.dart'; -import 'package:bitbox/src/networks.dart'; -import 'package:bitbox/src/privatekey.dart'; -import 'package:hex/hex.dart'; -import 'package:pointycastle/pointycastle.dart'; -import 'encoding/utils.dart'; -import 'exceptions.dart'; - -/// Manages an ECDSA public key. -/// -/// Bitcoin uses ECDSA for it's public/private key cryptography. -/// Specifically it uses the `secp256k1` elliptic curve. -/// -/// This class wraps cryptographic operations related to ECDSA from the -/// [PointyCastle](https://pub.dev/packages/pointycastle) library/package. -/// -/// You can read a good primer on Elliptic Curve Cryptography at [This Cloudflare blog post](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/) -/// -/// -class BCHPublicKey { - //We only deal with secp256k1 - final _domainParams = ECDomainParameters('secp256k1'); -// var _curve = ECCurve_secp256k1(); - - ECPoint _point; - -// ECPublicKey _publicKey; - - /// Creates a public key from it's corresponding ECDSA private key. - /// - /// NOTE: public key *Q* is computed as `Q = d * G` where *d* is the private key - /// and *G* is the elliptic curve Generator. - /// - /// [privkey] - The private key who's *d*-value we will use. - BCHPublicKey.fromPrivateKey(BCHPrivateKey privkey) { - var decodedPrivKey = encodeBigInt(privkey.privateKey); - var hexPrivKey = HEX.encode(decodedPrivKey); - - var actualKey = hexPrivKey; - var point = _domainParams.G * BigInt.parse(actualKey, radix: 16); - if (point.x == null && point.y == null) { - throw InvalidPointException( - 'Cannot generate point from private key. Private key greater than N ?'); - } - - //create a point taking into account compression request/indicator of parent private key - var finalPoint = _domainParams.curve.createPoint( - point.x.toBigInteger(), point.y.toBigInteger(), privkey.isCompressed); - - _checkIfOnCurve(finalPoint); // a bit paranoid - - _point = finalPoint; -// _publicKey = ECPublicKey((_point), _domainParams); - } - - /// Creates a public key instance from the ECDSA public key's `x-coordinate` - /// - /// ECDSA has some cool properties. Because we are dealing with an elliptic curve in a plane, - /// the public key *Q* has (x,y) cartesian coordinates. - /// It is possible to reconstruct the full public key from only it's `x-coordinate` - /// *IFF* one knows whether the Y-Value is *odd* or *even*. - /// - /// [xValue] - The Big Integer value of the `x-coordinate` in hexadecimal format - /// - /// [oddYValue] - *true* if the corresponding `y-coordinate` is even, *false* otherwise - BCHPublicKey.fromX(String xValue, bool oddYValue) { - _point = _getPointFromX(xValue, oddYValue); -// _publicKey = ECPublicKey((_point), _domainParams); - } - - /// Creates a public key from it's known *(x,y)* coordinates. - /// - /// [x] - X coordinate of the public key - /// - /// [y] - Y coordinate of the public key - /// - /// [compressed] = Specifies whether we will render this point in it's - /// compressed form by default with [toString()]. See [getEncoded()] to - /// learn more about compressed public keys. - BCHPublicKey.fromXY(BigInt x, BigInt y, {bool compressed = true}) { - //create a compressed point by default - var point = _domainParams.curve.createPoint(x, y, compressed); - - _checkIfOnCurve(point); - - _point = point; - -// _publicKey = ECPublicKey(_point, _domainParams); - } - - /// Reconstructs a public key from a DER-encoding. - /// - /// [buffer] - Byte array containing public key in DER format. - /// - /// [strict] - If *true* then we enforce strict DER encoding rules. Defaults to *true*. - BCHPublicKey.fromDER(List buffer, {bool strict = true}) { - if (buffer.isEmpty) { - throw BadParameterException('Empty compressed DER buffer'); - } - - _point = _transformDER(buffer, strict); - - if (_point.isInfinity) { - throw InvalidPointException( - 'That public key generates point at infinity'); - } - - if (_point.y.toBigInteger() == BigInt.zero) { - throw InvalidPointException('Invalid Y value for this public key'); - } - - _checkIfOnCurve(_point); - -// _publicKey = ECPublicKey(_point, _domainParams); - } - - /// Reconstruct a public key from the hexadecimal format of it's DER-encoding. - /// - /// [pubkey] - The DER-encoded public key as a hexadecimal string - /// - /// [strict] - If *true* then we enforce strict DER encoding rules. Defaults to *true*. - BCHPublicKey.fromHex(String pubkey, {bool strict = true}) { - if (pubkey.trim() == '') { - throw BadParameterException('Empty compressed public key string'); - } - -// _parseHexString(pubkey); - _point = _transformDER(HEX.decode(pubkey), strict); - - if (_point.isInfinity) { - throw InvalidPointException( - 'That public key generates point at infinity'); - } - - if (_point.y.toBigInteger() == BigInt.zero) { - throw InvalidPointException('Invalid Y value for this public key'); - } - - _checkIfOnCurve(_point); - -// _publicKey = ECPublicKey(_point, _domainParams); - } - - /// Validates that the DER-encoded hexadecimal string contains a valid - /// public key. - /// - /// [pubkey] - The DER-encoded public key as a hexadecimal string - /// - /// Returns *true* if the public key is valid, *false* otherwise. - static bool isValid(String pubkey) { - try { - BCHPublicKey.fromHex(pubkey); - } catch (err) { - return false; - } - - return true; - } - - /// Returns the *naked* public key value as either an (x,y) coordinate - /// or in a compact format using elliptic-curve point-compression. - /// - /// With EC point compression it is possible to reduce by half the - /// space occupied by a point, by taking advantage of a EC-curve property. - /// Specifically it is possible to recover the `y-coordinate` *IFF* the - /// `x-coordinate` is known *AND* we know whether the `y-coordinate` is - /// *odd* or *even*. - /// - /// [compressed] - If *true* the 'naked' public key value is returned in - /// compact format where the first byte is either 'odd' or 'even' followed - /// by the `x-coordinate`. If *false*, the full *(x,y)* coordinate pair will - /// be returned. - /// - /// NOTE: The first byte will contain either an odd number or an even number, - /// but this number is *NOT* a boolean flag. - String getEncoded(bool compressed) { - return HEX.encode(_point.getEncoded(compressed)); - } - - /// Returns the 'naked' public key value. Point compression is determined by - /// the default parameter in the constructor. If you want to enforce a specific preference - /// for the encoding, you can use the [getEncoded()] function instead. - @override - String toString() { - if (_point == null) { - return ''; - } - - return HEX.encode(_point.getEncoded(_point.isCompressed)); - } - - /// Convenience method that constructs an [Address] instance from this - /// public key. - BCHAddress toAddress(NetworkType nat) { - //generate compressed addresses by default - List buffer = _point.getEncoded(_point.isCompressed); - - if (_point.isCompressed) { - return BCHAddress.fromCompressedPubKey(buffer, nat); - } else { - return BCHAddress.fromHex(HEX.encode(buffer), nat); - } - } - - /// Alias for the [toString()] method. - String toHex() => toString(); - - ECPoint _transformDER(List buf, bool strict) { - BigInt x; - BigInt y; - List xbuf; - List ybuf; - ECPoint point; - - if (buf[0] == 0x04 || (!strict && (buf[0] == 0x06 || buf[0] == 0x07))) { - xbuf = buf.sublist(1, 33); - ybuf = buf.sublist(33, 65); - if (xbuf.length != 32 || ybuf.length != 32 || buf.length != 65) { - throw InvalidPointException('Length of x and y must be 32 bytes'); - } - x = BigInt.parse(HEX.encode(xbuf), radix: 16); - y = BigInt.parse(HEX.encode(ybuf), radix: 16); - - point = _domainParams.curve.createPoint(x, y); - } else if (buf[0] == 0x03 || buf[0] == 0x02) { - xbuf = buf.sublist(1); - x = BigInt.parse(HEX.encode(xbuf), radix: 16); - - var yTilde = buf[0] & 1; - point = _domainParams.curve.decompressPoint(yTilde, x); - } else { - throw InvalidPointException('Invalid DER format public key'); - } - return point; - } - - ECPoint _getPointFromX(String xValue, bool oddYValue) { - var prefixByte; - if (oddYValue) { - prefixByte = 0x03; - } else { - prefixByte = 0x02; - } - - var encoded = HEX.decode(xValue); - - var addressBytes = List(1 + encoded.length); - addressBytes[0] = prefixByte; - addressBytes.setRange(1, addressBytes.length, encoded); - - return _decodePoint(HEX.encode(addressBytes)); - } - - ECPoint _decodePoint(String pkHex) { - if (pkHex.trim() == '') { - throw BadParameterException('Empty compressed public key string'); - } - - var encoded = HEX.decode(pkHex); - try { - var point = _domainParams.curve.decodePoint(encoded); - - if (point.isCompressed && encoded.length != 33) { - throw BadParameterException( - "Compressed public keys must be 33 bytes long. Yours is [${encoded.length}]"); - } else if (!point.isCompressed && encoded.length != 65) { - throw BadParameterException( - "Uncompressed public keys must be 65 bytes long. Yours is [${encoded.length}]"); - } - - _checkIfOnCurve(point); - - return point; - } on ArgumentError catch (err) { - throw InvalidPointException(err.message); - } - } - - String _compressPoint(ECPoint point) { - return HEX.encode(point.getEncoded(true)); - } - - bool _checkIfOnCurve(ECPoint point) { - //a bit of math copied from PointyCastle. ecc/ecc_fp.dart -> decompressPoint() - var x = _domainParams.curve.fromBigInteger(point.x.toBigInteger()); - var alpha = (x * ((x * x) + _domainParams.curve.a)) + _domainParams.curve.b; - ECFieldElement beta = alpha.sqrt(); - - if (beta == null) { - throw InvalidPointException('This point is not on the curve'); - } - - //slight-of-hand. Create compressed point, reconstruct and check Y value. - var compressedPoint = _compressPoint(point); - var checkPoint = - _domainParams.curve.decodePoint(HEX.decode(compressedPoint)); - if (checkPoint.y.toBigInteger() != point.y.toBigInteger()) { - throw InvalidPointException('This point is not on the curve'); - } - - return (point.x.toBigInteger() == BigInt.zero) && - (point.y.toBigInteger() == BigInt.zero); - } - - /// Returns the (x,y) coordinates of this public key as an [ECPoint]. - /// The author dislikes leaking the wrapped PointyCastle implementation, but is too - /// lazy to write his own Point implementation. - ECPoint get point { - return _point; - } - - /// Returns *true* if this public key will render using EC point compression by - /// default when one calls the [toString()] or [toHex()] methods. - /// Returns *false* otherwise. - bool get isCompressed { - return _point.isCompressed; - } -} From 8c07cafdd937921237e581ea73bfb63bf14072ba Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 31 Aug 2020 00:55:19 +0530 Subject: [PATCH 053/106] fix bch only outputs for slp send --- lib/src/slp.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 7935a4a..c74bfcd 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -264,11 +264,11 @@ class SLP { : bchOnlyOutputSatoshis = bchOnlyOutputSatoshis; // Add BCH-only outputs - var outputAddress; if (requiredNonTokenOutputs != null) { if (requiredNonTokenOutputs.length > 0) { requiredNonTokenOutputs.forEach((output) { - transactionBuilder.addOutput(outputAddress, output.satoshis); + transactionBuilder.addOutput( + output['bchaddress'], output['satoshis']); }); } } From fa0de4cd59322f45e455179a12b6ba587c907f15 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 17 Sep 2020 17:47:27 +0530 Subject: [PATCH 054/106] improved address format detection --- lib/src/address.dart | 145 +++++++++++++++++++++++--------- lib/src/transactionbuilder.dart | 2 +- 2 files changed, 105 insertions(+), 42 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index b9ac9df..f783f6a 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -11,8 +11,9 @@ import 'package:fixnum/fixnum.dart'; /// There is no reason to instanciate this class. All constants, functions, and methods are static. /// It is assumed that all necessary data to work with addresses are kept in the instance of [ECPair] or [Transaction] class Address { - static const formatCashAddr = 0; - static const formatLegacy = 1; + static const formatCashAddr = 'cashaddr'; + static const formatLegacy = 'legacy'; + static const formatSlp = 'slpaddr'; static const _CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l'; static const _CHARSET_INVERSE_INDEX = { @@ -141,23 +142,11 @@ class Address { } } - /// Converts legacy address to cash address - static String toCashAddress(String legacyAddress, - [bool includePrefix = true]) { - final decoded = Address._decodeLegacyAddress(legacyAddress); - final cashAddress = - Address._encode(decoded['prefix'], decoded['type'], decoded["hash"]); - if (!includePrefix) { - return cashAddress.split(":")[1]; - } else { - return cashAddress; - } - } - /// Converts cashAddr format to legacy address - static String toLegacyAddress(String cashAddress) { - final decoded = _decodeCashAddress(cashAddress); - final testnet = decoded['prefix'] == "bchtest"; + static String toLegacyAddress(String address) { + final decoded = _decode(address); + final testnet = + decoded['prefix'] == "bchtest" || decoded['prefix'] == "slptest"; var version; if (testnet) { switch (decoded['type']) { @@ -181,19 +170,21 @@ class Address { return toBase58Check(decoded["hash"], version); } + /// Converts legacy address to cash address + static String toCashAddress(String address, [bool includePrefix = true]) { + final decoded = _decode(address); + final cashAddress = + _encode(decoded['prefix'], decoded['type'], decoded["hash"]); + if (!includePrefix) { + return cashAddress.split(":")[1]; + } else { + return cashAddress; + } + } + /// Converts legacy or cash address to SLP address static String toSLPAddress(String address, [bool includePrefix = true]) { - final decoded = Address._decode(address); - switch (decoded["prefix"]) { - case 'bitcoincash': - decoded['prefix'] = "simpleledger"; - break; - case 'bchtest': - decoded['prefix'] = "slptest"; - break; - default: - throw FormatException("Unsupported address format: $address"); - } + final decoded = _decode(address); final slpAddress = Address._encode(decoded['prefix'], decoded['type'], decoded["hash"]); if (!includePrefix) { @@ -203,8 +194,20 @@ class Address { } } - /// Detects type of the address and returns [formatCashAddr] or [formatLegacy] - static int detectFormat(String address) { + static bool isLegacyAddress(String address) { + return detectAddressFormat(address) == 'legacy'; + } + + static bool isCashAddress(String address) { + return detectAddressFormat(address) == 'cashaddr'; + } + + static bool isSlpAddress(String address) { + return detectAddressFormat(address) == 'slpaddr'; + } + + /// Detects type of the address and returns [legacy], [cashaddr] or [slpaddr] + static String detectAddressFormat(String address) { // decode the address to determine the format final decoded = _decode(address); // return the format @@ -342,8 +345,9 @@ class Address { } /// Decodes the given address into: - /// * (for cashAddr): constituting prefix (e.g. _bitcoincash_) /// * (for legacy): version + /// * (for cashAddr): constituting prefix (e.g. _bitcoincash_) + ///* (for slpAddr): constituting prefix (e.g. _simpleledger_) /// * hash /// * format static Map _decode(String address) { @@ -355,6 +359,10 @@ class Address { return _decodeCashAddress(address); } catch (e) {} + try { + return _decodeSlpAddress(address); + } catch (e) {} + throw FormatException("Invalid address format : $address"); } @@ -366,7 +374,7 @@ class Address { 'prefix': "", 'type': "", 'hash': buffer.sublist(1), - 'format': "" + 'format': formatLegacy }; switch (buffer.first) { @@ -374,32 +382,24 @@ class Address { decoded = { 'prefix': "bitcoincash", 'type': "P2PKH", - 'hash': buffer.sublist(1), - 'format': "legacy" }; break; case Network.bchPublicscriptHash: decoded = { 'prefix': "bitcoincash", 'type': "P2SH", - 'hash': buffer.sublist(1), - 'format': "legacy" }; break; case Network.bchTestnetPublic: decoded = { 'prefix': "bchtest", 'type': "P2PKH", - 'hash': buffer.sublist(1), - 'format': "legacy" }; break; case Network.bchTestnetscriptHash: decoded = { 'prefix': "bchtest", 'type': "P2SH", - 'hash': buffer.sublist(1), - 'format': "legacy" }; break; } @@ -471,6 +471,69 @@ class Address { throw FormatException(exception); } + static Map _decodeSlpAddress(String address) { + if (!_hasSingleCase(address)) { + throw FormatException("Address has both lower and upper case: $address"); + } + + // split the address with : separator to find out it if contains prefix + final pieces = address.toLowerCase().split(":"); + + // placeholder for different prefixes to be tested later + List prefixes; + + // check if the address contained : separator by looking at number of splitted pieces + if (pieces.length == 2) { + // if it contained the separator, use the first piece as a single prefix + prefixes = [pieces.first]; + address = pieces.last; + } else if (pieces.length == 1) { + // if it came without separator, try all three possible formats + prefixes = ["simpleledger", "slptest", "slpreg"]; + } else { + // if it came with more than one separator, throw a format exception + throw FormatException("Invalid Address Format: $address"); + } + + String exception; + + // try to decode the address with either one or all three possible prefixes + for (int i = 0; i < prefixes.length; i++) { + final payload = _base32Decode(address); + + if (!_validChecksum(prefixes[i], payload)) { + exception = "Invalid checksum: $address"; + continue; + } + + final payloadData = + _fromUint5Array(payload.sublist(0, payload.length - 8)); + + var versionByte = payloadData[0]; + final hash = payloadData.sublist(1); + + if (_getHashSize(payloadData[0]) != hash.length * 8) { + exception = "Invalid hash size: $address"; + continue; + } + + var type = _getType(versionByte); + + // If the loop got all the way here, it means validations went through and the address was decoded. + // Return the decoded data + return { + "prefix": prefixes[i], + "type": type, + "hash": hash, + "format": 'slpaddr' + }; + } + + // if the loop went through all possible formats and didn't return data from the function, it means there were + // validation issues. Throw a format exception + throw FormatException(exception); + } + /// Converts a list of 5-bit integers back into an array of 8-bit integers, removing extra zeroes left from padding /// if necessary. static Uint8List _fromUint5Array(Uint8List data) { diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index 3d57aa8..9532fe4 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -135,7 +135,7 @@ class TransactionBuilder { Uint8List scriptPubKey; if (data is String) { - if (Address.detectFormat(data) == Address.formatCashAddr) { + if (Address.detectAddressFormat(data) == Address.formatCashAddr) { data = Address.toLegacyAddress(data); } scriptPubKey = _addressToOutputScript(data, _network); From c4b53fcdca5ff97d3a49b3b7419358d206ba475c Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 17 Sep 2020 19:33:56 +0530 Subject: [PATCH 055/106] fixed address conversion bugs --- lib/src/address.dart | 61 +++++++++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index f783f6a..1ebd03f 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -173,6 +173,18 @@ class Address { /// Converts legacy address to cash address static String toCashAddress(String address, [bool includePrefix = true]) { final decoded = _decode(address); + switch (decoded["prefix"]) { + case 'bitcoincash': + case 'simpleledger': + decoded['prefix'] = "bitcoincash"; + break; + case 'bchtest': + case 'slptest': + decoded['prefix'] = "bchtest"; + break; + default: + throw FormatException("Unsupported address format: $address"); + } final cashAddress = _encode(decoded['prefix'], decoded['type'], decoded["hash"]); if (!includePrefix) { @@ -184,7 +196,19 @@ class Address { /// Converts legacy or cash address to SLP address static String toSLPAddress(String address, [bool includePrefix = true]) { - final decoded = _decode(address); + final decoded = Address._decode(address); + switch (decoded["prefix"]) { + case 'bitcoincash': + case 'simpleledger': + decoded['prefix'] = "simpleledger"; + break; + case 'bchtest': + case 'slptest': + decoded['prefix'] = "slptest"; + break; + default: + throw FormatException("Unsupported address format: $address"); + } final slpAddress = Address._encode(decoded['prefix'], decoded['type'], decoded["hash"]); if (!includePrefix) { @@ -195,15 +219,15 @@ class Address { } static bool isLegacyAddress(String address) { - return detectAddressFormat(address) == 'legacy'; + return detectAddressFormat(address) == formatLegacy; } static bool isCashAddress(String address) { - return detectAddressFormat(address) == 'cashaddr'; + return detectAddressFormat(address) == formatCashAddr; } static bool isSlpAddress(String address) { - return detectAddressFormat(address) == 'slpaddr'; + return detectAddressFormat(address) == formatSlp; } /// Detects type of the address and returns [legacy], [cashaddr] or [slpaddr] @@ -379,28 +403,23 @@ class Address { switch (buffer.first) { case Network.bchPublic: - decoded = { - 'prefix': "bitcoincash", - 'type': "P2PKH", - }; + decoded['prefix'] = "bitcoincash"; + decoded['type'] = "P2PKH"; break; + case Network.bchPublicscriptHash: - decoded = { - 'prefix': "bitcoincash", - 'type': "P2SH", - }; + decoded['prefix'] = "bitcoincash"; + decoded['type'] = "P2SH"; break; + case Network.bchTestnetPublic: - decoded = { - 'prefix': "bchtest", - 'type': "P2PKH", - }; + decoded['prefix'] = "bchtest"; + decoded['type'] = "P2PKH"; break; + case Network.bchTestnetscriptHash: - decoded = { - 'prefix': "bchtest", - 'type': "P2SH", - }; + decoded['prefix'] = "bchtest"; + decoded['type'] = "P2SH"; break; } return decoded; @@ -525,7 +544,7 @@ class Address { "prefix": prefixes[i], "type": type, "hash": hash, - "format": 'slpaddr' + "format": formatSlp }; } From 369ed51964e7ccc95bc6e768b2c920090f1675b4 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 21 Oct 2020 20:14:23 +0530 Subject: [PATCH 056/106] simplified slp judgements --- lib/src/slp.dart | 157 +++++++++++++++++++++++++++++++---------------- pubspec.lock | 4 +- 2 files changed, 106 insertions(+), 55 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index c74bfcd..31bcf02 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -1,14 +1,13 @@ -import 'dart:convert'; import 'dart:typed_data'; import 'package:bitbox/bitbox.dart'; import 'package:hex/hex.dart'; -import 'package:http/http.dart'; import 'package:slp_mdm/slp_mdm.dart'; import 'package:slp_parser/slp_parser.dart'; import 'dart:math' as math; class SLP { - getTokenInformation(String tokenID, [bool decimalConversion = false]) async { + static getTokenInformation(String tokenID, + [bool decimalConversion = false]) async { var res; try { res = await RawTransactions.getRawtransaction(tokenID, verbose: true); @@ -40,7 +39,40 @@ class SLP { return slpMsg; } - mapToSLPUtxoArray({List utxos, String xpriv, String wif}) { + static getAllSlpBalancesAndUtxos(String address) async { + List utxos = await mapToSlpAddressUtxoResultArray(address); + var txIds = []; + utxos.forEach((i) { + txIds.add(i['txid']); + }); + if (txIds.length == 0) { + return []; + } + } + + static Future mapToSlpAddressUtxoResultArray(String address) async { + var result; + try { + result = await Address.utxo([address]); + } catch (e) { + return []; + } + List utxo = []; + return result['utxos'].forEach((txo) => utxo.add({ + 'satoshis': txo.satoshis, + 'txid': txo.txid, + 'amount': txo.amount, + 'confirmations': txo.confirmations, + 'height': txo.height, + 'vout': txo.vout, + 'cashAddress': result.cashAddress, + 'legacyAddress': result.legacyAddress, + 'slpAddress': Address.toSLPAddress(result.legacyAddress), + 'scriptPubKey': result.scriptPubKey, + })); + } + + static mapToSLPUtxoArray({List utxos, String xpriv, String wif}) { List utxo = []; utxos.forEach((txo) => utxo.add({ 'satoshis': new BigInt.from(txo['satoshis']), @@ -48,14 +80,20 @@ class SLP { 'wif': wif, 'txid': txo['txid'], 'vout': txo['vout'], - 'slpTransactionDetails': txo['slpTransactionDetails'], - 'slpUtxoJudgement': txo['slpUtxoJudgement'], - 'slpUtxoJudgementAmount': txo['slpUtxoJudgementAmount'], + 'utxoType': txo['utxoType'], + 'transactionType': txo['transactionType'], + 'tokenId': txo['tokenId'], + 'tokenTicker': txo['tokenTicker'], + 'tokenName': txo['tokenName'], + 'decimals': txo['decimals'], + 'tokenType': txo['tokenType'], + 'tokenQty': txo['tokenQty'], + 'isValid': txo['isValid'], })); return utxo; } - simpleTokenSend( + static simpleTokenSend( {String tokenId, List sendAmounts, List inputUtxos, @@ -127,48 +165,56 @@ class SLP { return result; } - BigInt _preSendSlpJudgementCheck(Map txo, tokenID) { - if (txo['slpUtxoJudgement'] == "undefined" || - txo['slpUtxoJudgement'] == null || - txo['slpUtxoJudgement'] == "UNKNOWN") { - throw Exception( - "There is at least one input UTXO that does not have a proper SLP judgement"); - } - if (txo['slpUtxoJudgement'] == "UNSUPPORTED_TYPE") { - throw Exception( - "There is at least one input UTXO that is an Unsupported SLP type."); - } - if (txo['slpUtxoJudgement'] == "SLP_BATON") { - throw Exception( - "There is at least one input UTXO that is a baton. You can only spend batons in a MINT transaction."); - } - if (txo.containsKey('slpTransactionDetails')) { - if (txo['slpUtxoJudgement'] == "SLP_TOKEN") { - if (!txo.containsKey('slpUtxoJudgementAmount')) { - throw Exception( - "There is at least one input token that does not have the 'slpUtxoJudgementAmount' property set."); - } - if (txo['slpTransactionDetails']['tokenIdHex'] != tokenID) { - throw Exception( - "There is at least one input UTXO that is a different SLP token than the one specified."); - } - if (txo['slpTransactionDetails']['tokenIdHex'] == tokenID) { - return BigInt.from(double.parse(txo['slpUtxoJudgementAmount'])); - } + static BigInt _preSendSlpJudgementCheck(Map txo, tokenID) { + // if (txo['slpUtxoJudgement'] == "undefined" || + // txo['slpUtxoJudgement'] == null || + // txo['slpUtxoJudgement'] == "UNKNOWN") { + // throw Exception( + // "There is at least one input UTXO that does not have a proper SLP judgement"); + // } + // if (txo['slpUtxoJudgement'] == "UNSUPPORTED_TYPE") { + // throw Exception( + // "There is at least one input UTXO that is an Unsupported SLP type."); + // } + // if (txo['slpUtxoJudgement'] == "SLP_BATON") { + // throw Exception( + // "There is at least one input UTXO that is a baton. You can only spend batons in a MINT transaction."); + // } + //if (txo.containsKey('slpTransactionDetails')) { + //if (txo['slpUtxoJudgement'] == "SLP_TOKEN") { + if (txo['utxoType'] == "token") { + if (txo['transactionType'] != 'send') { + throw Exception( + "There is at least one input UTXO that does not have a proper SLP judgement"); + } + //if (!txo.containsKey('slpUtxoJudgementAmount')) { + if (!txo.containsKey('tokenQty')) { + throw Exception( + "There is at least one input token that does not have the 'slpUtxoJudgementAmount' property set."); + } + //if (txo['slpTransactionDetails']['tokenIdHex'] != tokenID) { + if (txo['tokenId'] != tokenID) { + throw Exception( + "There is at least one input UTXO that is a different SLP token than the one specified."); + } + // if (txo['slpTransactionDetails']['tokenIdHex'] == tokenID) { + if (txo['tokenId'] == tokenID) { + //return BigInt.from(num.parse(txo['slpUtxoJudgementAmount'])); + return BigInt.from(txo['tokenQty']); } } + // } return BigInt.from(0); } - _buildRawSendTx( + static _buildRawSendTx( {List slpSendOpReturn, List inputTokenUtxos, List bchInputUtxos, List tokenReceiverAddresses, String bchChangeReceiverAddress, List requiredNonTokenOutputs, - int extraFee, - type = 0x01}) async { + int extraFee}) async { // Check proper address formats are given tokenReceiverAddresses.forEach((addr) { if (!addr.startsWith('simpleledger:')) { @@ -190,25 +236,29 @@ class SLP { // Make sure we're not spending inputs from any other token or baton var tokenInputQty = new BigInt.from(0); inputTokenUtxos.forEach((txo) { - if (txo['slpUtxoJudgement'] == "NOT_SLP") { + //if (txo['slpUtxoJudgement'] == "NOT_SLP") { + if (!txo['isValid']) { return; } - if (txo['slpUtxoJudgement'] == "SLP_TOKEN") { - if (txo['slpTransactionDetails']['tokenIdHex'] != - sendMsgData['tokenId']) { + //if (txo['slpUtxoJudgement'] == "SLP_TOKEN") { + if (txo['utxoType'] == "token") { + // if (txo['slpTransactionDetails']['tokenIdHex'] != + // sendMsgData['tokenId']) { + if (txo['tokenId'] != sendMsgData['tokenId']) { throw Exception("Input UTXOs included a token for another tokenId."); } - tokenInputQty += - BigInt.from(double.parse(txo['slpUtxoJudgementAmount'])); + // tokenInputQty += + // BigInt.from(double.parse(txo['slpUtxoJudgementAmount'])); + tokenInputQty += BigInt.from(txo['tokenQty']); return; } - if (txo['slpUtxoJudgement'] == "SLP_BATON") { - throw Exception("Cannot spend a minting baton."); - } - if (txo['slpUtxoJudgement'] == ['INVALID_TOKEN_DAG'] || - txo['slpUtxoJudgement'] == "INVALID_BATON_DAG") { - throw Exception("Cannot currently spend UTXOs with invalid DAGs."); - } + // if (txo['slpUtxoJudgement'] == "SLP_BATON") { + // throw Exception("Cannot spend a minting baton."); + // } + // if (txo['slpUtxoJudgement'] == ['INVALID_TOKEN_DAG'] || + // txo['slpUtxoJudgement'] == "INVALID_BATON_DAG") { + // throw Exception("Cannot currently spend UTXOs with invalid DAGs."); + // } throw Exception("Cannot spend utxo with no SLP judgement."); }); @@ -341,7 +391,8 @@ class SLP { return {'hex': hex, 'fee': sendCost}; } - int _calculateSendCost(int sendOpReturnLength, int inputUtxoSize, int outputs, + static int _calculateSendCost( + int sendOpReturnLength, int inputUtxoSize, int outputs, {String bchChangeAddress, int feeRate = 1, bool forTokens = true}) { int nonfeeoutputs = 0; if (forTokens) { diff --git a/pubspec.lock b/pubspec.lock index 5ea780c..08c128a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -224,7 +224,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.2.16" typed_data: dependency: transitive description: @@ -240,4 +240,4 @@ packages: source: hosted version: "2.0.8" sdks: - dart: ">=2.6.0 <3.0.0" + dart: ">=2.7.0 <3.0.0" From cf78c439927ae571a7097c7820baa389cb445c50 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 27 Oct 2020 07:39:48 +0530 Subject: [PATCH 057/106] added blockchain end points --- lib/bitbox.dart | 1 + lib/src/blockchain.dart | 98 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 lib/src/blockchain.dart diff --git a/lib/bitbox.dart b/lib/bitbox.dart index 7e5d073..01c9b28 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -5,6 +5,7 @@ export 'src/address.dart'; export 'src/bitbox.dart'; export 'src/bitcoincash.dart'; export 'src/block.dart'; +export 'src/blockchain.dart'; export 'src/cashaccounts.dart'; export 'src/crypto/crypto.dart'; export 'src/ecpair.dart'; diff --git a/lib/src/blockchain.dart b/lib/src/blockchain.dart new file mode 100644 index 0000000..9dcf778 --- /dev/null +++ b/lib/src/blockchain.dart @@ -0,0 +1,98 @@ +import 'utils/rest_api.dart'; + +class Blockchain { + // Hash of the best block in the longest blockchain. + static Future getBestBlockHash() async { + // Returns the hash of the best (tip) block in the longest blockchain. + return await RestApi.sendGetRequest("block/getBestBlockHash"); + } + + // Info regarding blockchain processing + static Future getBlockchainInfo() async { + // Returns an object containing various state info regarding blockchain processing. + return await RestApi.sendGetRequest("block/getBlockchainInfo"); + } + + // Number of blocks in the longest blockchain. + static Future getBlockCount() async { + // Returns the number of blocks in the longest blockchain. + return await RestApi.sendGetRequest("block/getBlockCount"); + } + + // Information about blockheader hash + static Future getBlockHeader(hash) async { + if (hash is String) { + // If verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'. If verbose is true, returns an Object with information about blockheader hash. + return await RestApi.sendGetRequest( + "block/getBlockHeader", '$hash?verbose=true'); + } else if (hash is List) { + // Bulk information about blockheader hash + return await RestApi.sendPostRequest( + "block/getBlockHeader", "hashes", hash); + } else + return throw ("Function parameter must be String for single block and List for multiple blocks"); + } + +// Information about all known tips in the block tree + static Future getChainTips() async { + // Return information about all known tips in the block tree, including the main chain as well as orphaned branches. + return await RestApi.sendGetRequest("block/getChainTips"); + } + + // Proof-of-work difficulty + static Future getDifficulty() async { + // Returns the proof-of-work difficulty as a multiple of the minimum difficulty. + return await RestApi.sendGetRequest("block/getDifficulty"); + } + + // Mempool data for transaction + static Future getMempoolEntry(txid) async { + if (txid is String) { + // Returns mempool data for given transaction + return await RestApi.sendGetRequest("block/getMempoolEntry", txid); + } else if (txid is List) { + // Returns mempool data for given transaction + return await RestApi.sendPostRequest( + "block/getMempoolEntry", "txids", txid); + } else + return throw ("Function parameter must be String for single block and List for multiple blocks"); + } + + // All transaction ids in memory pool. + static Future getRawMempool() async { + // Returns all transaction ids in memory pool as a json array of string transaction ids. + return await RestApi.sendGetRequest("block/getRawMempool"); + } + + // Details about unspent transaction output. + static Future getTxOut(txid, n) async { + // Returns mempool data for given transaction + return await RestApi.sendGetRequest("block/getTxOut", '$txid/$n'); + } + + // Hex-encoded proof that single txid was included. + static Future getTxOutProof(txid) async { + if (txid is String) { + // Returns a hex-encoded proof that 'txid' was included in a block. + return await RestApi.sendGetRequest("block/getTxOutProof", txid); + } else if (txid is List) { + // Returns a hex-encoded proof that multiple txids were included in a block. + return await RestApi.sendPostRequest( + "block/getTxOutProof", "txids", txid); + } else + return throw ("Function parameter must be String for single block and List for multiple blocks"); + } + + // Verify that a single proof points to a transaction in a block + static Future verifyTxOutProof(proof) async { + if (proof is String) { + // Returns a hex-encoded proof that 'txid' was included in a block. + return await RestApi.sendGetRequest("block/verifyTxOutProof", proof); + } else if (proof is List) { + // Returns a hex-encoded proof that multiple txids were included in a block. + return await RestApi.sendPostRequest( + "block/verifyTxOutProof", "proofs", proof); + } else + return throw ("Function parameter must be String for single block and List for multiple blocks"); + } +} From 0f560ee69d13eab5b8b755c4a92363da0a5ed43b Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 28 Oct 2020 10:10:01 +0530 Subject: [PATCH 058/106] fixed bigint decimal bug --- lib/src/address.dart | 1 + lib/src/slp.dart | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index 1ebd03f..cf62df4 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -623,6 +623,7 @@ class Address { for (int i = 0; i < data.length; ++i) { final value = data[i]; final topBits = checksum >> 35; + checksum = ((checksum & 0x07ffffffff) << 5) ^ value; for (int j = 0; j < GENERATOR.length; ++j) { diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 31bcf02..78915b6 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -200,7 +200,7 @@ class SLP { // if (txo['slpTransactionDetails']['tokenIdHex'] == tokenID) { if (txo['tokenId'] == tokenID) { //return BigInt.from(num.parse(txo['slpUtxoJudgementAmount'])); - return BigInt.from(txo['tokenQty']); + return BigInt.from(txo['tokenQty'] * math.pow(10, txo['decimals'])); } } // } @@ -249,7 +249,8 @@ class SLP { } // tokenInputQty += // BigInt.from(double.parse(txo['slpUtxoJudgementAmount'])); - tokenInputQty += BigInt.from(txo['tokenQty']); + tokenInputQty += + BigInt.from(txo['tokenQty'] * math.pow(10, txo['decimals'])); return; } // if (txo['slpUtxoJudgement'] == "SLP_BATON") { From b24b0f476e0356f26b26be040614fd69525c1f99 Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 6 Nov 2020 12:44:16 +0530 Subject: [PATCH 059/106] updated rest api call --- lib/src/blockchain.dart | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/lib/src/blockchain.dart b/lib/src/blockchain.dart index 9dcf778..78c1f10 100644 --- a/lib/src/blockchain.dart +++ b/lib/src/blockchain.dart @@ -4,19 +4,19 @@ class Blockchain { // Hash of the best block in the longest blockchain. static Future getBestBlockHash() async { // Returns the hash of the best (tip) block in the longest blockchain. - return await RestApi.sendGetRequest("block/getBestBlockHash"); + return await RestApi.sendGetRequest("blockchain/getBestBlockHash"); } // Info regarding blockchain processing static Future getBlockchainInfo() async { // Returns an object containing various state info regarding blockchain processing. - return await RestApi.sendGetRequest("block/getBlockchainInfo"); + return await RestApi.sendGetRequest("blockchain/getBlockchainInfo"); } // Number of blocks in the longest blockchain. static Future getBlockCount() async { // Returns the number of blocks in the longest blockchain. - return await RestApi.sendGetRequest("block/getBlockCount"); + return await RestApi.sendGetRequest("blockchain/getBlockCount"); } // Information about blockheader hash @@ -24,11 +24,11 @@ class Blockchain { if (hash is String) { // If verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'. If verbose is true, returns an Object with information about blockheader hash. return await RestApi.sendGetRequest( - "block/getBlockHeader", '$hash?verbose=true'); + "blockchain/getBlockHeader", '$hash?verbose=true'); } else if (hash is List) { // Bulk information about blockheader hash return await RestApi.sendPostRequest( - "block/getBlockHeader", "hashes", hash); + "blockchain/getBlockHeader", "hashes", hash); } else return throw ("Function parameter must be String for single block and List for multiple blocks"); } @@ -36,20 +36,20 @@ class Blockchain { // Information about all known tips in the block tree static Future getChainTips() async { // Return information about all known tips in the block tree, including the main chain as well as orphaned branches. - return await RestApi.sendGetRequest("block/getChainTips"); + return await RestApi.sendGetRequest("blockchain/getChainTips"); } // Proof-of-work difficulty static Future getDifficulty() async { // Returns the proof-of-work difficulty as a multiple of the minimum difficulty. - return await RestApi.sendGetRequest("block/getDifficulty"); + return await RestApi.sendGetRequest("blockchain/getDifficulty"); } // Mempool data for transaction static Future getMempoolEntry(txid) async { if (txid is String) { // Returns mempool data for given transaction - return await RestApi.sendGetRequest("block/getMempoolEntry", txid); + return await RestApi.sendGetRequest("blockchain/getMempoolEntry", txid); } else if (txid is List) { // Returns mempool data for given transaction return await RestApi.sendPostRequest( @@ -61,20 +61,20 @@ class Blockchain { // All transaction ids in memory pool. static Future getRawMempool() async { // Returns all transaction ids in memory pool as a json array of string transaction ids. - return await RestApi.sendGetRequest("block/getRawMempool"); + return await RestApi.sendGetRequest("blockchain/getRawMempool"); } // Details about unspent transaction output. static Future getTxOut(txid, n) async { // Returns mempool data for given transaction - return await RestApi.sendGetRequest("block/getTxOut", '$txid/$n'); + return await RestApi.sendGetRequest("blockchain/getTxOut", '$txid/$n'); } // Hex-encoded proof that single txid was included. static Future getTxOutProof(txid) async { if (txid is String) { // Returns a hex-encoded proof that 'txid' was included in a block. - return await RestApi.sendGetRequest("block/getTxOutProof", txid); + return await RestApi.sendGetRequest("blockchain/getTxOutProof", txid); } else if (txid is List) { // Returns a hex-encoded proof that multiple txids were included in a block. return await RestApi.sendPostRequest( @@ -87,11 +87,11 @@ class Blockchain { static Future verifyTxOutProof(proof) async { if (proof is String) { // Returns a hex-encoded proof that 'txid' was included in a block. - return await RestApi.sendGetRequest("block/verifyTxOutProof", proof); + return await RestApi.sendGetRequest("blockchain/verifyTxOutProof", proof); } else if (proof is List) { // Returns a hex-encoded proof that multiple txids were included in a block. return await RestApi.sendPostRequest( - "block/verifyTxOutProof", "proofs", proof); + "blockchain/verifyTxOutProof", "proofs", proof); } else return throw ("Function parameter must be String for single block and List for multiple blocks"); } From b52d66ea5eddcf700b43eece251da9012524ab20 Mon Sep 17 00:00:00 2001 From: Radical Date: Sat, 16 Jan 2021 11:52:00 +0530 Subject: [PATCH 060/106] added NFT Child Send --- lib/src/slp.dart | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 78915b6..ea5efad 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -151,8 +151,16 @@ class SLP { if (sendChange) { tokenReceiverAddresses.add(slpChangeReceiverAddress); } + + int tokenType = tokenInfo['tokenType']; + // 3 Create the Send OP_RETURN message - var sendOpReturn = Send(HEX.decode(tokenId), amounts); + var sendOpReturn; + if (tokenType == 1) { + sendOpReturn = Send(HEX.decode(tokenId), amounts); + } else { + sendOpReturn = Nft1ChildSend(HEX.decode(tokenId), amounts[0]); + } // 4 Create the raw Send transaction hex Map result = await _buildRawSendTx( slpSendOpReturn: sendOpReturn, @@ -183,10 +191,10 @@ class SLP { //if (txo.containsKey('slpTransactionDetails')) { //if (txo['slpUtxoJudgement'] == "SLP_TOKEN") { if (txo['utxoType'] == "token") { - if (txo['transactionType'] != 'send') { - throw Exception( - "There is at least one input UTXO that does not have a proper SLP judgement"); - } + //if (txo['transactionType'] != 'send') { + // throw Exception( + // "There is at least one input UTXO that does not have a proper SLP judgement"); + // } //if (!txo.containsKey('slpUtxoJudgementAmount')) { if (!txo.containsKey('tokenQty')) { throw Exception( From bc905f657186d78a70705ae7631f7b5c68df5639 Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 29 Jan 2021 14:58:01 +0530 Subject: [PATCH 061/106] fix utxo isvalid bug --- lib/src/slp.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index ea5efad..652d02e 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -245,6 +245,9 @@ class SLP { var tokenInputQty = new BigInt.from(0); inputTokenUtxos.forEach((txo) { //if (txo['slpUtxoJudgement'] == "NOT_SLP") { + if (txo['isValid'] == null) { + return; + } if (!txo['isValid']) { return; } From 152c1e7bf4fb2dc07f0f6a153aa91d953dc7d758 Mon Sep 17 00:00:00 2001 From: Radical Date: Sun, 31 Jan 2021 13:41:57 +0530 Subject: [PATCH 062/106] add nft group send --- lib/src/slp.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 652d02e..5438939 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -158,8 +158,12 @@ class SLP { var sendOpReturn; if (tokenType == 1) { sendOpReturn = Send(HEX.decode(tokenId), amounts); - } else { + } else if (tokenType == 129) { + sendOpReturn = Nft1GroupSend(HEX.decode(tokenId), amounts); + } else if (tokenType == 65) { sendOpReturn = Nft1ChildSend(HEX.decode(tokenId), amounts[0]); + } else { + return throw Exception('Invalid token type'); } // 4 Create the raw Send transaction hex Map result = await _buildRawSendTx( From 4ce85e7024447ead7ee87d4b8850170ac77f363a Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 16 Feb 2021 18:58:44 +0530 Subject: [PATCH 063/106] add slp parser --- lib/src/slp.dart | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 5438939..34fc2b4 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -93,6 +93,11 @@ class SLP { return utxo; } + static parseSlp(String scriptPubKey) { + var slpMsg = parseSLP(HEX.decode(scriptPubKey)); + return slpMsg.toMap(raw: true); + } + static simpleTokenSend( {String tokenId, List sendAmounts, @@ -167,13 +172,14 @@ class SLP { } // 4 Create the raw Send transaction hex Map result = await _buildRawSendTx( - slpSendOpReturn: sendOpReturn, - inputTokenUtxos: inputUtxos, - bchInputUtxos: bchInputUtxos, - tokenReceiverAddresses: tokenReceiverAddresses, - bchChangeReceiverAddress: bchChangeReceiverAddress, - requiredNonTokenOutputs: requiredNonTokenOutputs, - extraFee: extraFee); + slpSendOpReturn: sendOpReturn, + inputTokenUtxos: inputUtxos, + bchInputUtxos: bchInputUtxos, + tokenReceiverAddresses: tokenReceiverAddresses, + bchChangeReceiverAddress: bchChangeReceiverAddress, + requiredNonTokenOutputs: requiredNonTokenOutputs, + extraFee: extraFee, + ); return result; } From b666a10868a3013e86a9c7690db5e1fc5c6a8601 Mon Sep 17 00:00:00 2001 From: Radical Date: Mon, 22 Feb 2021 23:59:56 +0530 Subject: [PATCH 064/106] remove extra tx fee --- lib/src/slp.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 34fc2b4..ee31513 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -351,7 +351,7 @@ class SLP { inputTokenUtxos.length + bchInputUtxos.length, tokenReceiverAddresses.length + bchOnlyCount, bchChangeAddress: bchChangeReceiverAddress, - feeRate: extraFee != null ? extraFee : 1); + feeRate: extraFee ?? 1); // Compute BCH change amount BigInt bchChangeAfterFeeSatoshis = @@ -410,7 +410,9 @@ class SLP { if (inValue - outValue < hex.length / 2) { return {'hex': null, 'fee': "Insufficient fee"}; } - return {'hex': hex, 'fee': sendCost}; + + int _extraFee = (tokenReceiverAddresses.length + bchOnlyCount) * 546; + return {'hex': hex, 'fee': sendCost - _extraFee}; } static int _calculateSendCost( From 76b6f756701095ae03a4b50e98811990be357c26 Mon Sep 17 00:00:00 2001 From: devanshig Date: Fri, 26 Mar 2021 10:04:46 +0530 Subject: [PATCH 065/106] upgraded dependencies --- lib/src/cashaccounts.dart | 15 ++++++++------- lib/src/rawtransactions.dart | 7 ++++--- lib/src/utils/rest_api.dart | 4 ++-- pubspec.yaml | 10 +++++----- 4 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/src/cashaccounts.dart b/lib/src/cashaccounts.dart index 091c720..198e983 100644 --- a/lib/src/cashaccounts.dart +++ b/lib/src/cashaccounts.dart @@ -8,20 +8,20 @@ class CashAccounts { if (collision != null) { col = collision.toString(); } - final response = await http.get( - "https://rest.bitcoin.com/v2/cashAccounts/lookup/$account/$number/$col"); + final response = await http.get(Uri.parse( + "https://rest.bitcoin.com/v2/cashAccounts/lookup/$account/$number/$col")); return json.decode(response.body); } static Future check(String account, int number) async { - final response = await http - .get("https://rest.bitcoin.com/v2/cashAccounts/check/$account/$number"); + final response = await http.get(Uri.parse( + "https://rest.bitcoin.com/v2/cashAccounts/check/$account/$number")); return json.decode(response.body); } static Future reverseLookup(String cashAddress) async { - final response = await http.get( - "https://rest.bitcoin.com/v2/cashAccounts/reverseLookup/$cashAddress"); + final response = await http.get(Uri.parse( + "https://rest.bitcoin.com/v2/cashAccounts/reverseLookup/$cashAddress")); return json.decode(response.body); } @@ -30,7 +30,8 @@ class CashAccounts { 'name': name, 'payments': [address] }; - final response = await http.post('https://api.cashaccount.info/register', + final response = await http.post( + Uri.parse('https://api.cashaccount.info/register'), headers: {'Content-Type': 'application/json'}, body: jsonEncode(register)); Map data = jsonDecode(response.body); diff --git a/lib/src/rawtransactions.dart b/lib/src/rawtransactions.dart index c914ae0..90ace2e 100644 --- a/lib/src/rawtransactions.dart +++ b/lib/src/rawtransactions.dart @@ -37,8 +37,8 @@ class RawTransactions { static Future getRawtransaction(String script, {bool verbose = true, bool testnet = false}) async { var _restURL = testnet ? 'trest' : 'rest'; - final response = await http.get( - "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction/$script?verbose=$verbose"); + final response = await http.get(Uri.parse( + "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction/$script?verbose=$verbose")); return jsonDecode(response.body); } @@ -47,7 +47,8 @@ class RawTransactions { {bool verbose = true, bool testnet = false}) async { var _restURL = testnet ? 'trest' : 'rest'; final response = await http.post( - "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction", + Uri.parse( + "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction"), headers: {"content-type": "application/json"}, body: jsonEncode({'txids': scripts, "verbose": verbose})); return jsonDecode(response.body); diff --git a/lib/src/utils/rest_api.dart b/lib/src/utils/rest_api.dart index 04c5605..5919d13 100644 --- a/lib/src/utils/rest_api.dart +++ b/lib/src/utils/rest_api.dart @@ -10,7 +10,7 @@ class RestApi { static Future sendGetRequest(String path, [String parameter = ""]) async { - final response = await http.get("$_restUrl$path/$parameter"); + final response = await http.get(Uri.parse("$_restUrl$path/$parameter")); if (response.statusCode == 200) { return jsonDecode(response.body); @@ -26,7 +26,7 @@ class RestApi { postKey = "addresses"; } final response = await http.post( - "$_restUrl$path", + Uri.parse("$_restUrl$path"), headers: {"content-type": "application/json"}, body: jsonEncode({postKey: data}), ); diff --git a/pubspec.yaml b/pubspec.yaml index e5e0ffd..d3cfd69 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,7 +10,7 @@ environment: dependencies: flutter: sdk: flutter - http: ^0.12.0+1 + http: ^0.13.1 bip39: git: url: https://github.com/RomitRadical/bip39 @@ -25,11 +25,11 @@ dependencies: ref: master hex: ^0.1.2 bs58check: ^1.0.1 - fixnum: ^0.10.9 - meta: ^1.1.6 - buffer: ^1.0.6 + fixnum: ^1.0.0 + meta: ^1.3.0 + buffer: ^1.1.0 slp_parser: ^0.0.3 - slp_mdm: ^0.0.1 + slp_mdm: ^0.1.0 dev_dependencies: flutter_test: From 77d0ceb0618a25decb8fa86e99402ab0664d5aae Mon Sep 17 00:00:00 2001 From: devanshig Date: Fri, 26 Mar 2021 13:04:14 +0530 Subject: [PATCH 066/106] upgraded packages --- pubspec.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index d3cfd69..7820a41 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,8 +13,8 @@ dependencies: http: ^0.13.1 bip39: git: - url: https://github.com/RomitRadical/bip39 - ref: master + url: https://github.com/DevanshiGarg08/bip39 + ref: staging pointycastle: git: url: https://github.com/jbdtky/pc-dart.git @@ -24,13 +24,15 @@ dependencies: url: https://github.com/jbdtky/bip32-dart ref: master hex: ^0.1.2 - bs58check: ^1.0.1 + bs58check: ">=1.0.1 <2.0.0" fixnum: ^1.0.0 meta: ^1.3.0 buffer: ^1.1.0 slp_parser: ^0.0.3 slp_mdm: ^0.1.0 +dependency_overrides: + crypto: ^3.0.0 dev_dependencies: flutter_test: sdk: flutter From 4610b90db8a365aeae0553995808ef3ae7a13324 Mon Sep 17 00:00:00 2001 From: devanshig Date: Fri, 26 Mar 2021 15:08:33 +0530 Subject: [PATCH 067/106] upgraded bs58check_dart package --- pubspec.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 7820a41..322bfd3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,8 +23,8 @@ dependencies: git: url: https://github.com/jbdtky/bip32-dart ref: master - hex: ^0.1.2 - bs58check: ">=1.0.1 <2.0.0" + hex: ^0.2.0 + bs58check_dart: ^2.0.0+1 fixnum: ^1.0.0 meta: ^1.3.0 buffer: ^1.1.0 @@ -33,6 +33,7 @@ dependencies: dependency_overrides: crypto: ^3.0.0 + hex: ^0.2.0 dev_dependencies: flutter_test: sdk: flutter From aa03e580aad14d95beda25093f7916f2fcbb843e Mon Sep 17 00:00:00 2001 From: devanshig Date: Fri, 26 Mar 2021 16:10:54 +0530 Subject: [PATCH 068/106] upgraded to bs58check_dart version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 322bfd3..b18cd6c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,6 @@ dependencies: url: https://github.com/jbdtky/bip32-dart ref: master hex: ^0.2.0 - bs58check_dart: ^2.0.0+1 fixnum: ^1.0.0 meta: ^1.3.0 buffer: ^1.1.0 @@ -34,6 +33,7 @@ dependencies: dependency_overrides: crypto: ^3.0.0 hex: ^0.2.0 + bs58check_dart: ^2.0.0+1 dev_dependencies: flutter_test: sdk: flutter From c5dbf4816ee1a954e0df7c0228d91a16683061df Mon Sep 17 00:00:00 2001 From: devanshig Date: Fri, 26 Mar 2021 16:21:25 +0530 Subject: [PATCH 069/106] Upgraded bip32 --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index b18cd6c..a15b480 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,8 +21,8 @@ dependencies: ref: master bip32: git: - url: https://github.com/jbdtky/bip32-dart - ref: master + url: https://github.com/DevanshiGarg08/bip32-dart + ref: staging hex: ^0.2.0 fixnum: ^1.0.0 meta: ^1.3.0 From 08df2d77b392ec74bdd30fabd0ea084b09996495 Mon Sep 17 00:00:00 2001 From: devanshig Date: Fri, 26 Mar 2021 16:27:09 +0530 Subject: [PATCH 070/106] resolved errors --- lib/src/address.dart | 2 +- lib/src/hdnode.dart | 2 +- lib/src/transactionbuilder.dart | 2 +- lib/src/utils/p2pkh.dart | 2 +- lib/src/utils/p2sh.dart | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index cf62df4..e992feb 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'utils/rest_api.dart'; -import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:bs58check_dart/bs58check.dart' as bs58check; import 'utils/network.dart'; import 'package:fixnum/fixnum.dart'; diff --git a/lib/src/hdnode.dart b/lib/src/hdnode.dart index 220016b..4679098 100644 --- a/lib/src/hdnode.dart +++ b/lib/src/hdnode.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'crypto/ecurve.dart'; import 'address.dart'; import 'package:hex/hex.dart'; -import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:bs58check_dart/bs58check.dart' as bs58check; import 'crypto/crypto.dart'; import 'ecpair.dart'; diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index 9532fe4..eaba9b0 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; import 'package:hex/hex.dart'; -import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:bs58check_dart/bs58check.dart' as bs58check; import 'address.dart'; import 'crypto/crypto.dart'; import 'utils/network.dart'; diff --git a/lib/src/utils/p2pkh.dart b/lib/src/utils/p2pkh.dart index 71dbd3a..01df82f 100644 --- a/lib/src/utils/p2pkh.dart +++ b/lib/src/utils/p2pkh.dart @@ -3,7 +3,7 @@ import '../crypto/crypto.dart'; import '../utils/opcodes.dart'; import 'package:meta/meta.dart'; import 'package:bip32/src/utils/ecurve.dart' show isPoint; -import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:bs58check_dart/bs58check.dart' as bs58check; import 'script.dart' as bscript; import 'network.dart'; diff --git a/lib/src/utils/p2sh.dart b/lib/src/utils/p2sh.dart index 9ebdf3a..63b4f5c 100644 --- a/lib/src/utils/p2sh.dart +++ b/lib/src/utils/p2sh.dart @@ -3,7 +3,7 @@ import '../crypto/crypto.dart'; import '../utils/opcodes.dart'; import 'package:meta/meta.dart'; import 'package:bip32/src/utils/ecurve.dart' show isPoint; -import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:bs58check_dart/bs58check.dart' as bs58check; import 'script.dart' as bscript; import 'network.dart'; From 3fb94f6a0e1f3e7ef7a90f925b4702ec043af71b Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 3 Jun 2021 16:59:09 +0530 Subject: [PATCH 071/106] add build incomplete to slp --- lib/src/slp.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index ee31513..b40d003 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -108,7 +108,8 @@ class SLP { String bchChangeReceiverAddress, List requiredNonTokenOutputs, int extraFee, - int type = 0x01}) async { + int type = 0x01, + bool buildIncomplete = false}) async { List amounts = []; BigInt totalAmount = BigInt.from(0); if (tokenId is! String) { @@ -179,6 +180,7 @@ class SLP { bchChangeReceiverAddress: bchChangeReceiverAddress, requiredNonTokenOutputs: requiredNonTokenOutputs, extraFee: extraFee, + buildIncomplete: buildIncomplete, ); return result; } @@ -232,7 +234,8 @@ class SLP { List tokenReceiverAddresses, String bchChangeReceiverAddress, List requiredNonTokenOutputs, - int extraFee}) async { + int extraFee, + bool buildIncomplete}) async { // Check proper address formats are given tokenReceiverAddresses.forEach((addr) { if (!addr.startsWith('simpleledger:')) { @@ -399,7 +402,12 @@ class SLP { // Build the transaction to hex and return // warn user if the transaction was not fully signed - String hex = transactionBuilder.build().toHex(); + String hex; + if (buildIncomplete) { + hex = transactionBuilder.buildIncomplete().toHex(); + } else { + hex = transactionBuilder.build().toHex(); + } // Check For Low Fee int outValue = 0; From 04a15f9ececbafa94f41b34ffd71dc2c0f401685 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 3 Jun 2021 17:00:30 +0530 Subject: [PATCH 072/106] merge --- pubspec.lock | 81 ++++++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 08c128a..594ff72 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,23 +7,23 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.5.0" bip32: dependency: "direct main" description: path: "." - ref: master - resolved-ref: fc0cbf2b7f297fbdd6b5d24f836a23d97c993e47 - url: "https://github.com/jbdtky/bip32-dart" + ref: staging + resolved-ref: "8fd271153e687304a260a3b65bd96ba0bd272d8f" + url: "https://github.com/DevanshiGarg08/bip32-dart" source: git version: "1.0.5" bip39: dependency: "direct main" description: path: "." - ref: master - resolved-ref: "15b0567ce928095d3bcd65a4113359c2e5353dc8" - url: "https://github.com/RomitRadical/bip39" + ref: staging + resolved-ref: "8f1a58c13c3f4c1497834a99e13ba28cd670be95" + url: "https://github.com/DevanshiGarg08/bip39" source: git version: "1.0.3" boolean_selector: @@ -32,42 +32,49 @@ packages: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" - bs58check: - dependency: "direct main" + version: "2.1.0" + bs58check_dart: + dependency: "direct overridden" description: - name: bs58check + name: bs58check_dart url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0+1" buffer: dependency: "direct main" description: name: buffer url: "https://pub.dartlang.org" source: hosted - version: "1.0.6" + version: "1.1.1" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "1.1.0" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.15.0" convert: dependency: transitive description: @@ -76,26 +83,26 @@ packages: source: hosted version: "2.1.1" crypto: - dependency: transitive + dependency: "direct overridden" description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "3.0.1" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" fixnum: dependency: "direct main" description: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.9" + version: "1.0.0" flutter: dependency: "direct main" description: flutter @@ -112,49 +119,49 @@ packages: name: hex url: "https://pub.dartlang.org" source: hosted - version: "0.1.2" + version: "0.2.0" http: dependency: "direct main" description: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.0+2" + version: "0.13.3" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.3" + version: "4.0.0" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.10" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.1.8" + version: "1.3.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.8.0+1" + version: "1.11.0" pointycastle: dependency: "direct main" description: @@ -175,7 +182,7 @@ packages: name: slp_mdm url: "https://pub.dartlang.org" source: hosted - version: "0.0.1" + version: "0.1.0" slp_parser: dependency: "direct main" description: @@ -189,55 +196,55 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.16" + version: "0.2.19" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.0.8" + version: "2.1.0" sdks: - dart: ">=2.7.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" From 7c752dedd7ca12e55095778e57765e0fc77d46ac Mon Sep 17 00:00:00 2001 From: Radical Date: Fri, 25 Jun 2021 00:29:28 +0530 Subject: [PATCH 073/106] add anyone can pay flag for slp tx --- lib/src/slp.dart | 66 +++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index b40d003..ff4fe6e 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -98,18 +98,20 @@ class SLP { return slpMsg.toMap(raw: true); } - static simpleTokenSend( - {String tokenId, - List sendAmounts, - List inputUtxos, - List bchInputUtxos, - List tokenReceiverAddresses, - String slpChangeReceiverAddress, - String bchChangeReceiverAddress, - List requiredNonTokenOutputs, - int extraFee, - int type = 0x01, - bool buildIncomplete = false}) async { + static simpleTokenSend({ + String tokenId, + List sendAmounts, + List inputUtxos, + List bchInputUtxos, + List tokenReceiverAddresses, + String slpChangeReceiverAddress, + String bchChangeReceiverAddress, + List requiredNonTokenOutputs, + int extraFee, + int type = 0x01, + bool buildIncomplete = false, + bool anyoneCanPay = false, + }) async { List amounts = []; BigInt totalAmount = BigInt.from(0); if (tokenId is! String) { @@ -173,15 +175,15 @@ class SLP { } // 4 Create the raw Send transaction hex Map result = await _buildRawSendTx( - slpSendOpReturn: sendOpReturn, - inputTokenUtxos: inputUtxos, - bchInputUtxos: bchInputUtxos, - tokenReceiverAddresses: tokenReceiverAddresses, - bchChangeReceiverAddress: bchChangeReceiverAddress, - requiredNonTokenOutputs: requiredNonTokenOutputs, - extraFee: extraFee, - buildIncomplete: buildIncomplete, - ); + slpSendOpReturn: sendOpReturn, + inputTokenUtxos: inputUtxos, + bchInputUtxos: bchInputUtxos, + tokenReceiverAddresses: tokenReceiverAddresses, + bchChangeReceiverAddress: bchChangeReceiverAddress, + requiredNonTokenOutputs: requiredNonTokenOutputs, + extraFee: extraFee, + buildIncomplete: buildIncomplete, + anyoneCanPay: anyoneCanPay); return result; } @@ -235,7 +237,8 @@ class SLP { String bchChangeReceiverAddress, List requiredNonTokenOutputs, int extraFee, - bool buildIncomplete}) async { + bool buildIncomplete, + bool anyoneCanPay}) async { // Check proper address formats are given tokenReceiverAddresses.forEach((addr) { if (!addr.startsWith('simpleledger:')) { @@ -380,8 +383,14 @@ class SLP { } else { paymentKeyPair = ECPair.fromWIF(wif); } - transactionBuilder.sign(slpIndex, paymentKeyPair, i['satoshis'].toInt(), - Transaction.SIGHASH_ALL); + + transactionBuilder.sign( + slpIndex, + paymentKeyPair, + i['satoshis'].toInt(), + anyoneCanPay + ? Transaction.SIGHASH_ANYONECANPAY + : Transaction.SIGHASH_ALL); slpIndex++; }); @@ -395,8 +404,13 @@ class SLP { } else { paymentKeyPair = ECPair.fromWIF(wif); } - transactionBuilder.sign(bchIndex, paymentKeyPair, i['satoshis'].toInt(), - Transaction.SIGHASH_ALL); + transactionBuilder.sign( + bchIndex, + paymentKeyPair, + i['satoshis'].toInt(), + anyoneCanPay + ? Transaction.SIGHASH_ANYONECANPAY + : Transaction.SIGHASH_ALL); bchIndex++; }); From 06303d638c0809469a74e80ff0068aae301d3551 Mon Sep 17 00:00:00 2001 From: Radical Date: Tue, 6 Jul 2021 11:41:52 +0530 Subject: [PATCH 074/106] add default hashtype --- lib/src/slp.dart | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index ff4fe6e..f9af639 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -110,7 +110,7 @@ class SLP { int extraFee, int type = 0x01, bool buildIncomplete = false, - bool anyoneCanPay = false, + int hashType = Transaction.SIGHASH_ALL, }) async { List amounts = []; BigInt totalAmount = BigInt.from(0); @@ -183,7 +183,7 @@ class SLP { requiredNonTokenOutputs: requiredNonTokenOutputs, extraFee: extraFee, buildIncomplete: buildIncomplete, - anyoneCanPay: anyoneCanPay); + hashType: hashType); return result; } @@ -238,7 +238,7 @@ class SLP { List requiredNonTokenOutputs, int extraFee, bool buildIncomplete, - bool anyoneCanPay}) async { + int hashType}) async { // Check proper address formats are given tokenReceiverAddresses.forEach((addr) { if (!addr.startsWith('simpleledger:')) { @@ -372,6 +372,7 @@ class SLP { bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt()); } + if (hashType == null) hashType = Transaction.SIGHASH_ALL; // Sign txn and add sig to p2pkh input with xpriv, int slpIndex = 0; inputTokenUtxos.forEach((i) { @@ -385,12 +386,7 @@ class SLP { } transactionBuilder.sign( - slpIndex, - paymentKeyPair, - i['satoshis'].toInt(), - anyoneCanPay - ? Transaction.SIGHASH_ANYONECANPAY - : Transaction.SIGHASH_ALL); + slpIndex, paymentKeyPair, i['satoshis'].toInt(), hashType); slpIndex++; }); @@ -405,12 +401,7 @@ class SLP { paymentKeyPair = ECPair.fromWIF(wif); } transactionBuilder.sign( - bchIndex, - paymentKeyPair, - i['satoshis'].toInt(), - anyoneCanPay - ? Transaction.SIGHASH_ANYONECANPAY - : Transaction.SIGHASH_ALL); + bchIndex, paymentKeyPair, i['satoshis'].toInt(), hashType); bchIndex++; }); From dba82633f05251055d53805371789c2c13389584 Mon Sep 17 00:00:00 2001 From: Radical Date: Wed, 7 Jul 2021 02:23:50 +0530 Subject: [PATCH 075/106] add extra BCH param --- lib/src/slp.dart | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index f9af639..a330c54 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -108,6 +108,7 @@ class SLP { String bchChangeReceiverAddress, List requiredNonTokenOutputs, int extraFee, + int extraBCH = 0, int type = 0x01, bool buildIncomplete = false, int hashType = Transaction.SIGHASH_ALL, @@ -182,6 +183,7 @@ class SLP { bchChangeReceiverAddress: bchChangeReceiverAddress, requiredNonTokenOutputs: requiredNonTokenOutputs, extraFee: extraFee, + extraBCH: extraBCH, buildIncomplete: buildIncomplete, hashType: hashType); return result; @@ -236,6 +238,7 @@ class SLP { List tokenReceiverAddresses, String bchChangeReceiverAddress, List requiredNonTokenOutputs, + int extraBCH, int extraFee, bool buildIncomplete, int hashType}) async { @@ -367,9 +370,9 @@ class SLP { } // Add change, if any - if (bchChangeAfterFeeSatoshis > new BigInt.from(546)) { + if (bchChangeAfterFeeSatoshis + new BigInt.from(extraBCH) > new BigInt.from(546)) { transactionBuilder.addOutput( - bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt()); + bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt() + extraBCH); } if (hashType == null) hashType = Transaction.SIGHASH_ALL; @@ -405,11 +408,14 @@ class SLP { bchIndex++; }); + int _extraFee = (tokenReceiverAddresses.length + bchOnlyCount) * 546; + // Build the transaction to hex and return // warn user if the transaction was not fully signed String hex; if (buildIncomplete) { hex = transactionBuilder.buildIncomplete().toHex(); + return {'hex': hex, 'fee': sendCost - _extraFee}; } else { hex = transactionBuilder.build().toHex(); } @@ -424,7 +430,6 @@ class SLP { return {'hex': null, 'fee': "Insufficient fee"}; } - int _extraFee = (tokenReceiverAddresses.length + bchOnlyCount) * 546; return {'hex': hex, 'fee': sendCost - _extraFee}; } From 61f28194e373ad0f23dfbafd22c7455305e044d4 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 8 Jul 2021 10:57:42 +0530 Subject: [PATCH 076/106] add default value to script --- lib/src/transaction.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 2405b2b..ac7609b 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -230,8 +230,6 @@ class Transaction { tOffset += slice.length; } - ; - void writeUint32(int i) { tBuffer.buffer.asByteData().setUint32(tOffset, i, Endian.little); tOffset += 4; @@ -252,9 +250,9 @@ class Transaction { writeSlice(slice); } - Uint8List hashPrevoutputs; - Uint8List hashSequence; - Uint8List hashOutputs; + Uint8List hashPrevoutputs = zero; + Uint8List hashSequence = zero; + Uint8List hashOutputs = zero; if ((hashType & SIGHASH_ANYONECANPAY == 0)) { tBuffer = Uint8List(36 * this.inputs.length); From 669427ce3db743da347e09ca240ff433d6cd5502 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Tue, 11 Jan 2022 06:29:11 -0800 Subject: [PATCH 077/106] update dep --- pubspec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a15b480..4ffefcf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,7 +28,10 @@ dependencies: meta: ^1.3.0 buffer: ^1.1.0 slp_parser: ^0.0.3 - slp_mdm: ^0.1.0 + slp_mdm: + git: + url: https://github.com/zapit-io/slp-metadatamaker.dart.git + ref: master dependency_overrides: crypto: ^3.0.0 From 388cc68fe6fc269ade02bbd496913bac8e880020 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Tue, 11 Jan 2022 20:29:40 +0530 Subject: [PATCH 078/106] add magichash --- lib/src/utils/magic_hash.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 lib/src/utils/magic_hash.dart diff --git a/lib/src/utils/magic_hash.dart b/lib/src/utils/magic_hash.dart new file mode 100644 index 0000000..fefc947 --- /dev/null +++ b/lib/src/utils/magic_hash.dart @@ -0,0 +1,16 @@ +import 'dart:typed_data'; +import 'dart:convert'; +import '../../bitbox.dart'; + +Uint8List magicHash(String message) { + Uint8List messagePrefix = + Uint8List.fromList(utf8.encode('\x18Bitcoin Signed Message:\n')); + int messageVISize = encodingLength(message.length); + int length = messagePrefix.length + messageVISize + message.length; + Uint8List buffer = new Uint8List(length); + buffer.setRange(0, messagePrefix.length, messagePrefix); + encode(message.length, buffer, messagePrefix.length); + buffer.setRange( + messagePrefix.length + messageVISize, length, utf8.encode(message)); + return Crypto.hash256(buffer); +} From c02119c98ee6cd5fd127428a5ee416f46eb47eff Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Tue, 11 Jan 2022 20:40:46 +0530 Subject: [PATCH 079/106] add sign bitcoin message function --- lib/src/bitcoincash.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index eab2b44..690f2a3 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -1,3 +1,8 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:bitbox/src/utils/magic_hash.dart'; + import 'utils/bip21.dart'; /// Bitcoin Cash specific utilities @@ -26,4 +31,10 @@ class BitcoinCash { static Map decodeBIP21(String uri) { return Bip21.decode(uri); } + + // Sign a string message with privateKey in Bitcoin Signature format + static String signMessage(String message) { + Uint8List signatureBuffer = magicHash(message); + return utf8.decode(signatureBuffer); + } } From ffe7a22708f47dc2f1db26d60219abd6ca114934 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Tue, 11 Jan 2022 20:40:56 +0530 Subject: [PATCH 080/106] update packages --- pubspec.lock | 32 ++++++++++++++++++-------------- pubspec.yaml | 5 ++++- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 594ff72..f8ebb7b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.8.1" bip32: dependency: "direct main" description: @@ -60,7 +60,7 @@ packages: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" clock: dependency: transitive description: @@ -81,7 +81,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.1" crypto: dependency: "direct overridden" description: @@ -147,7 +147,7 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0" + version: "1.7.0" path: dependency: transitive description: @@ -179,24 +179,28 @@ packages: slp_mdm: dependency: "direct main" description: - name: slp_mdm - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.0" + path: "." + ref: master + resolved-ref: a28df2937d97612cafb681352bcc1fa71be99e94 + url: "https://github.com/zapit-io/slp-metadatamaker.dart.git" + source: git + version: "1.0.0" slp_parser: dependency: "direct main" description: - name: slp_parser - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.3" + path: "." + ref: master + resolved-ref: e0e53d225345e09cba3a1b056f655431cdcd36be + url: "https://github.com/zapit-io/slp-parser.dart.git" + source: git + version: "0.1.0" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -231,7 +235,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19" + version: "0.4.2" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4ffefcf..d76ff92 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,10 @@ dependencies: fixnum: ^1.0.0 meta: ^1.3.0 buffer: ^1.1.0 - slp_parser: ^0.0.3 + slp_parser: + git: + url: https://github.com/zapit-io/slp-parser.dart.git + ref: master slp_mdm: git: url: https://github.com/zapit-io/slp-metadatamaker.dart.git From 604056f988fb467201a273a230fd0829b58bed48 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Tue, 11 Jan 2022 07:31:29 -0800 Subject: [PATCH 081/106] return uint8list sig --- lib/src/bitcoincash.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index 690f2a3..fb9c7d0 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -33,8 +33,9 @@ class BitcoinCash { } // Sign a string message with privateKey in Bitcoin Signature format - static String signMessage(String message) { + static String signMessage(String message, [returnString = false]) { Uint8List signatureBuffer = magicHash(message); - return utf8.decode(signatureBuffer); + //return utf8.decode(signatureBuffer); + return signatureBuffer } } From 81c68b9d367202b25e4db6671be08b0d5e0c4eb4 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Tue, 11 Jan 2022 07:33:21 -0800 Subject: [PATCH 082/106] change type --- lib/src/bitcoincash.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index fb9c7d0..208cb09 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -33,7 +33,7 @@ class BitcoinCash { } // Sign a string message with privateKey in Bitcoin Signature format - static String signMessage(String message, [returnString = false]) { + static Uint8List signMessage(String message, [returnString = false]) { Uint8List signatureBuffer = magicHash(message); //return utf8.decode(signatureBuffer); return signatureBuffer From e5e994ac942059b10bda8ae6439193cfb40bdc2c Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Tue, 11 Jan 2022 07:40:58 -0800 Subject: [PATCH 083/106] update file --- lib/src/bitcoincash.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index 208cb09..dbdd296 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -36,6 +36,6 @@ class BitcoinCash { static Uint8List signMessage(String message, [returnString = false]) { Uint8List signatureBuffer = magicHash(message); //return utf8.decode(signatureBuffer); - return signatureBuffer + return signatureBuffer; } } From 96fa716583ff347a4ed29531fef7b97380acffb9 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Wed, 19 Jan 2022 07:12:02 -0800 Subject: [PATCH 084/106] update dep --- pubspec.yaml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index d76ff92..b5f16a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,10 +15,7 @@ dependencies: git: url: https://github.com/DevanshiGarg08/bip39 ref: staging - pointycastle: - git: - url: https://github.com/jbdtky/pc-dart.git - ref: master + pointycastle: ^3.5.0 bip32: git: url: https://github.com/DevanshiGarg08/bip32-dart From d45ce5b0b4c93725fd35bab8b0229543e5ca3941 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Wed, 19 Jan 2022 07:25:03 -0800 Subject: [PATCH 085/106] Update packages --- pubspec.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index b5f16a5..fb6c507 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,12 +13,12 @@ dependencies: http: ^0.13.1 bip39: git: - url: https://github.com/DevanshiGarg08/bip39 + url: https://github.com/RomitRadical/bip39.git ref: staging pointycastle: ^3.5.0 bip32: git: - url: https://github.com/DevanshiGarg08/bip32-dart + url: https://github.com/zapit-io/bip32-dart.git ref: staging hex: ^0.2.0 fixnum: ^1.0.0 From 89c471075e2b1e35a11dc9fec7defca056713e86 Mon Sep 17 00:00:00 2001 From: Radical <36259950+RomitRadical@users.noreply.github.com> Date: Wed, 19 Jan 2022 07:25:11 -0800 Subject: [PATCH 086/106] Update packages From 7beeae71b5978c1b3060ed023eed1bff191066c2 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Wed, 19 Jan 2022 21:32:37 +0530 Subject: [PATCH 087/106] fix bug --- pubspec.lock | 38 +++++++++++++++++++++++++------------- pubspec.yaml | 4 ++-- 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index f8ebb7b..c1408f9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -12,18 +12,18 @@ packages: dependency: "direct main" description: path: "." - ref: staging - resolved-ref: "8fd271153e687304a260a3b65bd96ba0bd272d8f" - url: "https://github.com/DevanshiGarg08/bip32-dart" + ref: master + resolved-ref: "96e8eec4d674a3f3b88e3d2f7d8cb80ed89c800d" + url: "https://github.com/zapit-io/bip32-dart.git" source: git - version: "1.0.5" + version: "2.0.0" bip39: dependency: "direct main" description: path: "." - ref: staging - resolved-ref: "8f1a58c13c3f4c1497834a99e13ba28cd670be95" - url: "https://github.com/DevanshiGarg08/bip39" + ref: master + resolved-ref: "91c63bf997167bb947ee1d5d45df3bf0e8ad88b8" + url: "https://github.com/RomitRadical/bip39.git" source: git version: "1.0.3" boolean_selector: @@ -33,6 +33,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + bs58check: + dependency: transitive + description: + name: bs58check + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" bs58check_dart: dependency: "direct overridden" description: @@ -134,6 +141,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" matcher: dependency: transitive description: @@ -165,12 +179,10 @@ packages: pointycastle: dependency: "direct main" description: - path: "." - ref: master - resolved-ref: "276ca77d6d7a3f93d0cfad019bb69da2b9a6324b" - url: "https://github.com/jbdtky/pc-dart.git" - source: git - version: "1.0.2" + name: pointycastle + url: "https://pub.dartlang.org" + source: hosted + version: "3.5.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index fb6c507..96f27b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,12 +14,12 @@ dependencies: bip39: git: url: https://github.com/RomitRadical/bip39.git - ref: staging + ref: master pointycastle: ^3.5.0 bip32: git: url: https://github.com/zapit-io/bip32-dart.git - ref: staging + ref: master hex: ^0.2.0 fixnum: ^1.0.0 meta: ^1.3.0 From dc3362bc96bb267aa037a1db010c86a0ee5c7787 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Sat, 19 Mar 2022 16:39:07 +0530 Subject: [PATCH 088/106] add sha256 --- lib/src/crypto/crypto.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/src/crypto/crypto.dart b/lib/src/crypto/crypto.dart index fa9f55f..908905e 100644 --- a/lib/src/crypto/crypto.dart +++ b/lib/src/crypto/crypto.dart @@ -20,4 +20,8 @@ class Crypto { Uint8List _tmp = new SHA256Digest().process(buffer); return new SHA256Digest().process(_tmp); } + + static Uint8List sha256(Uint8List buffer) { + return new SHA256Digest().process(buffer); + } } From 7300588773aa9cf3c5ce475634fe2e141e79154f Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Mon, 16 May 2022 16:38:37 +0530 Subject: [PATCH 089/106] update urls --- lib/src/rawtransactions.dart | 12 +++++------- lib/src/utils/rest_api.dart | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/src/rawtransactions.dart b/lib/src/rawtransactions.dart index 90ace2e..f9f98f3 100644 --- a/lib/src/rawtransactions.dart +++ b/lib/src/rawtransactions.dart @@ -34,23 +34,21 @@ class RawTransactions { "rawtransactions/decodeScript", "hexes", scripts); /// Returns the raw transaction data - static Future getRawtransaction(String script, + static Future getRawtransaction(String txid, {bool verbose = true, bool testnet = false}) async { - var _restURL = testnet ? 'trest' : 'rest'; final response = await http.get(Uri.parse( - "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction/$script?verbose=$verbose")); + "https://api.fullstack.cash/v5/rawtransactions/getRawTransaction/$txid")); return jsonDecode(response.body); } /// Returns raw transaction data for multiple transactions - static Future getRawtransactions(List scripts, + static Future getRawtransactions(List txids, {bool verbose = true, bool testnet = false}) async { - var _restURL = testnet ? 'trest' : 'rest'; final response = await http.post( Uri.parse( - "https://$_restURL.bitcoin.com/v2/rawtransactions/getRawTransaction"), + "https://api.fullstack.cash/v5/rawtransactions/getRawTransaction"), headers: {"content-type": "application/json"}, - body: jsonEncode({'txids': scripts, "verbose": verbose})); + body: jsonEncode({'txids': txids, "verbose": verbose})); return jsonDecode(response.body); } } diff --git a/lib/src/utils/rest_api.dart b/lib/src/utils/rest_api.dart index 5919d13..97a2e0c 100644 --- a/lib/src/utils/rest_api.dart +++ b/lib/src/utils/rest_api.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' as http; class RestApi { - static String _restUrl = "https://rest.bitcoin.com/v2/"; + static String _restUrl = "https://rest1.biggestfan.net/v2"; static set restUrl(String restUrl) { _restUrl = restUrl; From 3fee6786f83ed5d915378cbb41d289a693620da0 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Wed, 18 May 2022 18:36:32 +0530 Subject: [PATCH 090/106] fix encoding/decoding issue with pointy castle --- lib/src/crypto/ecurve.dart | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/src/crypto/ecurve.dart b/lib/src/crypto/ecurve.dart index 23f1e54..3493659 100644 --- a/lib/src/crypto/ecurve.dart +++ b/lib/src/crypto/ecurve.dart @@ -2,7 +2,6 @@ import 'dart:typed_data'; import 'package:hex/hex.dart'; import 'package:pointycastle/ecc/api.dart' show ECPoint; import 'package:pointycastle/ecc/curves/secp256k1.dart'; -import "package:pointycastle/src/utils.dart"; class ECurve { static final secp256k1 = new ECCurve_secp256k1(); @@ -14,6 +13,28 @@ class ECurve { static final ecP = HEX.decode( "fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); + /// Decode a BigInt from bytes in big-endian encoding. + static BigInt decodeBigInt(List bytes) { + var result = BigInt.from(0); + for (var i = 0; i < bytes.length; i++) { + result += BigInt.from(bytes[bytes.length - i - 1]) << (8 * i); + } + return result; + } + + /// Encode a BigInt into bytes using big-endian encoding. + static Uint8List encodeBigInt(BigInt number) { + var _byteMask = BigInt.from(0xff); + // Not handling negative numbers. Decide how you want to do that. + var size = (number.bitLength + 7) >> 3; + var result = Uint8List(size); + for (var i = 0; i < size; i++) { + result[size - i - 1] = (number & _byteMask).toInt(); + number = number >> 8; + } + return result; + } + static Uint8List privateAdd(Uint8List d, Uint8List tweak) { // if (!isPrivate(d)) throw new ArgumentError(THROW_BAD_PRIVATE); // if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK); From 3f66069bc1ad5f6f7e93ae56dbc51ced786f796c Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Thu, 19 May 2022 13:01:14 +0530 Subject: [PATCH 091/106] update bip39 dep --- pubspec.lock | 25 ++++++++++++++++--------- pubspec.yaml | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index c1408f9..9ac968a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.1" + version: "2.8.2" bip32: dependency: "direct main" description: @@ -22,10 +22,10 @@ packages: description: path: "." ref: master - resolved-ref: "91c63bf997167bb947ee1d5d45df3bf0e8ad88b8" - url: "https://github.com/RomitRadical/bip39.git" + resolved-ref: "3633daa2026b98c523ae9a091322be2903f7a8ab" + url: "https://github.com/zapit-io/bip39.git" source: git - version: "1.0.3" + version: "1.0.6" boolean_selector: dependency: transitive description: @@ -60,7 +60,7 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" charcode: dependency: transitive description: @@ -154,7 +154,14 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10" + version: "0.12.11" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3" meta: dependency: "direct main" description: @@ -247,7 +254,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.2" + version: "0.4.8" typed_data: dependency: transitive description: @@ -261,6 +268,6 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.1" sdks: - dart: ">=2.12.0 <3.0.0" + dart: ">=2.14.0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 96f27b1..0026b74 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: http: ^0.13.1 bip39: git: - url: https://github.com/RomitRadical/bip39.git + url: https://github.com/zapit-io/bip39.git ref: master pointycastle: ^3.5.0 bip32: From 6c233e900e33682f1ff224379a0b51c9b98d80e8 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Thu, 19 May 2022 16:24:35 +0530 Subject: [PATCH 092/106] update pointycastle version --- pubspec.lock | 4 ++-- pubspec.yaml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9ac968a..9de45cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,7 +13,7 @@ packages: description: path: "." ref: master - resolved-ref: "96e8eec4d674a3f3b88e3d2f7d8cb80ed89c800d" + resolved-ref: f9d9532be91a0031630f3be1dbb2b5ae3bf014c6 url: "https://github.com/zapit-io/bip32-dart.git" source: git version: "2.0.0" @@ -189,7 +189,7 @@ packages: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "3.6.0" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 0026b74..329d8c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: git: url: https://github.com/zapit-io/bip39.git ref: master - pointycastle: ^3.5.0 + pointycastle: ^3.6.0 bip32: git: url: https://github.com/zapit-io/bip32-dart.git @@ -37,6 +37,7 @@ dependency_overrides: crypto: ^3.0.0 hex: ^0.2.0 bs58check_dart: ^2.0.0+1 + dev_dependencies: flutter_test: sdk: flutter From 87ca88b92ff10eb7297c407368b605c90854e9b7 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Thu, 19 May 2022 16:50:43 +0530 Subject: [PATCH 093/106] removed pointycastle --- pubspec.lock | 2 +- pubspec.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9de45cd..d7aa2cb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -184,7 +184,7 @@ packages: source: hosted version: "1.11.0" pointycastle: - dependency: "direct main" + dependency: transitive description: name: pointycastle url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 329d8c5..835b248 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,6 @@ dependencies: git: url: https://github.com/zapit-io/bip39.git ref: master - pointycastle: ^3.6.0 bip32: git: url: https://github.com/zapit-io/bip32-dart.git From 09f35f4450d569c232fad23ba0e6b857bf6d9a03 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Thu, 19 May 2022 16:52:59 +0530 Subject: [PATCH 094/106] add pointycastle --- pubspec.lock | 2 +- pubspec.yaml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index d7aa2cb..9de45cd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -184,7 +184,7 @@ packages: source: hosted version: "1.11.0" pointycastle: - dependency: transitive + dependency: "direct main" description: name: pointycastle url: "https://pub.dartlang.org" diff --git a/pubspec.yaml b/pubspec.yaml index 835b248..329d8c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: git: url: https://github.com/zapit-io/bip39.git ref: master + pointycastle: ^3.6.0 bip32: git: url: https://github.com/zapit-io/bip32-dart.git From fb8ac5a39a02c07d5f42594c4e4ae9a6bf0a6ec8 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Thu, 19 May 2022 17:53:38 +0530 Subject: [PATCH 095/106] update lock --- pubspec.lock | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 9de45cd..b908def 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,7 +13,7 @@ packages: description: path: "." ref: master - resolved-ref: f9d9532be91a0031630f3be1dbb2b5ae3bf014c6 + resolved-ref: "029814992478134be170a1497a637ec740015e36" url: "https://github.com/zapit-io/bip32-dart.git" source: git version: "2.0.0" @@ -22,7 +22,7 @@ packages: description: path: "." ref: master - resolved-ref: "3633daa2026b98c523ae9a091322be2903f7a8ab" + resolved-ref: "06a412e02ece46e648cd21e96b4f74fadeea5784" url: "https://github.com/zapit-io/bip39.git" source: git version: "1.0.6" @@ -95,7 +95,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" fake_async: dependency: transitive description: @@ -109,7 +109,7 @@ packages: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "1.0.0" + version: "1.0.1" flutter: dependency: "direct main" description: flutter @@ -133,21 +133,21 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.3" + version: "0.13.4" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "4.0.0" + version: "4.0.1" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.3" + version: "0.6.4" matcher: dependency: transitive description: @@ -176,13 +176,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.0" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.11.0" pointycastle: dependency: "direct main" description: @@ -270,4 +263,4 @@ packages: source: hosted version: "2.1.1" sdks: - dart: ">=2.14.0 <3.0.0" + dart: ">=2.16.0-100.0.dev <3.0.0" From c71cf7e69079d4e22587b5ded29157e6b7c9d404 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Sat, 21 May 2022 11:13:25 +0530 Subject: [PATCH 096/106] fix apis --- lib/src/rawtransactions.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/src/rawtransactions.dart b/lib/src/rawtransactions.dart index f9f98f3..b9384e3 100644 --- a/lib/src/rawtransactions.dart +++ b/lib/src/rawtransactions.dart @@ -34,19 +34,18 @@ class RawTransactions { "rawtransactions/decodeScript", "hexes", scripts); /// Returns the raw transaction data - static Future getRawtransaction(String txid, - {bool verbose = true, bool testnet = false}) async { + static Future getRawtransaction(String txid, {bool verbose = true}) async { final response = await http.get(Uri.parse( - "https://api.fullstack.cash/v5/rawtransactions/getRawTransaction/$txid")); + "https://rest1.biggestfan.net/v2/rawtransactions/getRawTransaction/$txid?verbose=$verbose")); return jsonDecode(response.body); } /// Returns raw transaction data for multiple transactions static Future getRawtransactions(List txids, - {bool verbose = true, bool testnet = false}) async { + {bool verbose = true}) async { final response = await http.post( Uri.parse( - "https://api.fullstack.cash/v5/rawtransactions/getRawTransaction"), + "https://rest1.biggestfan.net/v2/rawtransactions/getRawTransaction"), headers: {"content-type": "application/json"}, body: jsonEncode({'txids': txids, "verbose": verbose})); return jsonDecode(response.body); From 255b6b9bd3b96ea59b0b482f4d1e3c246b68e939 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Sun, 19 Jun 2022 13:39:49 +0530 Subject: [PATCH 097/106] add op return script --- lib/src/bitcoincash.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index dbdd296..f31418c 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:typed_data'; +import 'package:bitbox/bitbox.dart'; import 'package:bitbox/src/utils/magic_hash.dart'; import 'utils/bip21.dart'; @@ -38,4 +39,8 @@ class BitcoinCash { //return utf8.decode(signatureBuffer); return signatureBuffer; } + + static Uint8List getOpReturnScript(String data) { + return compile([Opcodes.OP_RETURN, utf8.encode(data)]); + } } From 7d4b2c1e5945768e7a31ca82afcb57b81dba10f3 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Fri, 22 Jul 2022 16:57:47 +0530 Subject: [PATCH 098/106] add publicKey and privateKey files --- lib/src/privatekey.dart | 124 ++++++++++++++++ lib/src/publickey.dart | 311 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 435 insertions(+) create mode 100644 lib/src/privatekey.dart create mode 100644 lib/src/publickey.dart diff --git a/lib/src/privatekey.dart b/lib/src/privatekey.dart new file mode 100644 index 0000000..a0860cb --- /dev/null +++ b/lib/src/privatekey.dart @@ -0,0 +1,124 @@ +import 'package:bitbox/src/publickey.dart'; +import 'package:pointycastle/pointycastle.dart'; +import 'package:pointycastle/random/fortuna_random.dart'; +import 'dart:typed_data'; +import 'dart:math'; +import 'package:pointycastle/key_generators/ec_key_generator.dart'; +import 'package:pointycastle/ecc/curves/secp256k1.dart'; + +/// Manages an ECDSA private key. +/// +/// Bitcoin uses ECDSA for it's public/private key cryptography. +/// Specifically it uses the `secp256k1` elliptic curve. +/// +/// This class wraps cryptographic operations related to ECDSA from the +/// [PointyCastle](https://pub.dev/packages/pointycastle) library/package. +/// +/// You can read a good primer on Elliptic Curve Cryptography at [This Cloudflare blog post](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/) +/// +/// +class BCHPrivateKey { + final _domainParams = ECDomainParameters('secp256k1'); + final _secureRandom = FortunaRandom(); + + var _hasCompressedPubKey = false; + var _networkType = 0; //Mainnet by default + + var random = Random.secure(); + + BigInt _d; + ECPrivateKey _ecPrivateKey; + BCHPublicKey _bchPublicKey; + + /// Constructs a random private key. + /// + /// [networkType] - Optional network type. Defaults to mainnet. The network type is only + /// used when serialising the Private Key in *WIF* format. See [toWIF()]. + /// + BCHPrivateKey({networkType = 0}) { + var keyParams = ECKeyGeneratorParameters(ECCurve_secp256k1()); + _secureRandom.seed(KeyParameter(_seed())); + + var generator = ECKeyGenerator(); + generator.init(ParametersWithRandom(keyParams, _secureRandom)); + + var retry = + 100; //100 retries to get correct bitLength. Problem in PointyCastle lib ? + AsymmetricKeyPair keypair; + while (retry > 0) { + keypair = generator.generateKeyPair(); + ECPrivateKey key = keypair.privateKey as ECPrivateKey; + if (key.d.bitLength == 256) { + break; + } else { + retry--; + } + } + + _hasCompressedPubKey = true; + _networkType = networkType; + _ecPrivateKey = keypair.privateKey as ECPrivateKey; + _d = _ecPrivateKey.d; + + if (_d.bitLength != 256) { + throw Exception( + "Failed to generate a valid private key after 100 tries. Try again. "); + } + + _bchPublicKey = BCHPublicKey.fromPrivateKey(this); + } + + /// Construct a Private Key from the hexadecimal value representing the + /// BigInt value of (d) in ` Q = d * G ` + /// + /// [privhex] - The BigInt representation of the private key as a hexadecimal string + /// + /// [networkType] - The network type we intend to use to corresponding WIF representation on. + BCHPrivateKey.fromHex(String privhex) { + var d = BigInt.parse(privhex, radix: 16); + + _hasCompressedPubKey = true; + _networkType = 0; + _ecPrivateKey = _privateKeyFromBigInt(d); + _d = d; + _bchPublicKey = BCHPublicKey.fromPrivateKey(this); + } + + /// Returns the *naked* private key Big Integer value as a hexadecimal string + String toHex() { + return _d.toRadixString(16); + } + + Uint8List _seed() { + var random = Random.secure(); + var seed = List.generate(32, (_) => random.nextInt(256)); + return Uint8List.fromList(seed); + } + + ECPrivateKey _privateKeyFromBigInt(BigInt d) { + if (d == BigInt.zero) { + throw Exception( + 'Zero is a bad value for a private key. Pick something else.'); + } + + return ECPrivateKey(d, _domainParams); + } + + /// Returns the *naked* private key Big Integer value as a Big Integer + BigInt get privateKey { + return _d; + } + + /// Returns the [BCHPublicKey] corresponding to this ECDSA private key. + /// + /// NOTE: `Q = d * G` where *Q* is the public key, *d* is the private key and `G` is the curve's Generator. + BCHPublicKey get publicKey { + return _bchPublicKey; + } + + /// Returns true if the corresponding public key for this private key + /// is in *compressed* format. To read more about compressed public keys see [BCHPublicKey().getEncoded()] + bool get isCompressed { + return _hasCompressedPubKey; + } +} diff --git a/lib/src/publickey.dart b/lib/src/publickey.dart new file mode 100644 index 0000000..b142df6 --- /dev/null +++ b/lib/src/publickey.dart @@ -0,0 +1,311 @@ +import 'dart:typed_data'; +import 'package:bitbox/src/privatekey.dart'; +import 'package:hex/hex.dart'; +import 'package:pointycastle/pointycastle.dart'; + +/// Manages an ECDSA public key. +/// +/// Bitcoin uses ECDSA for it's public/private key cryptography. +/// Specifically it uses the `secp256k1` elliptic curve. +/// +/// This class wraps cryptographic operations related to ECDSA from the +/// [PointyCastle](https://pub.dev/packages/pointycastle) library/package. +/// +/// You can read a good primer on Elliptic Curve Cryptography at [This Cloudflare blog post](https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/) +/// +/// +class BCHPublicKey { + //We only deal with secp256k1 + final _domainParams = ECDomainParameters('secp256k1'); + + ECPoint _point; + + /// Creates a public key from it's corresponding ECDSA private key. + /// + /// NOTE: public key *Q* is computed as `Q = d * G` where *d* is the private key + /// and *G* is the elliptic curve Generator. + /// + /// [privkey] - The private key who's *d*-value we will use. + BCHPublicKey.fromPrivateKey(BCHPrivateKey privkey) { + var decodedPrivKey = encodeBigInt(privkey.privateKey); + var hexPrivKey = HEX.encode(decodedPrivKey); + + var actualKey = hexPrivKey; + var point = (_domainParams.G * BigInt.parse(actualKey, radix: 16)); + if (point.x == null && point.y == null) { + throw Exception( + 'Cannot generate point from private key. Private key greater than N ?'); + } + + //create a point taking into account compression request/indicator of parent private key + var finalPoint = _domainParams.curve.createPoint( + point.x.toBigInteger(), point.y.toBigInteger(), privkey.isCompressed); + + _checkIfOnCurve(finalPoint); // a bit paranoid + + _point = finalPoint; +// _publicKey = ECPublicKey((_point), _domainParams); + } + + /// Creates a public key instance from the ECDSA public key's `x-coordinate` + /// + /// ECDSA has some cool properties. Because we are dealing with an elliptic curve in a plane, + /// the public key *Q* has (x,y) cartesian coordinates. + /// It is possible to reconstruct the full public key from only it's `x-coordinate` + /// *IFF* one knows whether the Y-Value is *odd* or *even*. + /// + /// [xValue] - The Big Integer value of the `x-coordinate` in hexadecimal format + /// + /// [oddYValue] - *true* if the corresponding `y-coordinate` is even, *false* otherwise + BCHPublicKey.fromX(String xValue, bool oddYValue) { + _point = _getPointFromX(xValue, oddYValue); +// _publicKey = ECPublicKey((_point), _domainParams); + } + + /// Creates a public key from it's known *(x,y)* coordinates. + /// + /// [x] - X coordinate of the public key + /// + /// [y] - Y coordinate of the public key + /// + /// [compressed] = Specifies whether we will render this point in it's + /// compressed form by default with [toString()]. See [getEncoded()] to + /// learn more about compressed public keys. + BCHPublicKey.fromXY(BigInt x, BigInt y, {bool compressed = true}) { + //create a compressed point by default + var point = _domainParams.curve.createPoint(x, y, compressed); + + _checkIfOnCurve(point); + + _point = point; + +// _publicKey = ECPublicKey(_point, _domainParams); + } + + /// Reconstructs a public key from a DER-encoding. + /// + /// [buffer] - Byte array containing public key in DER format. + /// + /// [strict] - If *true* then we enforce strict DER encoding rules. Defaults to *true*. + BCHPublicKey.fromDER(List buffer, {bool strict = true}) { + if (buffer.isEmpty) { + throw Exception('Empty compressed DER buffer'); + } + + _point = _transformDER(buffer, strict); + + if (_point.isInfinity) { + throw Exception('That public key generates point at infinity'); + } + + if (_point.y.toBigInteger() == BigInt.zero) { + throw Exception('Invalid Y value for this public key'); + } + + _checkIfOnCurve(_point); + +// _publicKey = ECPublicKey(_point, _domainParams); + } + + /// Reconstruct a public key from the hexadecimal format of it's DER-encoding. + /// + /// [pubkey] - The DER-encoded public key as a hexadecimal string + /// + /// [strict] - If *true* then we enforce strict DER encoding rules. Defaults to *true*. + BCHPublicKey.fromHex(String pubkey, {bool strict = true}) { + if (pubkey.trim() == '') { + throw Exception('Empty compressed public key string'); + } + +// _parseHexString(pubkey); + _point = _transformDER(HEX.decode(pubkey), strict); + + if (_point.isInfinity) { + throw Exception('That public key generates point at infinity'); + } + + if (_point.y.toBigInteger() == BigInt.zero) { + throw Exception('Invalid Y value for this public key'); + } + + _checkIfOnCurve(_point); + +// _publicKey = ECPublicKey(_point, _domainParams); + } + + /// Validates that the DER-encoded hexadecimal string contains a valid + /// public key. + /// + /// [pubkey] - The DER-encoded public key as a hexadecimal string + /// + /// Returns *true* if the public key is valid, *false* otherwise. + static bool isValid(String pubkey) { + try { + BCHPublicKey.fromHex(pubkey); + } catch (err) { + return false; + } + + return true; + } + + /// Returns the *naked* public key value as either an (x,y) coordinate + /// or in a compact format using elliptic-curve point-compression. + /// + /// With EC point compression it is possible to reduce by half the + /// space occupied by a point, by taking advantage of a EC-curve property. + /// Specifically it is possible to recover the `y-coordinate` *IFF* the + /// `x-coordinate` is known *AND* we know whether the `y-coordinate` is + /// *odd* or *even*. + /// + /// [compressed] - If *true* the 'naked' public key value is returned in + /// compact format where the first byte is either 'odd' or 'even' followed + /// by the `x-coordinate`. If *false*, the full *(x,y)* coordinate pair will + /// be returned. + /// + /// NOTE: The first byte will contain either an odd number or an even number, + /// but this number is *NOT* a boolean flag. + String getEncoded(bool compressed) { + return HEX.encode(_point.getEncoded(compressed)); + } + + /// Returns the 'naked' public key value. Point compression is determined by + /// the default parameter in the constructor. If you want to enforce a specific preference + /// for the encoding, you can use the [getEncoded()] function instead. + @override + String toString() { + if (_point == null) { + return ''; + } + + return HEX.encode(_point.getEncoded(_point.isCompressed)); + } + + /// Alias for the [toString()] method. + String toHex() => toString(); + + ECPoint _transformDER(List buf, bool strict) { + BigInt x; + BigInt y; + List xbuf; + List ybuf; + ECPoint point; + + if (buf[0] == 0x04 || (!strict && (buf[0] == 0x06 || buf[0] == 0x07))) { + xbuf = buf.sublist(1, 33); + ybuf = buf.sublist(33, 65); + if (xbuf.length != 32 || ybuf.length != 32 || buf.length != 65) { + throw Exception('Length of x and y must be 32 bytes'); + } + x = BigInt.parse(HEX.encode(xbuf), radix: 16); + y = BigInt.parse(HEX.encode(ybuf), radix: 16); + + point = _domainParams.curve.createPoint(x, y); + } else if (buf[0] == 0x03 || buf[0] == 0x02) { + xbuf = buf.sublist(1); + x = BigInt.parse(HEX.encode(xbuf), radix: 16); + + var yTilde = buf[0] & 1; + point = _domainParams.curve.decompressPoint(yTilde, x); + } else { + throw Exception('Invalid DER format public key'); + } + return point; + } + + ECPoint _getPointFromX(String xValue, bool oddYValue) { + var prefixByte; + if (oddYValue) { + prefixByte = 0x03; + } else { + prefixByte = 0x02; + } + + var encoded = HEX.decode(xValue); + + var addressBytes = List.filled(1 + encoded.length, 0); + addressBytes[0] = prefixByte; + addressBytes.setRange(1, addressBytes.length, encoded); + + return _decodePoint(HEX.encode(addressBytes)); + } + + ECPoint _decodePoint(String pkHex) { + if (pkHex.trim() == '') { + throw Exception('Empty compressed public key string'); + } + + var encoded = HEX.decode(pkHex); + try { + var point = _domainParams.curve.decodePoint(encoded); + + if (point.isCompressed && encoded.length != 33) { + throw Exception( + "Compressed public keys must be 33 bytes long. Yours is [${encoded.length}]"); + } else if (!point.isCompressed && encoded.length != 65) { + throw Exception( + "Uncompressed public keys must be 65 bytes long. Yours is [${encoded.length}]"); + } + + _checkIfOnCurve(point); + + return point; + } on ArgumentError catch (err) { + throw Exception(err.message); + } + } + + /// Encode a BigInt into bytes using big-endian encoding. + Uint8List encodeBigInt(BigInt number) { + var _byteMask = BigInt.from(0xff); + int size = (number.bitLength + 7) >> 3; + + var result = Uint8List(size); + for (int i = 0; i < size; i++) { + result[size - i - 1] = (number & _byteMask).toInt(); + number = number >> 8; + } + + return result; + } + + String _compressPoint(ECPoint point) { + return HEX.encode(point.getEncoded(true)); + } + + bool _checkIfOnCurve(ECPoint point) { + //a bit of math copied from PointyCastle. ecc/ecc_fp.dart -> decompressPoint() + var x = _domainParams.curve.fromBigInteger(point.x.toBigInteger()); + var alpha = (x * ((x * x) + _domainParams.curve.a)) + _domainParams.curve.b; + ECFieldElement beta = alpha.sqrt(); + + if (beta == null) { + throw Exception('This point is not on the curve'); + } + + //slight-of-hand. Create compressed point, reconstruct and check Y value. + var compressedPoint = _compressPoint(point); + var checkPoint = + (_domainParams.curve.decodePoint(HEX.decode(compressedPoint))); + if (checkPoint.y.toBigInteger() != point.y.toBigInteger()) { + throw Exception('This point is not on the curve'); + } + + return (point.x.toBigInteger() == BigInt.zero) && + (point.y.toBigInteger() == BigInt.zero); + } + + /// Returns the (x,y) coordinates of this public key as an [ECPoint]. + /// The author dislikes leaking the wrapped PointyCastle implementation, but is too + /// lazy to write his own Point implementation. + ECPoint get point { + return _point; + } + + /// Returns *true* if this public key will render using EC point compression by + /// default when one calls the [toString()] or [toHex()] methods. + /// Returns *false* otherwise. + bool get isCompressed { + return _point.isCompressed; + } +} From 4b4c35eed67a865be0ba1731b0ee8eb240cd3e76 Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Fri, 22 Jul 2022 16:58:10 +0530 Subject: [PATCH 099/106] add ecies encryption/decryption --- lib/src/crypto/ecies.dart | 173 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 lib/src/crypto/ecies.dart diff --git a/lib/src/crypto/ecies.dart b/lib/src/crypto/ecies.dart new file mode 100644 index 0000000..4586d55 --- /dev/null +++ b/lib/src/crypto/ecies.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; +import 'dart:typed_data'; +import 'package:bitbox/src/privatekey.dart'; +import 'package:bitbox/src/publickey.dart'; +import 'package:collection/collection.dart'; +import 'package:hex/hex.dart'; +import 'package:pointycastle/digests/sha256.dart'; +import 'package:pointycastle/digests/sha512.dart'; +import 'package:pointycastle/macs/hmac.dart'; +import 'package:pointycastle/pointycastle.dart'; + +/// A Class for performing Elliptic Curve Integrated Encryption Scheme operations. +/// +/// This class only makes provision for the "Electrum ECIES" aka "BIE1" serialization +/// format for the cipherText. +class Ecies { + static final ECDomainParameters _domainParams = + ECDomainParameters('secp256k1'); + static final SHA256Digest _sha256Digest = SHA256Digest(); + static final _tagLength = 32; //size of hmac + + /// Perform an ECIES encryption using AES for the symmetric cipher. + /// + /// [messageBuffer] - The buffer to encrypt. Note that the buffer in this instance has a very specific + /// encoding format called "BIE1" or "Electrum ECIES". It is in essence a serialization format with a + /// built-in checksum. + /// - bytes [0 - 4] : Magic value. Literally "BIE1". + /// - bytes [4 - 37] : Compressed Public Key + /// - bytes [37 - (length - 32) ] : Actual cipherText + /// - bytes [ length - 32 ] : (last 32 bytes) Checksum value + /// + /// [senderPrivateKey] - Private Key of the sending party + /// + /// [recipientPublicKey] - Public Key of the party who can decrypt the message + /// + static String encryptData( + {String message, + String senderPrivateKeyHex, + String recipientPublicKeyHex, + String magicValue = "BIE1"}) { + //Encryption requires derivation of a cipher using the other party's Public Key + // Bob is sender, Alice is recipient of encrypted message + // Qb = k o Qa, where + // Qb = Bob's Public Key; + // k = Bob's private key; + // Qa = Alice's public key; + + BCHPrivateKey senderPrivateKey = BCHPrivateKey.fromHex(senderPrivateKeyHex); + BCHPublicKey recipientPublicKey = + BCHPublicKey.fromHex(recipientPublicKeyHex); + + List messageBuffer = Uint8List.fromList(message.codeUnits); + + final ECPoint S = (recipientPublicKey.point * + senderPrivateKey.privateKey); //point multiplication + + final pubkeyS = BCHPublicKey.fromXY(S.x.toBigInteger(), S.y.toBigInteger()); + final pubkeyBuffer = HEX.decode(pubkeyS.getEncoded(true)); + final pubkeyHash = SHA512Digest().process(pubkeyBuffer as Uint8List); + + //initialization vector parameters + final iv = pubkeyHash.sublist(0, 16); + final kE = pubkeyHash.sublist(16, 32); + final kM = pubkeyHash.sublist(32, 64); + + CipherParameters params = PaddedBlockCipherParameters( + ParametersWithIV(KeyParameter(kE), iv), null); + BlockCipher encryptionCipher = PaddedBlockCipher('AES/CBC/PKCS7'); + encryptionCipher.init(true, params); + + final cipherText = encryptionCipher.process(messageBuffer as Uint8List); + + final magic = utf8.encode(magicValue); + + final encodedBuffer = Uint8List.fromList( + magic + HEX.decode(senderPrivateKey.publicKey.toHex()) + cipherText); + + //calc checksum + final hmac = _calculateHmac(kM, encodedBuffer); + + return HEX.encode(encodedBuffer + hmac); + } + + static Uint8List _calculateHmac(Uint8List kM, Uint8List encodedBuffer) { + final sha256Hmac = HMac(_sha256Digest, 64); + sha256Hmac.init(KeyParameter(kM)); + final calculatedChecksum = sha256Hmac.process(encodedBuffer); + return calculatedChecksum; + } + + /// Perform an ECIES decryption using AES for the symmetric cipher. + /// + /// [cipherText] - The buffer to decrypt. Note that the buffer in this instance has a very specific + /// encoding format called "BIE1" or "Electrum ECIES". It is in essence a serialization format with a + /// built-in checksum. + /// - bytes [0 - 4] : Magic value. Literally "BIE1". + /// - bytes [4 - 37] : Compressed Public Key + /// - bytes [37 - (length - 32) ] : Actual cipherText + /// - bytes [ length - 32 ] : (last 32 bytes) Checksum valu + /// + /// [recipientPrivateKey] - Private Key of the receiving party + /// + static String decryptData( + {String cipherTextStr, + String recipientPrivateKeyHex, + String magicValue = "BIE1"}) { + //AES Cipher is calculated as + //1) S = recipientPrivateKey o senderPublicKey + //2) cipher = S.x + + List cipherText = HEX.decode(cipherTextStr); + + BCHPrivateKey recipientPrivateKey = + BCHPrivateKey.fromHex(recipientPrivateKeyHex); + + if (cipherText.length < 37) { + throw Exception('Buffer is too small '); + } + + final magic = utf8.decode(cipherText.sublist(0, 4)); + + if (magic != magicValue) { + throw Exception('Not a $magicValue-encoded buffer'); + } + + final senderPubkeyBuffer = cipherText.sublist(4, 37); + final senderPublicKey = + BCHPublicKey.fromHex(HEX.encode(senderPubkeyBuffer)); + + //calculate S = recipientPrivateKey o senderPublicKey + final S = (senderPublicKey.point * + recipientPrivateKey.privateKey); //point multiplication + final cipher = S.x; + + if (cipherText.length - _tagLength <= 37) { + throw Exception( + 'Invalid Checksum detected. Combined sum of Checksum and Message makes no sense'); + } + + //validate the checksum bytes + final pubkeyS = BCHPublicKey.fromXY(S.x.toBigInteger(), S.y.toBigInteger()); + final pubkeyBuffer = HEX.decode(pubkeyS.getEncoded(true)); + final pubkeyHash = SHA512Digest().process(pubkeyBuffer as Uint8List); + + //initialization vector parameters + final iv = pubkeyHash.sublist(0, 16); + final kE = pubkeyHash.sublist(16, 32); + final kM = pubkeyHash.sublist(32, 64); + + final message = Uint8List.fromList( + cipherText.sublist(0, cipherText.length - _tagLength)); + + final Uint8List hmac = _calculateHmac(kM, message); + + final Uint8List messageChecksum = + cipherText.sublist(cipherText.length - _tagLength, cipherText.length); + + // ignore: prefer_const_constructors + if (!ListEquality().equals(messageChecksum, hmac)) { + throw Exception('HMAC checksum failed to validate'); + } + + //decrypt! + CipherParameters params = PaddedBlockCipherParameters( + ParametersWithIV(KeyParameter(kE), iv), null); + BlockCipher decryptionCipher = PaddedBlockCipher("AES/CBC/PKCS7"); + decryptionCipher.init(false, params); + + final decrypted = decryptionCipher.process( + cipherText.sublist(37, cipherText.length - _tagLength) as Uint8List); + return utf8.decode(decrypted); + } +} From dd8ecaca6e6c30cb4d6408ac570d2913036a420a Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Fri, 22 Jul 2022 17:04:47 +0530 Subject: [PATCH 100/106] export ecies --- lib/bitbox.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/bitbox.dart b/lib/bitbox.dart index 01c9b28..034cbff 100644 --- a/lib/bitbox.dart +++ b/lib/bitbox.dart @@ -8,6 +8,7 @@ export 'src/block.dart'; export 'src/blockchain.dart'; export 'src/cashaccounts.dart'; export 'src/crypto/crypto.dart'; +export 'src/crypto/ecies.dart'; export 'src/ecpair.dart'; export 'src/hdnode.dart'; export 'src/mnemonic.dart'; From 1cf59e116f0f70f3d353bb6f0d89e47afb7137ea Mon Sep 17 00:00:00 2001 From: RomitRadical Date: Thu, 25 Aug 2022 18:04:44 +0530 Subject: [PATCH 101/106] change plugin --- lib/src/address.dart | 2 +- lib/src/hdnode.dart | 2 +- lib/src/transactionbuilder.dart | 2 +- lib/src/utils/p2pkh.dart | 2 +- lib/src/utils/p2sh.dart | 2 +- pubspec.lock | 15 ++++----------- pubspec.yaml | 2 +- 7 files changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index e992feb..cf62df4 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'utils/rest_api.dart'; -import 'package:bs58check_dart/bs58check.dart' as bs58check; +import 'package:bs58check/bs58check.dart' as bs58check; import 'utils/network.dart'; import 'package:fixnum/fixnum.dart'; diff --git a/lib/src/hdnode.dart b/lib/src/hdnode.dart index 4679098..220016b 100644 --- a/lib/src/hdnode.dart +++ b/lib/src/hdnode.dart @@ -4,7 +4,7 @@ import 'dart:typed_data'; import 'crypto/ecurve.dart'; import 'address.dart'; import 'package:hex/hex.dart'; -import 'package:bs58check_dart/bs58check.dart' as bs58check; +import 'package:bs58check/bs58check.dart' as bs58check; import 'crypto/crypto.dart'; import 'ecpair.dart'; diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index eaba9b0..9532fe4 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -1,6 +1,6 @@ import 'dart:typed_data'; import 'package:hex/hex.dart'; -import 'package:bs58check_dart/bs58check.dart' as bs58check; +import 'package:bs58check/bs58check.dart' as bs58check; import 'address.dart'; import 'crypto/crypto.dart'; import 'utils/network.dart'; diff --git a/lib/src/utils/p2pkh.dart b/lib/src/utils/p2pkh.dart index 01df82f..71dbd3a 100644 --- a/lib/src/utils/p2pkh.dart +++ b/lib/src/utils/p2pkh.dart @@ -3,7 +3,7 @@ import '../crypto/crypto.dart'; import '../utils/opcodes.dart'; import 'package:meta/meta.dart'; import 'package:bip32/src/utils/ecurve.dart' show isPoint; -import 'package:bs58check_dart/bs58check.dart' as bs58check; +import 'package:bs58check/bs58check.dart' as bs58check; import 'script.dart' as bscript; import 'network.dart'; diff --git a/lib/src/utils/p2sh.dart b/lib/src/utils/p2sh.dart index 63b4f5c..9ebdf3a 100644 --- a/lib/src/utils/p2sh.dart +++ b/lib/src/utils/p2sh.dart @@ -3,7 +3,7 @@ import '../crypto/crypto.dart'; import '../utils/opcodes.dart'; import 'package:meta/meta.dart'; import 'package:bip32/src/utils/ecurve.dart' show isPoint; -import 'package:bs58check_dart/bs58check.dart' as bs58check; +import 'package:bs58check/bs58check.dart' as bs58check; import 'script.dart' as bscript; import 'network.dart'; diff --git a/pubspec.lock b/pubspec.lock index b908def..ca46697 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -34,19 +34,12 @@ packages: source: hosted version: "2.1.0" bs58check: - dependency: transitive + dependency: "direct main" description: name: bs58check url: "https://pub.dartlang.org" source: hosted version: "1.0.2" - bs58check_dart: - dependency: "direct overridden" - description: - name: bs58check_dart - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0+1" buffer: dependency: "direct main" description: @@ -88,7 +81,7 @@ packages: name: convert url: "https://pub.dartlang.org" source: hosted - version: "3.0.1" + version: "3.0.2" crypto: dependency: "direct overridden" description: @@ -133,7 +126,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.13.4" + version: "0.13.5" http_parser: dependency: transitive description: @@ -182,7 +175,7 @@ packages: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "3.6.0" + version: "3.6.1" sky_engine: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 329d8c5..e8c97f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: fixnum: ^1.0.0 meta: ^1.3.0 buffer: ^1.1.0 + bs58check: ^1.0.2 slp_parser: git: url: https://github.com/zapit-io/slp-parser.dart.git @@ -36,7 +37,6 @@ dependencies: dependency_overrides: crypto: ^3.0.0 hex: ^0.2.0 - bs58check_dart: ^2.0.0+1 dev_dependencies: flutter_test: From 1e27805cf52c64d86a0b91eb67b0d2b739f601e6 Mon Sep 17 00:00:00 2001 From: Sathish Date: Tue, 29 Nov 2022 19:19:40 +0530 Subject: [PATCH 102/106] upgrade packages --- pubspec.lock | 39 ++++++++++++++++----------------------- pubspec.yaml | 10 +++++----- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index ca46697..9028f93 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,7 +7,7 @@ packages: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.8.2" + version: "2.9.0" bip32: dependency: "direct main" description: @@ -53,28 +53,21 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" collection: dependency: transitive description: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0" + version: "1.16.0" convert: dependency: transitive description: @@ -95,7 +88,7 @@ packages: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" fixnum: dependency: "direct main" description: @@ -147,35 +140,35 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.11" + version: "0.12.12" material_color_utilities: dependency: transitive description: name: material_color_utilities url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "0.1.5" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.2" pointycastle: dependency: "direct main" description: name: pointycastle url: "https://pub.dartlang.org" source: hosted - version: "3.6.1" + version: "3.6.2" sky_engine: dependency: transitive description: flutter @@ -205,7 +198,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.9.0" stack_trace: dependency: transitive description: @@ -226,21 +219,21 @@ packages: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.2.1" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.4.8" + version: "0.4.12" typed_data: dependency: transitive description: @@ -254,6 +247,6 @@ packages: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.1.2" sdks: - dart: ">=2.16.0-100.0.dev <3.0.0" + dart: ">=2.17.0-0 <3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index e8c97f4..490ca5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,20 +10,20 @@ environment: dependencies: flutter: sdk: flutter - http: ^0.13.1 + http: ^0.13.5 bip39: git: url: https://github.com/zapit-io/bip39.git ref: master - pointycastle: ^3.6.0 + pointycastle: ^3.6.2 bip32: git: url: https://github.com/zapit-io/bip32-dart.git ref: master hex: ^0.2.0 - fixnum: ^1.0.0 - meta: ^1.3.0 - buffer: ^1.1.0 + fixnum: ^1.0.1 + meta: ^1.8.0 + buffer: ^1.1.1 bs58check: ^1.0.2 slp_parser: git: From 63a6a8634d0053e7bfbb94f22bc63aa15060442d Mon Sep 17 00:00:00 2001 From: Sathish Date: Tue, 29 Nov 2022 19:20:15 +0530 Subject: [PATCH 103/106] ran dart migrate --- example/main.dart | 2 +- lib/src/account.dart | 10 +-- lib/src/address.dart | 38 ++++----- lib/src/bitcoincash.dart | 2 +- lib/src/cashaccounts.dart | 10 +-- lib/src/crypto/ecies.dart | 26 +++--- lib/src/crypto/ecurve.dart | 24 +++--- lib/src/ecpair.dart | 32 ++++---- lib/src/encoding/base58check.dart | 28 +++---- lib/src/encoding/utils.dart | 6 +- lib/src/hdnode.dart | 26 +++--- lib/src/privatekey.dart | 18 ++--- lib/src/publickey.dart | 44 +++++----- lib/src/rawtransactions.dart | 30 +++---- lib/src/slp.dart | 110 ++++++++++++------------- lib/src/transaction.dart | 130 +++++++++++++++--------------- lib/src/transactionbuilder.dart | 76 ++++++++--------- lib/src/utils/bip21.dart | 4 +- lib/src/utils/p2pkh.dart | 36 ++++----- lib/src/utils/p2sh.dart | 36 ++++----- lib/src/utils/pushdata.dart | 22 ++--- lib/src/utils/rest_api.dart | 6 +- lib/src/utils/script.dart | 38 ++++----- lib/src/varuint.dart | 4 +- pubspec.yaml | 2 +- test/bitbox_test.dart | 50 ++++++------ 26 files changed, 405 insertions(+), 405 deletions(-) diff --git a/example/main.dart b/example/main.dart index 3374226..757d3a3 100644 --- a/example/main.dart +++ b/example/main.dart @@ -75,7 +75,7 @@ void main() async { "original_amount": utxo.satoshis }); - totalBalance += utxo.satoshis; + totalBalance += utxo.satoshis!; }); // set an address to send the remaining balance to diff --git a/lib/src/account.dart b/lib/src/account.dart index f9ac976..167b167 100644 --- a/lib/src/account.dart +++ b/lib/src/account.dart @@ -4,21 +4,21 @@ import 'hdnode.dart'; class Account { final HDNode accountNode; - int currentChild = 0; + int? currentChild = 0; Account(this.accountNode, [this.currentChild]); /// Returns address at the current position - String getCurrentAddress([legacyFormat = true]) { + String? getCurrentAddress([legacyFormat = true]) { if (legacyFormat) { - return accountNode.derive(currentChild).toLegacyAddress(); + return accountNode.derive(currentChild!).toLegacyAddress(); } else { - return accountNode.derive(currentChild).toCashAddress(); + return accountNode.derive(currentChild!).toCashAddress(); } } /// moves the position forward and returns an address from the new position - String getNextAddress([legacyFormat = true]) { + String? getNextAddress([legacyFormat = true]) { if (legacyFormat) { return accountNode.derive(++currentChild).toLegacyAddress(); } else { diff --git a/lib/src/address.dart b/lib/src/address.dart index cf62df4..f6a3e44 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -54,8 +54,8 @@ class Address { /// Returns information about the given Bitcoin Cash address. /// /// See https://developer.bitcoin.com/bitbox/docs/util for details about returned format - static Future> validateAddress(String address) async => - await RestApi.sendGetRequest("util/validateAddress", address); + static Future?> validateAddress(String address) async => + await (RestApi.sendGetRequest("util/validateAddress", address) as FutureOr?>); /// Returns details of the provided address or addresses /// @@ -90,7 +90,7 @@ class Address { return Utxo.convertMapListToUtxos(result["utxos"]); } else if (result is List) { final returnList = []; - final returnMap = {}; + final returnMap = {}; result.forEach((addressUtxoMap) { if (returnAsMap) { @@ -123,7 +123,7 @@ class Address { return Utxo.convertMapListToUtxos(result["utxos"]); } else if (result is List) { final returnList = []; - final returnMap = {}; + final returnMap = {}; result.forEach((addressUtxoMap) { if (returnAsMap) { @@ -147,7 +147,7 @@ class Address { final decoded = _decode(address); final testnet = decoded['prefix'] == "bchtest" || decoded['prefix'] == "slptest"; - var version; + late var version; if (testnet) { switch (decoded['type']) { case "P2PKH": @@ -171,7 +171,7 @@ class Address { } /// Converts legacy address to cash address - static String toCashAddress(String address, [bool includePrefix = true]) { + static String? toCashAddress(String address, [bool includePrefix = true]) { final decoded = _decode(address); switch (decoded["prefix"]) { case 'bitcoincash': @@ -195,7 +195,7 @@ class Address { } /// Converts legacy or cash address to SLP address - static String toSLPAddress(String address, [bool includePrefix = true]) { + static String? toSLPAddress(String address, [bool includePrefix = true]) { final decoded = Address._decode(address); switch (decoded["prefix"]) { case 'bitcoincash': @@ -231,7 +231,7 @@ class Address { } /// Detects type of the address and returns [legacy], [cashaddr] or [slpaddr] - static String detectAddressFormat(String address) { + static String? detectAddressFormat(String address) { // decode the address to determine the format final decoded = _decode(address); // return the format @@ -261,7 +261,7 @@ class Address { /// [prefix] - Network prefix. E.g.: 'bitcoincash'. /// [type] Type of address to generate. Either 'P2PKH' or 'P2SH'. /// [hash] is the address hash, which can be decode either using [_decodeCashAddress()] or [_decodeLegacyAddress()] - static _encode(String prefix, String type, Uint8List hash) { + static _encode(String prefix, String? type, Uint8List hash) { final prefixData = _prefixToUint5List(prefix) + Uint8List(1); final versionByte = _getTypeBits(type) + _getHashSizeBits(hash); final payloadData = @@ -278,7 +278,7 @@ class Address { assert(addresses is String || addresses is List); if (addresses is String) { - return await RestApi.sendGetRequest("address/$path", addresses) as Map; + return await RestApi.sendGetRequest("address/$path", addresses) as Map?; } else if (addresses is List) { return await RestApi.sendPostRequest( "address/$path", "addresses", addresses, @@ -453,7 +453,7 @@ class Address { throw FormatException("Invalid Address Format: $address"); } - String exception; + late String exception; // try to decode the address with either one or all three possible prefixes for (int i = 0; i < prefixes.length; i++) { final payload = _base32Decode(address); @@ -514,7 +514,7 @@ class Address { throw FormatException("Invalid Address Format: $address"); } - String exception; + late String exception; // try to decode the address with either one or all three possible prefixes for (int i = 0; i < prefixes.length; i++) { @@ -642,7 +642,7 @@ class Address { final value = string[i]; if (!_CHARSET_INVERSE_INDEX.containsKey(value)) throw FormatException("Invalid character '$value'"); - data[i] = _CHARSET_INVERSE_INDEX[string[i]]; + data[i] = _CHARSET_INVERSE_INDEX[string[i]]!; } return data; @@ -672,12 +672,12 @@ class Address { /// Container for to make it easier to work with Utxos class Utxo { - final String txid; - final int vout; - final double amount; - final int satoshis; - final int height; - final int confirmations; + final String? txid; + final int? vout; + final double? amount; + final int? satoshis; + final int? height; + final int? confirmations; Utxo(this.txid, this.vout, this.amount, this.satoshis, this.height, this.confirmations); diff --git a/lib/src/bitcoincash.dart b/lib/src/bitcoincash.dart index f31418c..0249a63 100644 --- a/lib/src/bitcoincash.dart +++ b/lib/src/bitcoincash.dart @@ -40,7 +40,7 @@ class BitcoinCash { return signatureBuffer; } - static Uint8List getOpReturnScript(String data) { + static Uint8List? getOpReturnScript(String data) { return compile([Opcodes.OP_RETURN, utf8.encode(data)]); } } diff --git a/lib/src/cashaccounts.dart b/lib/src/cashaccounts.dart index 198e983..0fc7e4c 100644 --- a/lib/src/cashaccounts.dart +++ b/lib/src/cashaccounts.dart @@ -3,7 +3,7 @@ import 'utils/rest_api.dart'; import 'package:http/http.dart' as http; class CashAccounts { - static Future lookup(String account, int number, {int collision}) async { + static Future lookup(String account, int number, {int? collision}) async { String col = ""; if (collision != null) { col = collision.toString(); @@ -13,19 +13,19 @@ class CashAccounts { return json.decode(response.body); } - static Future check(String account, int number) async { + static Future check(String account, int number) async { final response = await http.get(Uri.parse( "https://rest.bitcoin.com/v2/cashAccounts/check/$account/$number")); return json.decode(response.body); } - static Future reverseLookup(String cashAddress) async { + static Future reverseLookup(String cashAddress) async { final response = await http.get(Uri.parse( "https://rest.bitcoin.com/v2/cashAccounts/reverseLookup/$cashAddress")); return json.decode(response.body); } - static Future register(String name, String address) async { + static Future register(String name, String address) async { Map register = { 'name': name, 'payments': [address] @@ -34,7 +34,7 @@ class CashAccounts { Uri.parse('https://api.cashaccount.info/register'), headers: {'Content-Type': 'application/json'}, body: jsonEncode(register)); - Map data = jsonDecode(response.body); + Map? data = jsonDecode(response.body); return data; } } diff --git a/lib/src/crypto/ecies.dart b/lib/src/crypto/ecies.dart index 4586d55..8b69372 100644 --- a/lib/src/crypto/ecies.dart +++ b/lib/src/crypto/ecies.dart @@ -34,9 +34,9 @@ class Ecies { /// [recipientPublicKey] - Public Key of the party who can decrypt the message /// static String encryptData( - {String message, - String senderPrivateKeyHex, - String recipientPublicKeyHex, + {required String message, + required String senderPrivateKeyHex, + required String recipientPublicKeyHex, String magicValue = "BIE1"}) { //Encryption requires derivation of a cipher using the other party's Public Key // Bob is sender, Alice is recipient of encrypted message @@ -51,10 +51,10 @@ class Ecies { List messageBuffer = Uint8List.fromList(message.codeUnits); - final ECPoint S = (recipientPublicKey.point * - senderPrivateKey.privateKey); //point multiplication + final ECPoint S = (recipientPublicKey.point! * + senderPrivateKey.privateKey)!; //point multiplication - final pubkeyS = BCHPublicKey.fromXY(S.x.toBigInteger(), S.y.toBigInteger()); + final pubkeyS = BCHPublicKey.fromXY(S.x!.toBigInteger()!, S.y!.toBigInteger()!); final pubkeyBuffer = HEX.decode(pubkeyS.getEncoded(true)); final pubkeyHash = SHA512Digest().process(pubkeyBuffer as Uint8List); @@ -73,7 +73,7 @@ class Ecies { final magic = utf8.encode(magicValue); final encodedBuffer = Uint8List.fromList( - magic + HEX.decode(senderPrivateKey.publicKey.toHex()) + cipherText); + magic + HEX.decode(senderPrivateKey.publicKey!.toHex()) + cipherText); //calc checksum final hmac = _calculateHmac(kM, encodedBuffer); @@ -101,8 +101,8 @@ class Ecies { /// [recipientPrivateKey] - Private Key of the receiving party /// static String decryptData( - {String cipherTextStr, - String recipientPrivateKeyHex, + {required String cipherTextStr, + required String recipientPrivateKeyHex, String magicValue = "BIE1"}) { //AES Cipher is calculated as //1) S = recipientPrivateKey o senderPublicKey @@ -128,8 +128,8 @@ class Ecies { BCHPublicKey.fromHex(HEX.encode(senderPubkeyBuffer)); //calculate S = recipientPrivateKey o senderPublicKey - final S = (senderPublicKey.point * - recipientPrivateKey.privateKey); //point multiplication + final S = (senderPublicKey.point! * + recipientPrivateKey.privateKey)!; //point multiplication final cipher = S.x; if (cipherText.length - _tagLength <= 37) { @@ -138,7 +138,7 @@ class Ecies { } //validate the checksum bytes - final pubkeyS = BCHPublicKey.fromXY(S.x.toBigInteger(), S.y.toBigInteger()); + final pubkeyS = BCHPublicKey.fromXY(S.x!.toBigInteger()!, S.y!.toBigInteger()!); final pubkeyBuffer = HEX.decode(pubkeyS.getEncoded(true)); final pubkeyHash = SHA512Digest().process(pubkeyBuffer as Uint8List); @@ -153,7 +153,7 @@ class Ecies { final Uint8List hmac = _calculateHmac(kM, message); final Uint8List messageChecksum = - cipherText.sublist(cipherText.length - _tagLength, cipherText.length); + cipherText.sublist(cipherText.length - _tagLength, cipherText.length) as Uint8List; // ignore: prefer_const_constructors if (!ListEquality().equals(messageChecksum, hmac)) { diff --git a/lib/src/crypto/ecurve.dart b/lib/src/crypto/ecurve.dart index 3493659..56e2912 100644 --- a/lib/src/crypto/ecurve.dart +++ b/lib/src/crypto/ecurve.dart @@ -35,7 +35,7 @@ class ECurve { return result; } - static Uint8List privateAdd(Uint8List d, Uint8List tweak) { + static Uint8List? privateAdd(Uint8List d, Uint8List tweak) { // if (!isPrivate(d)) throw new ArgumentError(THROW_BAD_PRIVATE); // if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK); BigInt dd = decodeBigInt(d); @@ -48,31 +48,31 @@ class ECurve { static bool isPrivate(Uint8List x) { if (!isScalar(x)) return false; return _compare(x, zero32) > 0 && // > 0 - _compare(x, ecGroupOrder) < 0; // < G + _compare(x, ecGroupOrder as Uint8List) < 0; // < G } static bool isScalar(Uint8List x) { return x.length == 32; } - static Uint8List pointFromScalar(Uint8List d, bool _compressed) { + static Uint8List? pointFromScalar(Uint8List d, bool _compressed) { // if (!isPrivate(d)) throw new ArgumentError(THROW_BAD_PRIVATE); BigInt dd = decodeBigInt(d); - ECPoint pp = G * dd; + ECPoint pp = (G * dd)!; if (pp.isInfinity) return null; return pp.getEncoded(_compressed); } - static Uint8List pointAddScalar( + static Uint8List? pointAddScalar( Uint8List p, Uint8List tweak, bool _compressed) { // if (!isPoint(p)) throw new ArgumentError(THROW_BAD_POINT); // if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK); bool compressed = assumeCompression(_compressed, p); - ECPoint pp = decodeFrom(p); - if (_compare(tweak, zero32) == 0) return pp.getEncoded(compressed); + ECPoint? pp = decodeFrom(p); + if (_compare(tweak, zero32) == 0) return pp!.getEncoded(compressed); BigInt tt = decodeBigInt(tweak); - ECPoint qq = G * tt; - ECPoint uu = pp + qq; + ECPoint? qq = G * tt; + ECPoint uu = (pp! + qq)!; if (uu.isInfinity) return null; return uu.getEncoded(compressed); } @@ -101,7 +101,7 @@ class ECurve { if (_compare(x, zero32) == 0) { return false; } - if (_compare(x, ecP) == 1) { + if (_compare(x, ecP as Uint8List) == 1) { return false; } try { @@ -116,7 +116,7 @@ class ECurve { if (_compare(y, zero32) == 0) { return false; } - if (_compare(y, ecP) == 1) { + if (_compare(y, ecP as Uint8List) == 1) { return false; } if (t == 0x04 && p.length == 65) { @@ -129,5 +129,5 @@ class ECurve { return p[0] != 0x04; } - static ECPoint decodeFrom(Uint8List P) => secp256k1.curve.decodePoint(P); + static ECPoint? decodeFrom(Uint8List P) => secp256k1.curve.decodePoint(P); } diff --git a/lib/src/ecpair.dart b/lib/src/ecpair.dart index 3044cff..06d4e32 100644 --- a/lib/src/ecpair.dart +++ b/lib/src/ecpair.dart @@ -9,17 +9,17 @@ import 'utils/network.dart'; /// Stores a keypair and provides various methods and factories for creating it and working with it class ECPair { - final Uint8List _d; - final Uint8List _q; + final Uint8List? _d; + final Uint8List? _q; final Network network; - final bool compressed; + final bool? compressed; /// Default constructor. If [network] is not provided, it will assume Bitcoin Cash mainnet ECPair(this._d, this._q, {network, this.compressed = true}) : this.network = network ?? Network.bitcoinCash(); /// Creates a keypair from the private key provided in WIF format - factory ECPair.fromWIF(String wifPrivateKey, {Network network}) { + factory ECPair.fromWIF(String wifPrivateKey, {Network? network}) { wif.WIF decoded = wif.decode(wifPrivateKey); final version = decoded.version; // TODO support multi networks @@ -42,7 +42,7 @@ class ECPair { /// Creates a keypair from [publicKey. The returned keypair will contain [null] private key factory ECPair.fromPublicKey(Uint8List publicKey, - {Network network, bool compressed}) { + {Network? network, bool? compressed}) { if (!ecc.isPoint(publicKey)) { throw ArgumentError("Point is not on the curve"); } @@ -51,7 +51,7 @@ class ECPair { /// Creates a keypair from [privateKey] factory ECPair.fromPrivateKey(Uint8List privateKey, - {Network network, bool compressed}) { + {Network? network, bool? compressed}) { if (privateKey.length != 32) throw ArgumentError( "Expected property privateKey of type Buffer(Length: 32)"); @@ -61,24 +61,24 @@ class ECPair { } /// Creates a random keypair - factory ECPair.makeRandom({Network network, bool compressed, Function rng}) { + factory ECPair.makeRandom({Network? network, bool? compressed, Function? rng}) { final rfunc = rng ?? _randomBytes; - Uint8List d; + Uint8List? d; // int beginTime = DateTime.now().millisecondsSinceEpoch; do { d = rfunc(32); - if (d.length != 32) throw ArgumentError("Expected Buffer(Length: 32)"); + if (d!.length != 32) throw ArgumentError("Expected Buffer(Length: 32)"); // if (DateTime.now().millisecondsSinceEpoch - beginTime > 5000) throw ArgumentError("Timeout"); } while (!ecc.isPrivate(d)); return ECPair.fromPrivateKey(d, network: network, compressed: compressed); } - Uint8List get publicKey => _q ?? ecc.pointFromScalar(_d, compressed); + Uint8List? get publicKey => _q ?? ecc.pointFromScalar(_d!, compressed!); - Uint8List get privateKey => _d; + Uint8List? get privateKey => _d; String get address => - Address.toBase58Check(Crypto.hash160(publicKey), network.pubKeyHash); + Address.toBase58Check(Crypto.hash160(publicKey!), network.pubKeyHash); /// Returns the private key in WIF format String toWIF() { @@ -87,18 +87,18 @@ class ECPair { } return wif.encode(wif.WIF( version: network.private, - privateKey: privateKey, - compressed: compressed)); + privateKey: privateKey!, + compressed: compressed!)); } /// Signs the provided [hash] with the private key Uint8List sign(Uint8List hash) { - return ecc.sign(hash, privateKey); + return ecc.sign(hash, privateKey!); } /// Verifies whether the provided [signature] matches the [hash] using the keypair's [publicKey] bool verify(Uint8List hash, Uint8List signature) { - return ecc.verify(hash, publicKey, signature); + return ecc.verify(hash, publicKey!, signature); } } diff --git a/lib/src/encoding/base58check.dart b/lib/src/encoding/base58check.dart index 1476a51..9741668 100644 --- a/lib/src/encoding/base58check.dart +++ b/lib/src/encoding/base58check.dart @@ -6,7 +6,7 @@ import '../exceptions.dart'; var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; -List decode(String input) { +List decode(String input) { if (input.isEmpty) { return List(); } @@ -14,20 +14,20 @@ List decode(String input) { var encodedInput = utf8.encode(input); var uintAlphabet = utf8.encode(ALPHABET); - List INDEXES = List(128)..fillRange(0, 128, -1); + List INDEXES = List(128)..fillRange(0, 128, -1); for (int i = 0; i < ALPHABET.length; i++) { INDEXES[uintAlphabet[i]] = i; } // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). - List input58 = List(encodedInput.length); + List input58 = List(encodedInput.length); input58.fillRange(0, input58.length, 0); for (int i = 0; i < encodedInput.length; ++i) { var c = encodedInput[i]; - var digit = c < 128 ? INDEXES[c] : -1; + var digit = c < 128 ? INDEXES[c]! : -1; if (digit < 0) { - var buff = List(1)..add(c); - var invalidChar = utf8.decode(buff); + var buff = List(1)..add(c); + var invalidChar = utf8.decode(buff as List); throw new AddressFormatException( "Illegal character " + invalidChar + " at position " + i.toString()); } @@ -41,7 +41,7 @@ List decode(String input) { } // Convert base-58 digits to base-256 digits. - var decoded = List(encodedInput.length); + var decoded = List(encodedInput.length); decoded.fillRange(0, decoded.length, 0); int outputStart = decoded.length; for (int inputStart = zeros; inputStart < input58.length;) { @@ -65,11 +65,11 @@ List decode(String input) { * in the specified base, by the given divisor. The given number is modified in-place * to contain the quotient, and the return value is the remainder. */ -divmod(List number, int firstDigit, int base, int divisor) { +divmod(List number, int firstDigit, int base, int divisor) { // this is just long division which accounts for the base of the input digits int remainder = 0; for (int i = firstDigit; i < number.length; i++) { - int digit = number[i] & 0xFF; + int digit = number[i]! & 0xFF; int temp = remainder * base + digit; number[i] = (temp / divisor).toInt(); remainder = temp % divisor; @@ -88,7 +88,7 @@ Uint8List encode(List encodedInput) { // var encodedInput = utf8.encode(input); if (encodedInput.isEmpty) { - return List(); + return List() as Uint8List; } // Count leading zeros. @@ -120,12 +120,12 @@ Uint8List encode(List encodedInput) { return encoded.sublist(outputStart, encoded.length); } -List decodeChecked(String input) { - List decoded = decode(input); +List decodeChecked(String input) { + List decoded = decode(input); if (decoded.length < 4) throw new AddressFormatException("Input too short"); - List data = decoded.sublist(0, decoded.length - 4); - List checksum = decoded.sublist(decoded.length - 4, decoded.length); + List data = decoded.sublist(0, decoded.length - 4); + List checksum = decoded.sublist(decoded.length - 4, decoded.length); List actualChecksum = sha256Twice(data).sublist(0, 4); var byteConverted = actualChecksum diff --git a/lib/src/encoding/utils.dart b/lib/src/encoding/utils.dart index acbbb23..07cbd55 100644 --- a/lib/src/encoding/utils.dart +++ b/lib/src/encoding/utils.dart @@ -9,8 +9,8 @@ import 'dart:math'; //import 'package:pointycastle/src/utils.dart'; import 'package:pointycastle/export.dart'; -List sha256Twice(List bytes) { - var first = new SHA256Digest().process(Uint8List.fromList(bytes)); +List sha256Twice(List bytes) { + var first = new SHA256Digest().process(Uint8List.fromList(bytes as List)); var second = new SHA256Digest().process(first); return second.toList(); } @@ -25,7 +25,7 @@ List sha1(List bytes) { List hash160(List bytes) { List shaHash = new SHA256Digest().process(Uint8List.fromList(bytes)); - var ripeHash = new RIPEMD160Digest().process(shaHash); + var ripeHash = new RIPEMD160Digest().process(shaHash as Uint8List); return ripeHash.toList(); } diff --git a/lib/src/hdnode.dart b/lib/src/hdnode.dart index 220016b..8a4c0cb 100644 --- a/lib/src/hdnode.dart +++ b/lib/src/hdnode.dart @@ -35,13 +35,13 @@ class HDNode { int index = 0; int parentFingerprint = 0x00000000; - Uint8List get identifier => Crypto.hash160(publicKeyList); + Uint8List get identifier => Crypto.hash160(publicKeyList!); Uint8List get fingerprint => identifier.sublist(0, 4); - Uint8List get publicKeyList => _keyPair.publicKey; + Uint8List? get publicKeyList => _keyPair.publicKey; - String get privateKey => HEX.encode(_keyPair.privateKey); - String get publicKey => HEX.encode(publicKeyList); + String get privateKey => HEX.encode(_keyPair.privateKey!); + String get publicKey => HEX.encode(publicKeyList!); get rawPrivateKey => _keyPair.privateKey; @@ -57,7 +57,7 @@ class HDNode { final key = utf8.encode('Bitcoin seed'); - final I = Crypto.hmacSHA512(key, seed); + final I = Crypto.hmacSHA512(key as Uint8List, seed); final keyPair = ECPair(I.sublist(0, 32), null, network: network); @@ -167,10 +167,10 @@ class HDNode { throw ArgumentError("Missing private key for hardened child key"); } data[0] = 0x00; - data.setRange(1, 33, _keyPair.privateKey); + data.setRange(1, 33, _keyPair.privateKey!); data.buffer.asByteData().setUint32(33, index); } else { - data.setRange(0, 33, publicKeyList); + data.setRange(0, 33, publicKeyList!); data.buffer.asByteData().setUint32(33, index); } @@ -182,12 +182,12 @@ class HDNode { // } ECPair derivedKeyPair; if (!_isNeutered()) { - final ki = ECurve.privateAdd(_keyPair.privateKey, IL); + final ki = ECurve.privateAdd(_keyPair.privateKey!, IL); if (ki == null) return derive(index + 1); derivedKeyPair = ECPair(ki, null, network: this._keyPair.network); } else { - final ki = ECurve.pointAddScalar(publicKeyList, IL, true); + final ki = ECurve.pointAddScalar(publicKeyList!, IL, true); if (ki == null) return derive(index + 1); derivedKeyPair = ECPair(null, ki, network: this._keyPair.network); @@ -210,10 +210,10 @@ class HDNode { String toLegacyAddress() => _keyPair.address; /// Returns HDNode's address in cashAddr format - String toCashAddress() => Address.toCashAddress(toLegacyAddress()); + String? toCashAddress() => Address.toCashAddress(toLegacyAddress()); /// Returns HDNode's address in slpAddr format - String toSLPAddress() => Address.toSLPAddress(toLegacyAddress()); + String? toSLPAddress() => Address.toSLPAddress(toLegacyAddress()); HDNode _deriveHardened(int index) { return derive(index + HIGHEST_BIT); @@ -232,9 +232,9 @@ class HDNode { buffer.setRange(13, 45, _chainCode); if (!_isNeutered()) { bytes.setUint8(45, 0); - buffer.setRange(46, 78, _keyPair.privateKey); + buffer.setRange(46, 78, _keyPair.privateKey!); } else { - buffer.setRange(45, 78, publicKeyList); + buffer.setRange(45, 78, publicKeyList!); } return bs58check.encode(buffer); } diff --git a/lib/src/privatekey.dart b/lib/src/privatekey.dart index a0860cb..6c5230b 100644 --- a/lib/src/privatekey.dart +++ b/lib/src/privatekey.dart @@ -26,9 +26,9 @@ class BCHPrivateKey { var random = Random.secure(); - BigInt _d; - ECPrivateKey _ecPrivateKey; - BCHPublicKey _bchPublicKey; + BigInt? _d; + late ECPrivateKey _ecPrivateKey; + BCHPublicKey? _bchPublicKey; /// Constructs a random private key. /// @@ -44,11 +44,11 @@ class BCHPrivateKey { var retry = 100; //100 retries to get correct bitLength. Problem in PointyCastle lib ? - AsymmetricKeyPair keypair; + late AsymmetricKeyPair keypair; while (retry > 0) { keypair = generator.generateKeyPair(); ECPrivateKey key = keypair.privateKey as ECPrivateKey; - if (key.d.bitLength == 256) { + if (key.d!.bitLength == 256) { break; } else { retry--; @@ -60,7 +60,7 @@ class BCHPrivateKey { _ecPrivateKey = keypair.privateKey as ECPrivateKey; _d = _ecPrivateKey.d; - if (_d.bitLength != 256) { + if (_d!.bitLength != 256) { throw Exception( "Failed to generate a valid private key after 100 tries. Try again. "); } @@ -86,7 +86,7 @@ class BCHPrivateKey { /// Returns the *naked* private key Big Integer value as a hexadecimal string String toHex() { - return _d.toRadixString(16); + return _d!.toRadixString(16); } Uint8List _seed() { @@ -105,14 +105,14 @@ class BCHPrivateKey { } /// Returns the *naked* private key Big Integer value as a Big Integer - BigInt get privateKey { + BigInt? get privateKey { return _d; } /// Returns the [BCHPublicKey] corresponding to this ECDSA private key. /// /// NOTE: `Q = d * G` where *Q* is the public key, *d* is the private key and `G` is the curve's Generator. - BCHPublicKey get publicKey { + BCHPublicKey? get publicKey { return _bchPublicKey; } diff --git a/lib/src/publickey.dart b/lib/src/publickey.dart index b142df6..8892a9c 100644 --- a/lib/src/publickey.dart +++ b/lib/src/publickey.dart @@ -18,7 +18,7 @@ class BCHPublicKey { //We only deal with secp256k1 final _domainParams = ECDomainParameters('secp256k1'); - ECPoint _point; + ECPoint? _point; /// Creates a public key from it's corresponding ECDSA private key. /// @@ -27,11 +27,11 @@ class BCHPublicKey { /// /// [privkey] - The private key who's *d*-value we will use. BCHPublicKey.fromPrivateKey(BCHPrivateKey privkey) { - var decodedPrivKey = encodeBigInt(privkey.privateKey); + var decodedPrivKey = encodeBigInt(privkey.privateKey!); var hexPrivKey = HEX.encode(decodedPrivKey); var actualKey = hexPrivKey; - var point = (_domainParams.G * BigInt.parse(actualKey, radix: 16)); + var point = (_domainParams.G * BigInt.parse(actualKey, radix: 16))!; if (point.x == null && point.y == null) { throw Exception( 'Cannot generate point from private key. Private key greater than N ?'); @@ -39,7 +39,7 @@ class BCHPublicKey { //create a point taking into account compression request/indicator of parent private key var finalPoint = _domainParams.curve.createPoint( - point.x.toBigInteger(), point.y.toBigInteger(), privkey.isCompressed); + point.x!.toBigInteger()!, point.y!.toBigInteger()!, privkey.isCompressed); _checkIfOnCurve(finalPoint); // a bit paranoid @@ -94,15 +94,15 @@ class BCHPublicKey { _point = _transformDER(buffer, strict); - if (_point.isInfinity) { + if (_point!.isInfinity) { throw Exception('That public key generates point at infinity'); } - if (_point.y.toBigInteger() == BigInt.zero) { + if (_point!.y!.toBigInteger() == BigInt.zero) { throw Exception('Invalid Y value for this public key'); } - _checkIfOnCurve(_point); + _checkIfOnCurve(_point!); // _publicKey = ECPublicKey(_point, _domainParams); } @@ -120,15 +120,15 @@ class BCHPublicKey { // _parseHexString(pubkey); _point = _transformDER(HEX.decode(pubkey), strict); - if (_point.isInfinity) { + if (_point!.isInfinity) { throw Exception('That public key generates point at infinity'); } - if (_point.y.toBigInteger() == BigInt.zero) { + if (_point!.y!.toBigInteger() == BigInt.zero) { throw Exception('Invalid Y value for this public key'); } - _checkIfOnCurve(_point); + _checkIfOnCurve(_point!); // _publicKey = ECPublicKey(_point, _domainParams); } @@ -166,7 +166,7 @@ class BCHPublicKey { /// NOTE: The first byte will contain either an odd number or an even number, /// but this number is *NOT* a boolean flag. String getEncoded(bool compressed) { - return HEX.encode(_point.getEncoded(compressed)); + return HEX.encode(_point!.getEncoded(compressed)); } /// Returns the 'naked' public key value. Point compression is determined by @@ -178,7 +178,7 @@ class BCHPublicKey { return ''; } - return HEX.encode(_point.getEncoded(_point.isCompressed)); + return HEX.encode(_point!.getEncoded(_point!.isCompressed)); } /// Alias for the [toString()] method. @@ -237,7 +237,7 @@ class BCHPublicKey { var encoded = HEX.decode(pkHex); try { - var point = _domainParams.curve.decodePoint(encoded); + var point = _domainParams.curve.decodePoint(encoded)!; if (point.isCompressed && encoded.length != 33) { throw Exception( @@ -275,9 +275,9 @@ class BCHPublicKey { bool _checkIfOnCurve(ECPoint point) { //a bit of math copied from PointyCastle. ecc/ecc_fp.dart -> decompressPoint() - var x = _domainParams.curve.fromBigInteger(point.x.toBigInteger()); - var alpha = (x * ((x * x) + _domainParams.curve.a)) + _domainParams.curve.b; - ECFieldElement beta = alpha.sqrt(); + var x = _domainParams.curve.fromBigInteger(point.x!.toBigInteger()!); + var alpha = (x * ((x * x) + _domainParams.curve.a!)) + _domainParams.curve.b!; + ECFieldElement? beta = alpha.sqrt(); if (beta == null) { throw Exception('This point is not on the curve'); @@ -286,19 +286,19 @@ class BCHPublicKey { //slight-of-hand. Create compressed point, reconstruct and check Y value. var compressedPoint = _compressPoint(point); var checkPoint = - (_domainParams.curve.decodePoint(HEX.decode(compressedPoint))); - if (checkPoint.y.toBigInteger() != point.y.toBigInteger()) { + _domainParams.curve.decodePoint(HEX.decode(compressedPoint))!; + if (checkPoint.y!.toBigInteger() != point.y!.toBigInteger()) { throw Exception('This point is not on the curve'); } - return (point.x.toBigInteger() == BigInt.zero) && - (point.y.toBigInteger() == BigInt.zero); + return (point.x!.toBigInteger() == BigInt.zero) && + (point.y!.toBigInteger() == BigInt.zero); } /// Returns the (x,y) coordinates of this public key as an [ECPoint]. /// The author dislikes leaking the wrapped PointyCastle implementation, but is too /// lazy to write his own Point implementation. - ECPoint get point { + ECPoint? get point { return _point; } @@ -306,6 +306,6 @@ class BCHPublicKey { /// default when one calls the [toString()] or [toHex()] methods. /// Returns *false* otherwise. bool get isCompressed { - return _point.isCompressed; + return _point!.isCompressed; } } diff --git a/lib/src/rawtransactions.dart b/lib/src/rawtransactions.dart index b9384e3..dee3b75 100644 --- a/lib/src/rawtransactions.dart +++ b/lib/src/rawtransactions.dart @@ -6,32 +6,32 @@ import 'package:http/http.dart' as http; class RawTransactions { /// Send raw transaction to the network /// Returns the resulting txid - static Future sendRawTransaction(String rawTx) async => - await RestApi.sendGetRequest("rawtransactions/sendRawTransaction", rawTx); + static Future sendRawTransaction(String? rawTx) async => + await (RestApi.sendGetRequest("rawtransactions/sendRawTransaction", rawTx) as FutureOr); /// Send multiple raw transactions to the network /// Returns the resulting array of txids - static Future sendRawTransactions(List rawTxs) async => - await RestApi.sendPostRequest( - "rawtransactions/sendRawTransaction", "hexes", rawTxs); + static Future sendRawTransactions(List rawTxs) async => + await (RestApi.sendPostRequest( + "rawtransactions/sendRawTransaction", "hexes", rawTxs) as FutureOr?>); /// Returns a JSON object representing the serialized, hex-encoded transaction - static Future decodeRawTransaction(String hex) async => - await RestApi.sendGetRequest("rawtransactions/decodeRawTransaction", hex); + static Future decodeRawTransaction(String hex) async => + await (RestApi.sendGetRequest("rawtransactions/decodeRawTransaction", hex) as FutureOr?>); /// Returns bulk hex encoded transaction - static Future decodeRawTransactions(List hexes) async => - await RestApi.sendPostRequest( - "rawtransactions/decodeRawTransaction", "hexes", hexes); + static Future decodeRawTransactions(List hexes) async => + await (RestApi.sendPostRequest( + "rawtransactions/decodeRawTransaction", "hexes", hexes) as FutureOr?>); /// Decodes a hex-encoded script - static Future decodeScript(String script) async => - await RestApi.sendGetRequest("rawtransactions/decodeScript", script); + static Future decodeScript(String script) async => + await (RestApi.sendGetRequest("rawtransactions/decodeScript", script) as FutureOr?>); /// Decodes multiple hex-encoded scripts - static Future decodeScripts(List scripts) async => - await RestApi.sendPostRequest( - "rawtransactions/decodeScript", "hexes", scripts); + static Future decodeScripts(List scripts) async => + await (RestApi.sendPostRequest( + "rawtransactions/decodeScript", "hexes", scripts) as FutureOr?>); /// Returns the raw transaction data static Future getRawtransaction(String txid, {bool verbose = true}) async { diff --git a/lib/src/slp.dart b/lib/src/slp.dart index a330c54..bd08e91 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -21,14 +21,14 @@ class SLP { var slpMsg = parseSLP(HEX.decode(res['vout'][0]['scriptPubKey']['hex'])).toMap(); if (decimalConversion) { - Map slpMsgData = slpMsg['data']; + Map? slpMsgData = slpMsg['data'] as Map?; if (slpMsg['transactionType'] == "GENESIS" || slpMsg['transactionType'] == "MINT") { - slpMsgData['qty'] = + slpMsgData!['qty'] = slpMsgData['qty'] / math.pow(10, slpMsgData['decimals']); } else { - slpMsgData['amounts'] + slpMsgData!['amounts'] .map((o) => o / math.pow(10, slpMsgData['decimals'])); } } @@ -40,7 +40,7 @@ class SLP { } static getAllSlpBalancesAndUtxos(String address) async { - List utxos = await mapToSlpAddressUtxoResultArray(address); + List utxos = await (mapToSlpAddressUtxoResultArray(address) as FutureOr>); var txIds = []; utxos.forEach((i) { txIds.add(i['txid']); @@ -50,7 +50,7 @@ class SLP { } } - static Future mapToSlpAddressUtxoResultArray(String address) async { + static Future mapToSlpAddressUtxoResultArray(String address) async { var result; try { result = await Address.utxo([address]); @@ -72,7 +72,7 @@ class SLP { })); } - static mapToSLPUtxoArray({List utxos, String xpriv, String wif}) { + static mapToSLPUtxoArray({required List utxos, String? xpriv, String? wif}) { List utxo = []; utxos.forEach((txo) => utxo.add({ 'satoshis': new BigInt.from(txo['satoshis']), @@ -99,15 +99,15 @@ class SLP { } static simpleTokenSend({ - String tokenId, - List sendAmounts, - List inputUtxos, - List bchInputUtxos, - List tokenReceiverAddresses, - String slpChangeReceiverAddress, - String bchChangeReceiverAddress, - List requiredNonTokenOutputs, - int extraFee, + String? tokenId, + List? sendAmounts, + List? inputUtxos, + List? bchInputUtxos, + List? tokenReceiverAddresses, + String? slpChangeReceiverAddress, + String? bchChangeReceiverAddress, + List? requiredNonTokenOutputs, + int? extraFee, int extraBCH = 0, int type = 0x01, bool buildIncomplete = false, @@ -118,7 +118,7 @@ class SLP { if (tokenId is! String) { return Exception("Token id should be a String"); } - tokenReceiverAddresses.forEach((tokenReceiverAddress) { + tokenReceiverAddresses!.forEach((tokenReceiverAddress) { if (tokenReceiverAddress is! String) { throw new Exception("Token receiving address should be a String"); } @@ -131,11 +131,11 @@ class SLP { } var tokenInfo = await getTokenInformation(tokenId); - int decimals = tokenInfo['data']['decimals']; + int? decimals = tokenInfo['data']['decimals']; - sendAmounts.forEach((sendAmount) async { + sendAmounts!.forEach((sendAmount) async { if (sendAmount > 0) { - totalAmount += BigInt.from(sendAmount * math.pow(10, decimals)); + totalAmount += BigInt.from(sendAmount * math.pow(10, decimals!)); amounts.add(BigInt.from(sendAmount * math.pow(10, decimals))); } }); @@ -143,7 +143,7 @@ class SLP { // 1 Set the token send amounts, send tokens to a // new receiver and send token change back to the sender BigInt totalTokenInputAmount = BigInt.from(0); - inputUtxos.forEach((txo) => + inputUtxos!.forEach((txo) => totalTokenInputAmount += _preSendSlpJudgementCheck(txo, tokenId)); // 2 Compute the token Change amount. @@ -161,7 +161,7 @@ class SLP { tokenReceiverAddresses.add(slpChangeReceiverAddress); } - int tokenType = tokenInfo['tokenType']; + int? tokenType = tokenInfo['tokenType']; // 3 Create the Send OP_RETURN message var sendOpReturn; @@ -178,7 +178,7 @@ class SLP { Map result = await _buildRawSendTx( slpSendOpReturn: sendOpReturn, inputTokenUtxos: inputUtxos, - bchInputUtxos: bchInputUtxos, + bchInputUtxos: bchInputUtxos!, tokenReceiverAddresses: tokenReceiverAddresses, bchChangeReceiverAddress: bchChangeReceiverAddress, requiredNonTokenOutputs: requiredNonTokenOutputs, @@ -232,16 +232,16 @@ class SLP { } static _buildRawSendTx( - {List slpSendOpReturn, - List inputTokenUtxos, - List bchInputUtxos, - List tokenReceiverAddresses, - String bchChangeReceiverAddress, - List requiredNonTokenOutputs, - int extraBCH, - int extraFee, - bool buildIncomplete, - int hashType}) async { + {required List slpSendOpReturn, + required List inputTokenUtxos, + required List bchInputUtxos, + required List tokenReceiverAddresses, + String? bchChangeReceiverAddress, + List? requiredNonTokenOutputs, + int? extraBCH, + int? extraFee, + bool? buildIncomplete, + int? hashType}) async { // Check proper address formats are given tokenReceiverAddresses.forEach((addr) { if (!addr.startsWith('simpleledger:')) { @@ -258,7 +258,7 @@ class SLP { // Parse the SLP SEND OP_RETURN message var sendMsg = parseSLP(slpSendOpReturn).toMap(); - Map sendMsgData = sendMsg['data']; + Map sendMsgData = sendMsg['data'] as Map; // Make sure we're not spending inputs from any other token or baton var tokenInputQty = new BigInt.from(0); @@ -370,7 +370,7 @@ class SLP { } // Add change, if any - if (bchChangeAfterFeeSatoshis + new BigInt.from(extraBCH) > new BigInt.from(546)) { + if (bchChangeAfterFeeSatoshis + new BigInt.from(extraBCH!) > new BigInt.from(546)) { transactionBuilder.addOutput( bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt() + extraBCH); } @@ -380,31 +380,31 @@ class SLP { int slpIndex = 0; inputTokenUtxos.forEach((i) { ECPair paymentKeyPair; - String xpriv = i['xpriv']; - String wif = i['wif']; + String? xpriv = i['xpriv']; + String? wif = i['wif']; if (xpriv != null) { paymentKeyPair = HDNode.fromXPriv(xpriv).keyPair; } else { - paymentKeyPair = ECPair.fromWIF(wif); + paymentKeyPair = ECPair.fromWIF(wif!); } transactionBuilder.sign( - slpIndex, paymentKeyPair, i['satoshis'].toInt(), hashType); + slpIndex, paymentKeyPair, i['satoshis'].toInt(), hashType!); slpIndex++; }); int bchIndex = inputTokenUtxos.length; bchInputUtxos.forEach((i) { ECPair paymentKeyPair; - String xpriv = i['xpriv']; - String wif = i['wif']; + String? xpriv = i['xpriv']; + String? wif = i['wif']; if (xpriv != null) { paymentKeyPair = HDNode.fromXPriv(xpriv).keyPair; } else { - paymentKeyPair = ECPair.fromWIF(wif); + paymentKeyPair = ECPair.fromWIF(wif!); } transactionBuilder.sign( - bchIndex, paymentKeyPair, i['satoshis'].toInt(), hashType); + bchIndex, paymentKeyPair, i['satoshis'].toInt(), hashType!); bchIndex++; }); @@ -413,7 +413,7 @@ class SLP { // Build the transaction to hex and return // warn user if the transaction was not fully signed String hex; - if (buildIncomplete) { + if (buildIncomplete!) { hex = transactionBuilder.buildIncomplete().toHex(); return {'hex': hex, 'fee': sendCost - _extraFee}; } else { @@ -422,7 +422,7 @@ class SLP { // Check For Low Fee int outValue = 0; - transactionBuilder.tx.outputs.forEach((o) => outValue += o.value); + transactionBuilder.tx.outputs.forEach((o) => outValue += o.value!); int inValue = 0; inputTokenUtxos.forEach((i) => inValue += i['satoshis'].toInt()); bchInputUtxos.forEach((i) => inValue += i['satoshis']); @@ -435,7 +435,7 @@ class SLP { static int _calculateSendCost( int sendOpReturnLength, int inputUtxoSize, int outputs, - {String bchChangeAddress, int feeRate = 1, bool forTokens = true}) { + {String? bchChangeAddress, int feeRate = 1, bool forTokens = true}) { int nonfeeoutputs = 0; if (forTokens) { nonfeeoutputs = outputs * 546; @@ -459,16 +459,16 @@ class SLP { */ _createSimpleToken( - {String tokenName, - String tokenTicker, - int tokenAmount, - String documentUri, - Uint8List documentHash, - int decimals, - String tokenReceiverAddress, - String batonReceiverAddress, - String bchChangeReceiverAddress, - List inputUtxos, + {required String tokenName, + required String tokenTicker, + int? tokenAmount, + required String documentUri, + required Uint8List documentHash, + int? decimals, + String? tokenReceiverAddress, + required String batonReceiverAddress, + String? bchChangeReceiverAddress, + List? inputUtxos, int type = 0x01}) async { int batonVout = batonReceiverAddress.isNotEmpty ? 2 : null; if (decimals == null) { diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index ac7609b..6e88df7 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -27,7 +27,7 @@ class Transaction { '0000000000000000000000000000000000000000000000000000000000000001'); static final valueUint64Max = HEX.decode('ffffffffffffffff'); static final blankOutput = - Output(script: emptyScript, valueBuffer: valueUint64Max); + Output(script: emptyScript, valueBuffer: valueUint64Max as Uint8List?); static const SATOSHI_MAX = 21 * 1e14; int version; @@ -44,7 +44,7 @@ class Transaction { /// Creates transaction from its hex representation factory Transaction.fromHex(String hex) { - return Transaction.fromBuffer(HEX.decode(hex)); + return Transaction.fromBuffer(HEX.decode(hex) as Uint8List); } /// Creates transaction from its hex representation stored in list of integers @@ -53,7 +53,7 @@ class Transaction { ByteData bytes = buffer.buffer.asByteData(); Uint8List readSlice(n) { offset += n; - return buffer.sublist(offset - n, offset); + return buffer.sublist(offset - n as int, offset); } int readUInt32() { @@ -118,7 +118,7 @@ class Transaction { } /// Add input to the transaction. If [sequence] is not provided, defaults to [DEFAULT_SEQUENCE] - int addInput(Uint8List hash, int index, [int sequence, Uint8List scriptSig]) { + int addInput(Uint8List hash, int? index, [int? sequence, Uint8List? scriptSig]) { inputs.add(new Input( hash: hash, index: index, @@ -128,21 +128,21 @@ class Transaction { } /// Add input to the transaction - int addOutput(Uint8List scriptPubKey, int value) { + int addOutput(Uint8List? scriptPubKey, int? value) { outputs.add(new Output(script: scriptPubKey, value: value)); return outputs.length - 1; } - setInputScript(int index, Uint8List scriptSig) { + setInputScript(int index, Uint8List? scriptSig) { inputs[index].script = scriptSig; } /// Create hash for legacy signature - hashForSignature(int inIndex, Uint8List prevOutScript, int hashType) { + hashForSignature(int inIndex, Uint8List? prevOutScript, int hashType) { if (inIndex >= inputs.length) return one; // ignore OP_CODESEPARATOR final ourScript = - bscript.compile(bscript.decompile(prevOutScript).where((x) { + bscript.compile(bscript.decompile(prevOutScript!)!.where((x) { return x != Opcodes.OP_CODESEPARATOR; }).toList()); final txTmp = Transaction.clone(this); @@ -182,14 +182,14 @@ class Transaction { /// /// [amount] must not be null for BCH signatures hashForCashSignature( - int inIndex, Uint8List prevOutScript, int amount, int hashType) { + int inIndex, Uint8List? prevOutScript, int? amount, int hashType) { if ((hashType & SIGHASH_BITCOINCASHBIP143) > 0) { if (amount == null) { throw ArgumentError( 'Bitcoin Cash sighash requires value of input to be signed.'); } - return _hashForWitnessV0(inIndex, prevOutScript, amount, hashType); + return _hashForWitnessV0(inIndex, prevOutScript!, amount, hashType); } else { return hashForSignature(inIndex, prevOutScript, hashType); } @@ -199,12 +199,12 @@ class Transaction { return 8 + varuint.encodingLength(inputs.length) + varuint.encodingLength(outputs.length) + - inputs.fold(0, (sum, input) => sum + 40 + _varSliceSize(input.script)) + + inputs.fold(0, (sum, input) => sum + 40 + _varSliceSize(input.script!)) + outputs.fold( - 0, (sum, output) => sum + 8 + _varSliceSize(output.script)); + 0, (sum, output) => sum + 8 + _varSliceSize(output.script!)) as int; } - Uint8List toBuffer([Uint8List buffer, int initialOffset]) { + Uint8List toBuffer([Uint8List? buffer, int? initialOffset]) { return this._toBuffer(buffer, initialOffset); } @@ -222,21 +222,21 @@ class Transaction { _hashForWitnessV0( int inIndex, Uint8List prevOutScript, int amount, int hashType) { - Uint8List tBuffer; - int tOffset; + Uint8List? tBuffer; + int? tOffset; void writeSlice(Uint8List slice) { - tBuffer.setRange(tOffset, slice.length + tOffset, slice); + tBuffer!.setRange(tOffset!, slice.length + tOffset!, slice); tOffset += slice.length; } void writeUint32(int i) { - tBuffer.buffer.asByteData().setUint32(tOffset, i, Endian.little); + tBuffer!.buffer.asByteData().setUint32(tOffset!, i, Endian.little); tOffset += 4; } void writeUint64(int i) { - tBuffer.buffer.asByteData().setUint64(tOffset, i, Endian.little); + tBuffer!.buffer.asByteData().setUint64(tOffset!, i, Endian.little); tOffset += 8; } @@ -250,17 +250,17 @@ class Transaction { writeSlice(slice); } - Uint8List hashPrevoutputs = zero; - Uint8List hashSequence = zero; - Uint8List hashOutputs = zero; + Uint8List hashPrevoutputs = zero as Uint8List; + Uint8List hashSequence = zero as Uint8List; + Uint8List hashOutputs = zero as Uint8List; if ((hashType & SIGHASH_ANYONECANPAY == 0)) { tBuffer = Uint8List(36 * this.inputs.length); tOffset = 0; this.inputs.forEach((txInput) { - writeSlice(txInput.hash); - writeUint32(txInput.index); + writeSlice(txInput.hash!); + writeUint32(txInput.index!); }); hashPrevoutputs = bcrypto.Crypto.hash256(tBuffer); @@ -273,7 +273,7 @@ class Transaction { tOffset = 0; this.inputs.forEach((txInput) { - writeUint32(txInput.sequence); + writeUint32(txInput.sequence!); }); hashSequence = bcrypto.Crypto.hash256(tBuffer); @@ -282,14 +282,14 @@ class Transaction { if ((hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { final txOutputsSize = this.outputs.fold(0, (int sum, Output output) { - return sum + 8 + _varSliceSize(output.script); + return sum + 8 + _varSliceSize(output.script!); }); tBuffer = Uint8List(txOutputsSize); tOffset = 0; this.outputs.forEach((Output output) { - writeUint64(output.value); + writeUint64(output.value!); writeVarSlice(output.script); }); @@ -298,9 +298,9 @@ class Transaction { (inIndex < this.outputs.length)) { final output = this.outputs[inIndex]; - tBuffer = Uint8List(8 + _varSliceSize(output.script)); + tBuffer = Uint8List(8 + _varSliceSize(output.script!)); tOffset = 0; - writeUint64(output.value); + writeUint64(output.value!); writeVarSlice(output.script); hashOutputs = bcrypto.Crypto.hash256(tBuffer); @@ -313,23 +313,23 @@ class Transaction { writeUint32(this.version); writeSlice(hashPrevoutputs); writeSlice(hashSequence); - writeSlice(input.hash); - writeUint32(input.index); + writeSlice(input.hash!); + writeUint32(input.index!); writeVarSlice(prevOutScript); writeUint64(amount); - writeUint32(input.sequence); + writeUint32(input.sequence!); writeSlice(hashOutputs); writeUint32(this.locktime); writeUint32(hashType); return bcrypto.Crypto.hash256(tBuffer); } - _toBuffer([Uint8List buffer, initialOffset]) { + _toBuffer([Uint8List? buffer, initialOffset]) { if (buffer == null) buffer = new Uint8List(virtualSize()); var bytes = buffer.buffer.asByteData(); var offset = initialOffset ?? 0; writeSlice(slice) { - buffer.setRange(offset, offset + slice.length, slice); + buffer!.setRange(offset, offset + slice.length, slice); offset += slice.length; } @@ -403,15 +403,15 @@ class Transaction { /// Container for input data and factories to create them class Input { - Uint8List hash; - int index; - int sequence; - int value; - Uint8List script; - Uint8List signScript; - Uint8List prevOutScript; - List pubkeys; - List signatures; + Uint8List? hash; + int? index; + int? sequence; + int? value; + Uint8List? script; + Uint8List? signScript; + Uint8List? prevOutScript; + List? pubkeys; + List? signatures; Input( {this.hash, this.index, @@ -421,13 +421,13 @@ class Input { this.prevOutScript, this.pubkeys, this.signatures}) { - if (this.hash != null && this.hash.length != 32) + if (this.hash != null && this.hash!.length != 32) throw new ArgumentError("Invalid input hash"); - if (this.index != null && !isUint(this.index, 32)) + if (this.index != null && !isUint(this.index!, 32)) throw new ArgumentError("Invalid input index"); - if (this.sequence != null && !isUint(this.sequence, 32)) + if (this.sequence != null && !isUint(this.sequence!, 32)) throw new ArgumentError("Invalid input sequence"); - if (this.value != null && !isSatoshi(this.value)) + if (this.value != null && !isSatoshi(this.value!)) throw ArgumentError("Invalid ouput value"); } @@ -444,21 +444,21 @@ class Input { factory Input.clone(Input input) { return new Input( - hash: input.hash != null ? Uint8List.fromList(input.hash) : null, + hash: input.hash != null ? Uint8List.fromList(input.hash!) : null, index: input.index, - script: input.script != null ? Uint8List.fromList(input.script) : null, + script: input.script != null ? Uint8List.fromList(input.script!) : null, sequence: input.sequence, value: input.value, prevOutScript: input.prevOutScript != null - ? Uint8List.fromList(input.prevOutScript) + ? Uint8List.fromList(input.prevOutScript!) : null, pubkeys: input.pubkeys != null - ? input.pubkeys.map( - (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) + ? input.pubkeys!.map( + (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) as List? : null, signatures: input.signatures != null - ? input.signatures.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) + ? input.signatures!.map((signature) => + signature != null ? Uint8List.fromList(signature) : null) as List? : null, ); } @@ -480,11 +480,11 @@ class Input { /// Container for storing outputs and factories for working with them class Output { - Uint8List script; - int value; - Uint8List valueBuffer; - List pubkeys; - List signatures; + Uint8List? script; + int? value; + Uint8List? valueBuffer; + List? pubkeys; + List? signatures; Output( {this.script, @@ -492,7 +492,7 @@ class Output { this.pubkeys, this.signatures, this.valueBuffer}) { - if (value != null && !isSatoshi(value)) + if (value != null && !isSatoshi(value!)) throw ArgumentError("Invalid ouput value"); } /* @@ -508,18 +508,18 @@ class Output { }*/ factory Output.clone(Output output) { return new Output( - script: output.script != null ? Uint8List.fromList(output.script) : null, + script: output.script != null ? Uint8List.fromList(output.script!) : null, value: output.value, valueBuffer: output.valueBuffer != null - ? Uint8List.fromList(output.valueBuffer) + ? Uint8List.fromList(output.valueBuffer!) : null, pubkeys: output.pubkeys != null - ? output.pubkeys.map( - (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) + ? output.pubkeys!.map( + (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) as List? : null, signatures: output.signatures != null - ? output.signatures.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) + ? output.signatures!.map((signature) => + signature != null ? Uint8List.fromList(signature) : null) as List? : null, ); } diff --git a/lib/src/transactionbuilder.dart b/lib/src/transactionbuilder.dart index 9532fe4..6fc8289 100644 --- a/lib/src/transactionbuilder.dart +++ b/lib/src/transactionbuilder.dart @@ -29,7 +29,7 @@ class TransactionBuilder { final Map _prevTxSet = {}; /// Creates an empty transaction builder - TransactionBuilder({Network network, int maximumFeeRate}) + TransactionBuilder({Network? network, int? maximumFeeRate}) : this._network = network ?? Network.bitcoinCash(), this._maximumFeeRate = maximumFeeRate ?? 2500, this._inputs = [], @@ -37,7 +37,7 @@ class TransactionBuilder { /// Creates a builder from pre-built transaction factory TransactionBuilder.fromTransaction(Transaction transaction, - [Network network]) { + [Network? network]) { final txb = new TransactionBuilder(network: network); // Copy transaction fields txb.setVersion(transaction.version); @@ -50,7 +50,7 @@ class TransactionBuilder { // Copy inputs transaction.inputs.forEach((txIn) { - txb._addInputUnsafe(txIn.hash, txIn.index, + txb._addInputUnsafe(txIn.hash!, txIn.index, new Input(sequence: txIn.sequence, script: txIn.script)); }); @@ -73,7 +73,7 @@ class TransactionBuilder { // if any signatures exist, throw if (this._inputs.map((input) { if (input.signatures == null) return false; - return input.signatures.map((s) { + return input.signatures!.map((s) { return s != null; }).contains(true); }).contains(true)) { @@ -91,8 +91,8 @@ class TransactionBuilder { /// Returns vin of the input /// /// Throws [ArgumentError] if the inputs of this transaction can't be modified or if [txHashOrInstance] is invalid - int addInput(dynamic txHashOrInstance, int vout, - [int sequence, Uint8List prevOutScript]) { + int addInput(dynamic txHashOrInstance, int? vout, + [int? sequence, Uint8List? prevOutScript]) { assert(txHashOrInstance is String || txHashOrInstance is Uint8List || txHashOrInstance is Transaction); @@ -107,7 +107,7 @@ class TransactionBuilder { } else if (txHashOrInstance is Uint8List) { hash = txHashOrInstance; } else if (txHashOrInstance is Transaction) { - final txOut = txHashOrInstance.outputs[vout]; + final txOut = txHashOrInstance.outputs[vout!]; prevOutScript = txOut.script; value = txOut.value; hash = txHashOrInstance.getHash(); @@ -130,10 +130,10 @@ class TransactionBuilder { /// Returns output id /// /// Throws [ArgumentError] if outputs can't be modified or the output format is invalid - int addOutput(dynamic data, int value) { + int addOutput(dynamic data, int? value) { assert(data is String || data is Uint8List); - Uint8List scriptPubKey; + Uint8List? scriptPubKey; if (data is String) { if (Address.detectAddressFormat(data) == Address.formatCashAddr) { data = Address.toLegacyAddress(data); @@ -162,7 +162,7 @@ class TransactionBuilder { /// Add signature for the input [vin] using [keyPair] and with a specified [value] /// /// Throws [ArgumentError] if something goes wrong - sign(int vin, ECPair keyPair, int value, + sign(int vin, ECPair keyPair, int? value, [int hashType = Transaction.SIGHASH_ALL]) { hashType = hashType | Transaction.SIGHASH_BITCOINCASHBIP143; @@ -184,7 +184,7 @@ class TransactionBuilder { if (!_canSign(input)) { // Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); - _prepareInput(input, ourPubKey, value); + _prepareInput(input, ourPubKey!, value); } var signatureHash = @@ -192,18 +192,18 @@ class TransactionBuilder { // enforce in order signing of public keys var signed = false; - for (var i = 0; i < input.pubkeys.length; i++) { - if (HEX.encode(ourPubKey).compareTo(HEX.encode(input.pubkeys[i])) != 0) { + for (var i = 0; i < input.pubkeys!.length; i++) { + if (HEX.encode(ourPubKey!).compareTo(HEX.encode(input.pubkeys![i]!)) != 0) { continue; } - if (input.signatures[i] != null) { + if (input.signatures![i] != null) { throw ArgumentError('Signature already exists'); } final signature = keyPair.sign(signatureHash); - input.signatures[i] = bscript.encodeSignature(signature, hashType); + input.signatures![i] = bscript.encodeSignature(signature, hashType); signed = true; } @@ -242,8 +242,8 @@ class TransactionBuilder { for (var i = 0; i < _inputs.length; i++) { if (_inputs[i].pubkeys != null && _inputs[i].signatures != null && - _inputs[i].pubkeys.length != 0 && - _inputs[i].signatures.length != 0) { + _inputs[i].pubkeys!.length != 0 && + _inputs[i].signatures!.length != 0) { final result = _buildInput(_inputs[i]); tx.setInputScript(i, result); } else if (!allowIncomplete) { @@ -264,7 +264,7 @@ class TransactionBuilder { bool _canModifyInputs() { return _inputs.every((input) { if (input.signatures == null) return true; - return input.signatures.every((signature) { + return input.signatures!.every((signature) { if (signature == null) return true; return _signatureHashType(signature) & SIGHASH_ANYONECANPAY != 0; }); @@ -276,7 +276,7 @@ class TransactionBuilder { final nOutputs = _tx.outputs.length; return _inputs.every((input) { if (input.signatures == null) return true; - return input.signatures.every((signature) { + return input.signatures!.every((signature) { if (signature == null) return true; final hashType = _signatureHashType(signature); final hashTypeMod = hashType & 0x1f; @@ -300,9 +300,9 @@ class TransactionBuilder { // .build() will fail, but .buildIncomplete() is OK return (this._tx.outputs.length == 0) && _inputs.map((input) { - if (input.signatures == null || input.signatures.length == 0) + if (input.signatures == null || input.signatures!.length == 0) return false; - return input.signatures.map((signature) { + return input.signatures!.map((signature) { if (signature == null) return false; // no signature, no issue final hashType = _signatureHashType(signature); if (hashType & SIGHASH_NONE != 0) @@ -316,11 +316,11 @@ class TransactionBuilder { return input.pubkeys != null && input.signScript != null && input.signatures != null && - input.signatures.length == input.pubkeys.length && - input.pubkeys.length > 0; + input.signatures!.length == input.pubkeys!.length && + input.pubkeys!.length > 0; } - _addInputUnsafe(Uint8List hash, int vout, Input options) { + _addInputUnsafe(Uint8List hash, int? vout, Input options) { String txHash = HEX.encode(hash); Input input; if (_isCoinbaseHash(hash)) { @@ -331,7 +331,7 @@ class TransactionBuilder { if (_prevTxSet[prevTxOut] != null) throw new ArgumentError('Duplicate TxOut: ' + prevTxOut); if (options.script != null) { - input = Input.expandInput(options.script); + input = Input.expandInput(options.script!); } else { input = new Input(); } @@ -356,14 +356,14 @@ class TransactionBuilder { Map get prevTxSet => _prevTxSet; - Input _prepareInput(Input input, Uint8List kpPubKey, int value) { + Input _prepareInput(Input input, Uint8List kpPubKey, int? value) { final prevOutScript = bscript.compile([ Opcodes.OP_DUP, Opcodes.OP_HASH160, Crypto.hash160(kpPubKey), Opcodes.OP_EQUALVERIFY, Opcodes.OP_CHECKSIG - ]); + ])!; final expanded = _expandOutput(prevOutScript, kpPubKey); @@ -375,9 +375,9 @@ class TransactionBuilder { } // returns input script - Uint8List _buildInput(Input input) { + Uint8List? _buildInput(Input input) { // this is quite rudimentary for P2PKH purposes - return bscript.compile([input.signatures.first, input.pubkeys.first]); + return bscript.compile([input.signatures!.first, input.pubkeys!.first]); } Output _expandOutput(Uint8List script, Uint8List ourPubKey) { @@ -386,7 +386,7 @@ class TransactionBuilder { throw ArgumentError("Unsupport script!"); } - final scriptChunks = bscript.decompile(script); + final scriptChunks = bscript.decompile(script)!; // does our hash160(pubKey) match the output scripts? Uint8List pkh1 = scriptChunks[ @@ -400,14 +400,14 @@ class TransactionBuilder { return new Output(pubkeys: [ourPubKey], signatures: [null]); } - Uint8List _addressToOutputScript(String address, [Network nw]) { + Uint8List? _addressToOutputScript(String address, [Network? nw]) { final network = nw ?? Network.bitcoinCash(); final payload = bs58check.decode(address); if (payload.length < 21) throw ArgumentError(address + ' is too short'); if (payload.length > 21) throw ArgumentError(address + ' is too long'); final version = payload.buffer.asByteData().getUint8(0); var hash = payload.sublist(1); - Uint8List output; + Uint8List? output; if (hash.length != 20) throw new ArgumentError('Invalid address'); @@ -423,21 +423,21 @@ class TransactionBuilder { return output; } - Uint8List _pubkeyToOutputScript(Uint8List pubkey, [Network nw]) { + Uint8List? _pubkeyToOutputScript(Uint8List pubkey, [Network? nw]) { final network = nw ?? Network.bitcoinCash(); final p2pkh = P2PKH(data: P2PKHData(pubkey: pubkey), network: network); return p2pkh.data.output; } - Uint8List _scriptHashToOutputScript(Uint8List scriptHash, [Network nw]) { + Uint8List? _scriptHashToOutputScript(Uint8List scriptHash, [Network? nw]) { final network = nw ?? Network.bitcoinCash(); final p2shh = P2SH(data: P2SHData(scriptHash: scriptHash), network: network); return p2shh.data.output; } - Uint8List _toInputScript(Uint8List pubkey, Uint8List signature, - [Network nw]) { + Uint8List? _toInputScript(Uint8List pubkey, Uint8List signature, + [Network? nw]) { final network = nw ?? Network.bitcoinCash(); final p2pkh = P2PKH( data: P2PKHData(pubkey: pubkey, signature: signature), @@ -446,7 +446,7 @@ class TransactionBuilder { } bool _isP2PKHOutput(script) { - final buffer = bscript.compile(script); + final buffer = bscript.compile(script)!; return buffer.length == 25 && buffer[0] == Opcodes.OP_DUP && buffer[1] == Opcodes.OP_HASH160 && @@ -456,7 +456,7 @@ class TransactionBuilder { } bool _isP2SHOutput(script) { - final buffer = bscript.compile(script); + final buffer = bscript.compile(script)!; return buffer.length == 23 && buffer[0] == Opcodes.OP_HASH160 && buffer[1] == 0x14 && diff --git a/lib/src/utils/bip21.dart b/lib/src/utils/bip21.dart index 93fbedd..bc0f512 100644 --- a/lib/src/utils/bip21.dart +++ b/lib/src/utils/bip21.dart @@ -15,10 +15,10 @@ class Bip21 { String address = uri.substring(0, split == -1 ? null : split); if (uriOptions["amount"] != null) { - if (uriOptions["amount"].indexOf(",") != -1) + if (uriOptions["amount"]!.indexOf(",") != -1) throw ("Invalid amount: commas are invalid"); - double amount = double.tryParse(uriOptions["amount"]); + double? amount = double.tryParse(uriOptions["amount"]!); if (amount == null || amount.isNaN) throw ("Invalid amount: not a number"); if (!amount.isFinite) throw ("Invalid amount: not finite"); diff --git a/lib/src/utils/p2pkh.dart b/lib/src/utils/p2pkh.dart index 71dbd3a..a1f8a99 100644 --- a/lib/src/utils/p2pkh.dart +++ b/lib/src/utils/p2pkh.dart @@ -11,10 +11,10 @@ import 'network.dart'; /// This is almost exact copy of https://github.com/anicdh/bitcoin_flutter/blob/master/lib/src/payments/p2pkh.dart /// except using [Opcodes] static members instead of map class P2PKH { - P2PKHData data; - Network network; + late P2PKHData data; + late Network network; - P2PKH({@required data, network}) { + P2PKH({required data, network}) { this.network = network ?? Network.bitcoinCash(); this.data = data; _init(); @@ -22,21 +22,21 @@ class P2PKH { _init() { if (data.address != null) { - _getDataFromAddress(data.address); + _getDataFromAddress(data.address!); _getDataFromHash(); } else if (data.hash != null) { _getDataFromHash(); } else if (data.output != null) { - if (!isValidOutput(data.output)) + if (!isValidOutput(data.output!)) throw new ArgumentError('Output is invalid'); - data.hash = data.output.sublist(3, 23); + data.hash = data.output!.sublist(3, 23); _getDataFromHash(); } else if (data.pubkey != null) { - data.hash = Crypto.hash160(data.pubkey); + data.hash = Crypto.hash160(data.pubkey!); _getDataFromHash(); _getDataFromChunk(); } else if (data.input != null) { - List _chunks = bscript.decompile(data.input); + List _chunks = bscript.decompile(data.input!)!; _getDataFromChunk(_chunks); if (_chunks.length != 2) throw new ArgumentError('Input is invalid'); if (!bscript.isCanonicalScriptSignature(_chunks[0])) @@ -48,12 +48,12 @@ class P2PKH { } } - void _getDataFromChunk([List _chunks]) { + void _getDataFromChunk([List? _chunks]) { if (data.pubkey == null && _chunks != null) { data.pubkey = (_chunks[1] is int) ? new Uint8List.fromList([_chunks[1]]) : _chunks[1]; - data.hash = Crypto.hash160(data.pubkey); + data.hash = Crypto.hash160(data.pubkey!); _getDataFromHash(); } if (data.signature == null && _chunks != null) @@ -69,7 +69,7 @@ class P2PKH { if (data.address == null) { final payload = new Uint8List(21); payload.buffer.asByteData().setUint8(0, network.pubKeyHash); - payload.setRange(1, payload.length, data.hash); + payload.setRange(1, payload.length, data.hash!); data.address = bs58check.encode(payload); } if (data.output == null) { @@ -89,17 +89,17 @@ class P2PKH { if (version != network.pubKeyHash) throw new ArgumentError('Invalid version or Network mismatch'); data.hash = payload.sublist(1); - if (data.hash.length != 20) throw new ArgumentError('Invalid address'); + if (data.hash!.length != 20) throw new ArgumentError('Invalid address'); } } class P2PKHData { - String address; - Uint8List hash; - Uint8List output; - Uint8List signature; - Uint8List pubkey; - Uint8List input; + String? address; + Uint8List? hash; + Uint8List? output; + Uint8List? signature; + Uint8List? pubkey; + Uint8List? input; P2PKHData( {this.address, this.hash, diff --git a/lib/src/utils/p2sh.dart b/lib/src/utils/p2sh.dart index 9ebdf3a..f9ee4f8 100644 --- a/lib/src/utils/p2sh.dart +++ b/lib/src/utils/p2sh.dart @@ -11,10 +11,10 @@ import 'network.dart'; /// This is almost exact copy of https://github.com/anicdh/bitcoin_flutter/blob/master/lib/src/payments/p2pkh.dart /// except using [Opcodes] static members instead of map class P2SH { - P2SHData data; - Network network; + late P2SHData data; + late Network network; - P2SH({@required data, network}) { + P2SH({required data, network}) { this.network = network ?? Network.bitcoinCash(); this.data = data; _init(); @@ -22,21 +22,21 @@ class P2SH { _init() { if (data.address != null) { - _getDataFromAddress(data.address); + _getDataFromAddress(data.address!); _getDataFromHash(); } else if (data.hash != null) { _getDataFromHash(); } else if (data.output != null) { - if (!isValidOutput(data.output)) + if (!isValidOutput(data.output!)) throw new ArgumentError('Output is invalid'); - data.hash = data.output.sublist(2, 22); + data.hash = data.output!.sublist(2, 22); _getDataFromHash(); } else if (data.scriptHash != null) { - data.hash = Crypto.hash160(data.scriptHash); + data.hash = Crypto.hash160(data.scriptHash!); _getDataFromHash(); _getDataFromChunk(); } else if (data.input != null) { - List _chunks = bscript.decompile(data.input); + List _chunks = bscript.decompile(data.input!)!; _getDataFromChunk(_chunks); if (_chunks.length != 2) throw new ArgumentError('Input is invalid'); if (!bscript.isCanonicalScriptSignature(_chunks[0])) @@ -48,12 +48,12 @@ class P2SH { } } - void _getDataFromChunk([List _chunks]) { + void _getDataFromChunk([List? _chunks]) { if (data.scriptHash == null && _chunks != null) { data.scriptHash = (_chunks[1] is int) ? new Uint8List.fromList([_chunks[1]]) : _chunks[1]; - data.hash = Crypto.hash160(data.scriptHash); + data.hash = Crypto.hash160(data.scriptHash!); _getDataFromHash(); } if (data.signature == null && _chunks != null) @@ -71,7 +71,7 @@ class P2SH { if (data.address == null) { final payload = new Uint8List(21); payload.buffer.asByteData().setUint8(0, network.scriptHash); - payload.setRange(1, payload.length, data.hash); + payload.setRange(1, payload.length, data.hash!); data.address = bs58check.encode(payload); } if (data.output == null) { @@ -86,17 +86,17 @@ class P2SH { if (version != network.scriptHash) throw new ArgumentError('Invalid version or Network mismatch'); data.hash = payload.sublist(1); - if (data.hash.length != 20) throw new ArgumentError('Invalid address'); + if (data.hash!.length != 20) throw new ArgumentError('Invalid address'); } } class P2SHData { - String address; - Uint8List hash; - Uint8List output; - Uint8List signature; - Uint8List scriptHash; - Uint8List input; + String? address; + Uint8List? hash; + Uint8List? output; + Uint8List? signature; + Uint8List? scriptHash; + Uint8List? input; P2SHData( {this.address, this.hash, diff --git a/lib/src/utils/pushdata.dart b/lib/src/utils/pushdata.dart index 6233982..43db703 100644 --- a/lib/src/utils/pushdata.dart +++ b/lib/src/utils/pushdata.dart @@ -2,45 +2,45 @@ import 'dart:typed_data'; import '../utils/opcodes.dart'; class DecodedPushData { - int opcode; - int number; - int size; + int? opcode; + int? number; + int? size; DecodedPushData({this.opcode, this.number, this.size}); } class EncodedPushData { - int size; - Uint8List buffer; + int? size; + Uint8List? buffer; EncodedPushData({this.size, this.buffer}); } -EncodedPushData encode(Uint8List buffer, number, offset) { +EncodedPushData encode(Uint8List? buffer, number, offset) { var size = encodingLength(number); // ~6 bit if (size == 1) { - buffer.buffer.asByteData().setUint8(offset, number); + buffer!.buffer.asByteData().setUint8(offset, number); // 8 bit } else if (size == 2) { - buffer.buffer.asByteData().setUint8(offset, Opcodes.OP_PUSHDATA1); + buffer!.buffer.asByteData().setUint8(offset, Opcodes.OP_PUSHDATA1); buffer.buffer.asByteData().setUint8(offset + 1, number); // 16 bit } else if (size == 3) { - buffer.buffer.asByteData().setUint8(offset, Opcodes.OP_PUSHDATA2); + buffer!.buffer.asByteData().setUint8(offset, Opcodes.OP_PUSHDATA2); buffer.buffer.asByteData().setUint16(offset + 1, number, Endian.little); // 32 bit } else { - buffer.buffer.asByteData().setUint8(offset, Opcodes.OP_PUSHDATA4); + buffer!.buffer.asByteData().setUint8(offset, Opcodes.OP_PUSHDATA4); buffer.buffer.asByteData().setUint32(offset + 1, number, Endian.little); } return new EncodedPushData(size: size, buffer: buffer); } -DecodedPushData decode(Uint8List bf, int offset) { +DecodedPushData? decode(Uint8List bf, int offset) { ByteBuffer buffer = bf.buffer; int opcode = buffer.asByteData().getUint8(offset); int number, size; diff --git a/lib/src/utils/rest_api.dart b/lib/src/utils/rest_api.dart index 97a2e0c..ed314e5 100644 --- a/lib/src/utils/rest_api.dart +++ b/lib/src/utils/rest_api.dart @@ -9,7 +9,7 @@ class RestApi { } static Future sendGetRequest(String path, - [String parameter = ""]) async { + [String? parameter = ""]) async { final response = await http.get(Uri.parse("$_restUrl$path/$parameter")); if (response.statusCode == 200) { @@ -21,7 +21,7 @@ class RestApi { static Future sendPostRequest( String path, String postKey, List data, - {String returnKey}) async { + {String? returnKey}) async { if (postKey == null || postKey.isEmpty) { postKey = "addresses"; } @@ -45,7 +45,7 @@ class RestApi { "return data (below) is not List of Maps: \n${response.body}"); } - Map returnMap = {}; + Map returnMap = {}; responseData.forEach((Map item) { if (!item.containsKey(returnKey)) { diff --git a/lib/src/utils/script.dart b/lib/src/utils/script.dart index 5035990..5920713 100644 --- a/lib/src/utils/script.dart +++ b/lib/src/utils/script.dart @@ -8,15 +8,15 @@ import 'check_types.dart'; const OP_INT_BASE = Opcodes.OP_RESERVED; final zero = Uint8List.fromList([0]); -Uint8List compile(List chunks) { - final bufferSize = chunks.fold(0, (acc, chunk) { +Uint8List? compile(List chunks) { + final bufferSize = chunks.fold(0, (dynamic acc, chunk) { if (chunk is int) return acc + 1; if (chunk.length == 1 && asMinimalOP(chunk) != null) { return acc + 1; } return acc + pushData.encodingLength(chunk.length) + chunk.length; }); - var buffer = new Uint8List(bufferSize); + Uint8List? buffer = new Uint8List(bufferSize); var offset = 0; chunks.forEach((chunk) { @@ -25,29 +25,29 @@ Uint8List compile(List chunks) { // adhere to BIP62.3, minimal push policy final opcode = asMinimalOP(chunk); if (opcode != null) { - buffer.buffer.asByteData().setUint8(offset, opcode); + buffer!.buffer.asByteData().setUint8(offset, opcode); offset += 1; return null; } pushData.EncodedPushData epd = pushData.encode(buffer, chunk.length, offset); - offset += epd.size; + offset += epd.size!; buffer = epd.buffer; - buffer.setRange(offset, offset + chunk.length, chunk); + buffer!.setRange(offset, offset + chunk.length, chunk); offset += chunk.length; // opcode } else { - buffer.buffer.asByteData().setUint8(offset, chunk); + buffer!.buffer.asByteData().setUint8(offset, chunk); offset += 1; } }); - if (offset != buffer.length) + if (offset != buffer!.length) throw new ArgumentError("Could not decode chunks"); return buffer; } -List decompile(Uint8List buffer) { +List? decompile(Uint8List buffer) { List chunks = []; var i = 0; @@ -60,13 +60,13 @@ List decompile(Uint8List buffer) { // did reading a pushDataInt fail? if (d == null) return null; - i += d.size; + i += d.size!; // attempt to read too much data? - if (i + d.number > buffer.length) return null; + if (i + d.number! > buffer.length) return null; - final data = buffer.sublist(i, i + d.number); - i += d.number; + final data = buffer.sublist(i, i + d.number!); + i += d.number!; // decompile minimally final op = asMinimalOP(data); @@ -109,7 +109,7 @@ String toASM (List c) { }).join(' '); }*/ -int asMinimalOP(Uint8List buffer) { +int? asMinimalOP(Uint8List buffer) { if (buffer.length == 0) return Opcodes.OP_0; if (buffer.length != 1) return null; if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; @@ -169,17 +169,17 @@ Uint8List bip66encode(r, s) { if (lenS > 1 && (s[0] == 0x00) && s[1] & 0x80 == 0) throw new ArgumentError('S value excessively padded'); - var signature = new Uint8List(6 + lenR + lenS); + var signature = new Uint8List(6 + lenR + lenS as int); // 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] signature[0] = 0x30; signature[1] = signature.length - 2; signature[2] = 0x02; signature[3] = r.length; - signature.setRange(4, 4 + lenR, r); - signature[4 + lenR] = 0x02; - signature[5 + lenR] = s.length; - signature.setRange(6 + lenR, 6 + lenR + lenS, s); + signature.setRange(4, 4 + lenR as int, r); + signature[4 + lenR as int] = 0x02; + signature[5 + lenR as int] = s.length; + signature.setRange(6 + lenR as int, 6 + lenR + lenS as int, s); return signature; } diff --git a/lib/src/varuint.dart b/lib/src/varuint.dart index 25de476..f01bce1 100644 --- a/lib/src/varuint.dart +++ b/lib/src/varuint.dart @@ -1,7 +1,7 @@ import 'dart:typed_data'; import 'utils/check_types.dart'; -Uint8List encode(int number, [Uint8List buffer,int offset]) { +Uint8List encode(int number, [Uint8List? buffer,int? offset]) { // if (!isUint(number, 53)); buffer = buffer ?? new Uint8List(encodingLength(number)); @@ -30,7 +30,7 @@ Uint8List encode(int number, [Uint8List buffer,int offset]) { return buffer; } -int decode (Uint8List buffer, [int offset]) { +int decode (Uint8List buffer, [int? offset]) { offset = offset ?? 0; ByteData bytes = buffer.buffer.asByteData(); final first = bytes.getUint8(offset); diff --git a/pubspec.yaml b/pubspec.yaml index 490ca5c..2ef99f1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,7 +5,7 @@ author: Tomas Forgac homepage: https://github.com/tomasforgacbch/bitbox-flutter environment: - sdk: ">=2.1.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: flutter: diff --git a/test/bitbox_test.dart b/test/bitbox_test.dart index 721c51d..2b7d716 100644 --- a/test/bitbox_test.dart +++ b/test/bitbox_test.dart @@ -20,7 +20,7 @@ void main() { const BROADCAST_MAINNET_TRANSACTION = true; // Data generated by the original bitbox library - Map testData; + Map? testData; // Placeholder for data about master, account and childnodes for both networks Map nodeData = {"mainnet" : {}, "testnet" : {}}; @@ -38,7 +38,7 @@ void main() { testData = jsonDecode(testDataJson); // create a seed from the mnemonic - final seed = Bitbox.Mnemonic.toSeed(testData["mnemonic"]); + final seed = Bitbox.Mnemonic.toSeed(testData!["mnemonic"]); // create master nodes for both networks and store their master keys for (int i = 0; i < networks.length; i++) { @@ -48,8 +48,8 @@ void main() { final masterXpub = nodeData[network]["master_node"].toXPub(); // compare the result with the js test data - expect(masterXPriv, testData[network]["master_xpriv"]); - expect(masterXpub, testData[network]["master_xpub"]); + expect(masterXPriv, testData![network]["master_xpriv"]); + expect(masterXpub, testData![network]["master_xpub"]); } }); @@ -58,13 +58,13 @@ void main() { // generate the nodes for both networks for (int i = 0; i < networks.length; i++) { final network = networks[i]; - nodeData[network]["account_node"] = nodeData[network]["master_node"].derivePath(testData["account_path"]); + nodeData[network]["account_node"] = nodeData[network]["master_node"].derivePath(testData!["account_path"]); final accountXPriv = nodeData[network]["account_node"].toXPriv(); final accountXPub = nodeData[network]["account_node"].toXPub(); // compare the master private and public key with the original testing data - expect(accountXPriv, testData[network]["account_xpriv"]); - expect(accountXPub, testData[network]["account_xpub"]); + expect(accountXPriv, testData![network]["account_xpriv"]); + expect(accountXPub, testData![network]["account_xpub"]); } }); @@ -80,7 +80,7 @@ void main() { for (int i = 0; i < networks.length; i++) { final network = networks[i]; - testData[network]["child_nodes"].forEach((childTestData) { + testData![network]["child_nodes"].forEach((childTestData) { // generate the child node and extract its private key final childNode = nodeData[network]["account_node"].derive(childTestData["index"]); final childPrivateKey = childNode.privateKey; @@ -94,7 +94,7 @@ void main() { test('Generating child nodes and legacy addresses', () { for (int i = 0; i < networks.length; i++) { final network = networks[i]; - testData[network]["child_nodes"].forEach((childTestData) { + testData![network]["child_nodes"].forEach((childTestData) { final childNode = nodeData[network]["account_node"].derive(childTestData["index"]); final childLegacy = childNode.toLegacyAddress(); @@ -106,7 +106,7 @@ void main() { test('Generating child nodes and cash addresses', () { for (int i = 0; i < networks.length; i++) { final network = networks[i]; - testData[network]["child_nodes"].forEach((childTestData) { + testData![network]["child_nodes"].forEach((childTestData) { final childNode = nodeData[network]["account_node"].derive(childTestData["index"]); final childCashAddr = childNode.toCashAddress(); @@ -119,7 +119,7 @@ void main() { test('Converting cashAddr to legacy', () { for (int i = 0; i < networks.length; i++) { final network = networks[i]; - testData[network]["child_nodes"].forEach((childTestData) { + testData![network]["child_nodes"].forEach((childTestData) { final cashAddr = childTestData["cashAddress"]; expect(Bitbox.Address.toLegacyAddress(cashAddr), childTestData["toLegacy"]); @@ -130,7 +130,7 @@ void main() { test('Converting legacy to cashAddr', () { for (int i = 0; i < networks.length; i++) { final network = networks[i]; - testData[network]["child_nodes"].forEach((childTestData) { + testData![network]["child_nodes"].forEach((childTestData) { final legacy = childTestData["legacy"]; expect(Bitbox.Address.toCashAddress(legacy), childTestData["toCashAddr"]); @@ -139,21 +139,21 @@ void main() { }); // Placeholder to store addresses with balance for which to fetch utxos later - final utxosToFetch = >{}; + final utxosToFetch = >{}; test('Fetching address details', () async { for (int i = 0; i < networks.length; i++) { final network = networks[i]; - utxosToFetch[network] = []; + utxosToFetch[network] = []; // set rest url based on which network is being tested Bitbox.Bitbox.setRestUrl(restUrl: network == "mainnet" ? Bitbox.Bitbox.restUrl : Bitbox.Bitbox.trestUrl); // Placeholder for test addresses to fetch the details off - List testAddresses = []; + List testAddresses = []; // Accumulate the list of all addresses from the test file - testData[network]["child_nodes"].forEach((childTestData) { + testData![network]["child_nodes"].forEach((childTestData) { testAddresses.add(childTestData["cashAddress"]); }); @@ -168,7 +168,7 @@ void main() { // store all addresses with non-zero confirmed balance detailsAll.forEach((addressDetails) { if (addressDetails["balance"] > 0) { - utxosToFetch[network].add(addressDetails["cashAddress"]); + utxosToFetch[network]!.add(addressDetails["cashAddress"]); } }); } @@ -185,7 +185,7 @@ void main() { utxos[network] = []; // If there were addresses with non-zero balance for this network, fetch their utxos - if (utxosToFetch[network].length > 0) { + if (utxosToFetch[network]!.length > 0) { // set the appropriate rest api url Bitbox.Bitbox.setRestUrl(restUrl: network == "mainnet" ? Bitbox.Bitbox.restUrl : Bitbox.Bitbox.trestUrl); @@ -193,9 +193,9 @@ void main() { utxos[network] = await Bitbox.Address.utxo(utxosToFetch[network]) as List; // go through the list of the returned utxos - utxos[network].forEach((addressUtxo) { + utxos[network]!.forEach((addressUtxo) { // go through each address in the testing data to test if the utxos match - testData[network]["child_nodes"].forEach((childNode) { + testData![network]["child_nodes"].forEach((childNode) { if (childNode["cashAddress"] == addressUtxo["cashAddress"]) { for (int i = 0; i < addressUtxo["utxos"].length; i++) { Bitbox.Utxo utxo = addressUtxo["utxos"][i]; @@ -228,8 +228,8 @@ void main() { final builder = Bitbox.Bitbox.transactionBuilder(testnet: network == "testnet"); // go through the list of utxos accumulated in the previous test - utxos[network].forEach((addressUtxos) { - testData[network]["child_nodes"].forEach((childNode) { + utxos[network]!.forEach((addressUtxos) { + testData![network]["child_nodes"].forEach((childNode) { if (childNode["cashAddress"] == addressUtxos["cashAddress"]) { addressUtxos["utxos"].forEach((Bitbox.Utxo utxo) { // add the utxo as an input for the transaction @@ -242,7 +242,7 @@ void main() { "original_amount": utxo.satoshis }); - totalBalance += utxo.satoshis; + totalBalance += utxo.satoshis!; }); } }); @@ -257,7 +257,7 @@ void main() { final sendAmount = totalBalance - fee; // add the ouput based on the address provided in the testing data - builder.addOutput(testData[network]["output_address"], sendAmount); + builder.addOutput(testData![network]["output_address"], sendAmount); // sign all inputs signatures.forEach((signature) { @@ -268,7 +268,7 @@ void main() { final tx = builder.build(); // compare the transaction raw hex with the output from the original bitbox - expect(tx.toHex(), testData[network]["testing_tx_hex"]); + expect(tx.toHex(), testData![network]["testing_tx_hex"]); // add the raw transaction to the list to be (optionally) broadcastd rawTx[network] = tx.toHex(); From 117b166923fcb97d3fce81d5efdda68343d14f04 Mon Sep 17 00:00:00 2001 From: Sathish Date: Tue, 29 Nov 2022 20:31:48 +0530 Subject: [PATCH 104/106] manual null safe type fixes --- lib/src/account.dart | 10 ++++++++-- lib/src/address.dart | 1 + lib/src/encoding/base58check.dart | 13 ++++++------- lib/src/encoding/utils.dart | 2 +- lib/src/rawtransactions.dart | 1 + lib/src/slp.dart | 7 ++++--- lib/src/transaction.dart | 12 ++++++------ lib/src/utils/bip21.dart | 2 +- 8 files changed, 28 insertions(+), 20 deletions(-) diff --git a/lib/src/account.dart b/lib/src/account.dart index 167b167..c8d77c4 100644 --- a/lib/src/account.dart +++ b/lib/src/account.dart @@ -20,9 +20,15 @@ class Account { /// moves the position forward and returns an address from the new position String? getNextAddress([legacyFormat = true]) { if (legacyFormat) { - return accountNode.derive(++currentChild).toLegacyAddress(); + if (currentChild != null) { + currentChild = currentChild! + 1; + return accountNode.derive(currentChild!).toLegacyAddress(); + } } else { - return accountNode.derive(++currentChild).toCashAddress(); + if (currentChild != null) { + currentChild = currentChild! + 1; + return accountNode.derive(currentChild!).toCashAddress(); + } } } } diff --git a/lib/src/address.dart b/lib/src/address.dart index f6a3e44..0d64ff5 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; import 'utils/rest_api.dart'; diff --git a/lib/src/encoding/base58check.dart b/lib/src/encoding/base58check.dart index 9741668..ca8a272 100644 --- a/lib/src/encoding/base58check.dart +++ b/lib/src/encoding/base58check.dart @@ -8,25 +8,25 @@ var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; List decode(String input) { if (input.isEmpty) { - return List(); + return []; } var encodedInput = utf8.encode(input); var uintAlphabet = utf8.encode(ALPHABET); - List INDEXES = List(128)..fillRange(0, 128, -1); + List INDEXES = List.filled(128, -1); for (int i = 0; i < ALPHABET.length; i++) { INDEXES[uintAlphabet[i]] = i; } // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). - List input58 = List(encodedInput.length); + List input58 = List.generate(encodedInput.length, ((index) => index)); input58.fillRange(0, input58.length, 0); for (int i = 0; i < encodedInput.length; ++i) { var c = encodedInput[i]; var digit = c < 128 ? INDEXES[c]! : -1; if (digit < 0) { - var buff = List(1)..add(c); + var buff = [c]; var invalidChar = utf8.decode(buff as List); throw new AddressFormatException( "Illegal character " + invalidChar + " at position " + i.toString()); @@ -41,8 +41,7 @@ List decode(String input) { } // Convert base-58 digits to base-256 digits. - var decoded = List(encodedInput.length); - decoded.fillRange(0, decoded.length, 0); + var decoded = List.filled(encodedInput.length, 0); int outputStart = decoded.length; for (int inputStart = zeros; inputStart < input58.length;) { decoded[--outputStart] = divmod(input58, inputStart, 58, 256); @@ -88,7 +87,7 @@ Uint8List encode(List encodedInput) { // var encodedInput = utf8.encode(input); if (encodedInput.isEmpty) { - return List() as Uint8List; + return [] as Uint8List; } // Count leading zeros. diff --git a/lib/src/encoding/utils.dart b/lib/src/encoding/utils.dart index 07cbd55..7c37ca3 100644 --- a/lib/src/encoding/utils.dart +++ b/lib/src/encoding/utils.dart @@ -172,7 +172,7 @@ BigInt readVarInt(Uint8List buffer) { } } -int getBufferOffset(int count) { +int? getBufferOffset(int count) { if (count < 0xFD) return 1; if (count == 0xFD) return 3; //2 bytes == Uint16 diff --git a/lib/src/rawtransactions.dart b/lib/src/rawtransactions.dart index dee3b75..0c5826a 100644 --- a/lib/src/rawtransactions.dart +++ b/lib/src/rawtransactions.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'utils/rest_api.dart'; import 'package:http/http.dart' as http; diff --git a/lib/src/slp.dart b/lib/src/slp.dart index bd08e91..0497fc7 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:typed_data'; import 'package:bitbox/bitbox.dart'; import 'package:hex/hex.dart'; @@ -424,8 +425,8 @@ class SLP { int outValue = 0; transactionBuilder.tx.outputs.forEach((o) => outValue += o.value!); int inValue = 0; - inputTokenUtxos.forEach((i) => inValue += i['satoshis'].toInt()); - bchInputUtxos.forEach((i) => inValue += i['satoshis']); + inputTokenUtxos.forEach((i) => inValue = inValue + (i['satoshis'] as num).toInt()); + bchInputUtxos.forEach((i) => inValue = inValue + (i['satoshis'] as num).toInt()); if (inValue - outValue < hex.length / 2) { return {'hex': null, 'fee': "Insufficient fee"}; } @@ -470,7 +471,7 @@ class SLP { String? bchChangeReceiverAddress, List? inputUtxos, int type = 0x01}) async { - int batonVout = batonReceiverAddress.isNotEmpty ? 2 : null; + int batonVout = batonReceiverAddress.isNotEmpty ? 2 : 0; if (decimals == null) { throw Exception("Decimals property must be in range 0 to 9"); } diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 6e88df7..d9a0b18 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -52,8 +52,8 @@ class Transaction { var offset = 0; ByteData bytes = buffer.buffer.asByteData(); Uint8List readSlice(n) { - offset += n; - return buffer.sublist(offset - n as int, offset); + offset += n as int; + return buffer.sublist(offset - n, offset); } int readUInt32() { @@ -227,22 +227,22 @@ class Transaction { void writeSlice(Uint8List slice) { tBuffer!.setRange(tOffset!, slice.length + tOffset!, slice); - tOffset += slice.length; + tOffset = tOffset! + slice.length; } void writeUint32(int i) { tBuffer!.buffer.asByteData().setUint32(tOffset!, i, Endian.little); - tOffset += 4; + tOffset = tOffset! + 4; } void writeUint64(int i) { tBuffer!.buffer.asByteData().setUint64(tOffset!, i, Endian.little); - tOffset += 8; + tOffset = tOffset! + 8; } void writeVarInt(int i) { varuint.encode(i, tBuffer, tOffset); - tOffset += varuint.encodingLength(i); + tOffset = tOffset! + varuint.encodingLength(i); } writeVarSlice(slice) { diff --git a/lib/src/utils/bip21.dart b/lib/src/utils/bip21.dart index bc0f512..8230528 100644 --- a/lib/src/utils/bip21.dart +++ b/lib/src/utils/bip21.dart @@ -54,7 +54,7 @@ class Bip21 { uriOptions[key] = value.toString(); }); - if (uriOptions.isEmpty) uriOptions = null; + if (uriOptions.isEmpty) uriOptions = {}; query = Uri(queryParameters: uriOptions).toString(); // Dart isn't following RFC-3986... query = query.replaceAll(RegExp(r"\+"), "%20"); From 43951275c7931047c4603a38f66a7a5486fe7579 Mon Sep 17 00:00:00 2001 From: Sathish Date: Thu, 5 Jan 2023 17:58:20 +0530 Subject: [PATCH 105/106] fix type issue --- lib/src/slp.dart | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/src/slp.dart b/lib/src/slp.dart index 0497fc7..5a1a913 100644 --- a/lib/src/slp.dart +++ b/lib/src/slp.dart @@ -41,7 +41,8 @@ class SLP { } static getAllSlpBalancesAndUtxos(String address) async { - List utxos = await (mapToSlpAddressUtxoResultArray(address) as FutureOr>); + List utxos = await (mapToSlpAddressUtxoResultArray(address) + as FutureOr>); var txIds = []; utxos.forEach((i) { txIds.add(i['txid']); @@ -371,9 +372,10 @@ class SLP { } // Add change, if any - if (bchChangeAfterFeeSatoshis + new BigInt.from(extraBCH!) > new BigInt.from(546)) { - transactionBuilder.addOutput( - bchChangeReceiverAddress, bchChangeAfterFeeSatoshis.toInt() + extraBCH); + if (bchChangeAfterFeeSatoshis + new BigInt.from(extraBCH!) > + new BigInt.from(546)) { + transactionBuilder.addOutput(bchChangeReceiverAddress, + bchChangeAfterFeeSatoshis.toInt() + extraBCH); } if (hashType == null) hashType = Transaction.SIGHASH_ALL; @@ -409,7 +411,7 @@ class SLP { bchIndex++; }); - int _extraFee = (tokenReceiverAddresses.length + bchOnlyCount) * 546; + int _extraFee = (tokenReceiverAddresses.length + bchOnlyCount) * 546; // Build the transaction to hex and return // warn user if the transaction was not fully signed @@ -423,10 +425,12 @@ class SLP { // Check For Low Fee int outValue = 0; - transactionBuilder.tx.outputs.forEach((o) => outValue += o.value!); + transactionBuilder.tx.outputs.forEach((o) => outValue += o.value ?? 0); int inValue = 0; - inputTokenUtxos.forEach((i) => inValue = inValue + (i['satoshis'] as num).toInt()); - bchInputUtxos.forEach((i) => inValue = inValue + (i['satoshis'] as num).toInt()); + inputTokenUtxos + .forEach((i) => inValue += int.parse(i['satoshis'].toString())); + bchInputUtxos + .forEach((i) => inValue += int.parse(i['satoshis'].toString())); if (inValue - outValue < hex.length / 2) { return {'hex': null, 'fee': "Insufficient fee"}; } From d7dee4829ecb2c64c19f82db485928e057ce3f28 Mon Sep 17 00:00:00 2001 From: Sathish Date: Wed, 28 Jun 2023 20:22:04 +0530 Subject: [PATCH 106/106] add cash token address --- lib/src/address.dart | 33 ++++++++++- lib/src/hdnode.dart | 2 + pubspec.lock | 132 ++++++++++++++++++++++++++----------------- 3 files changed, 114 insertions(+), 53 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index 0d64ff5..e019a75 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -56,7 +56,8 @@ class Address { /// /// See https://developer.bitcoin.com/bitbox/docs/util for details about returned format static Future?> validateAddress(String address) async => - await (RestApi.sendGetRequest("util/validateAddress", address) as FutureOr?>); + await (RestApi.sendGetRequest("util/validateAddress", address) + as FutureOr?>); /// Returns details of the provided address or addresses /// @@ -195,6 +196,32 @@ class Address { } } + /// Converts legacy address to cash address + static String? toTokenAddress(String address, [bool includePrefix = true]) { + final decoded = _decode(address); + switch (decoded["prefix"]) { + case 'bitcoincash': + case 'simpleledger': + decoded['prefix'] = "bitcoincash"; + break; + case 'bchtest': + case 'slptest': + decoded['prefix'] = "bchtest"; + break; + default: + throw FormatException("Unsupported address format: $address"); + } + + final tokenAddress = + _encode(decoded['prefix'], "P2PKHWITHTOKENS", decoded["hash"]); + + if (!includePrefix) { + return tokenAddress.split(":")[1]; + } else { + return tokenAddress; + } + } + /// Converts legacy or cash address to SLP address static String? toSLPAddress(String address, [bool includePrefix = true]) { final decoded = Address._decode(address); @@ -352,6 +379,8 @@ class Address { return 'P2PKH'; case 8: return 'P2SH'; + case 16: + return 'P2PKHWITHTOKENS'; default: throw FormatException( 'Invalid address type in version byte: ' + versionByte + '.'); @@ -364,6 +393,8 @@ class Address { return 0; case 'P2SH': return 8; + case 'P2PKHWITHTOKENS': + return 16; default: throw new FormatException('Invalid type: ' + type + '.'); } diff --git a/lib/src/hdnode.dart b/lib/src/hdnode.dart index 8a4c0cb..44edbb3 100644 --- a/lib/src/hdnode.dart +++ b/lib/src/hdnode.dart @@ -215,6 +215,8 @@ class HDNode { /// Returns HDNode's address in slpAddr format String? toSLPAddress() => Address.toSLPAddress(toLegacyAddress()); + String? toTokenAddress() => Address.toTokenAddress(toLegacyAddress()); + HDNode _deriveHardened(int index) { return derive(index + HIGHEST_BIT); } diff --git a/pubspec.lock b/pubspec.lock index 9028f93..26a7f04 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,9 +5,10 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.11.0" bip32: dependency: "direct main" description: @@ -30,72 +31,82 @@ packages: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" bs58check: dependency: "direct main" description: name: bs58check - url: "https://pub.dartlang.org" + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 + url: "https://pub.dev" source: hosted version: "1.0.2" buffer: dependency: "direct main" description: name: buffer - url: "https://pub.dartlang.org" + sha256: "8962c12174f53e2e848a6acd7ac7fd63d8a1a6a316c20c458a832d87eba5422a" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.1" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.1" crypto: dependency: "direct overridden" description: name: crypto - url: "https://pub.dartlang.org" + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" fixnum: dependency: "direct main" description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -110,65 +121,74 @@ packages: dependency: "direct main" description: name: hex - url: "https://pub.dartlang.org" + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" source: hosted version: "0.2.0" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "0.13.6" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.7" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.15" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.2.0" meta: dependency: "direct main" description: name: meta - url: "https://pub.dartlang.org" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" pointycastle: dependency: "direct main" description: name: pointycastle - url: "https://pub.dartlang.org" + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.3" sky_engine: dependency: transitive description: flutter @@ -196,57 +216,65 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.5.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.2" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" sdks: - dart: ">=2.17.0-0 <3.0.0" + dart: ">=3.0.0-0 <4.0.0"