From ad9ec8872b98fa504a570f48efe4f77909b03b62 Mon Sep 17 00:00:00 2001 From: RideWindX Date: Thu, 22 Jul 2021 21:04:30 +0800 Subject: [PATCH] null-safety --- lib/src/address.dart | 8 +- lib/src/bitcoin_flutter_base.dart | 70 ++++++------ lib/src/classify.dart | 6 +- lib/src/ecpair.dart | 41 ++++--- lib/src/models/networks.dart | 16 ++- lib/src/payments/index.dart | 14 +-- lib/src/payments/p2pk.dart | 18 ++- lib/src/payments/p2pkh.dart | 28 ++--- lib/src/payments/p2wpkh.dart | 42 +++---- lib/src/templates/pubkey.dart | 2 +- lib/src/transaction.dart | 164 ++++++++++++++------------- lib/src/transaction_builder.dart | 105 ++++++++--------- lib/src/utils/magic_hash.dart | 4 +- lib/src/utils/push_data.dart | 29 +++-- lib/src/utils/script.dart | 26 ++--- lib/src/utils/varuint.dart | 10 +- pubspec.yaml | 20 +++- test/ecpair_test.dart | 26 ++--- test/integration/addresses_test.dart | 4 +- test/integration/bip32_test.dart | 12 +- test/payments/p2pkh_test.dart | 28 +++-- test/payments/p2wpkh_test.dart | 44 ++++--- test/transaction_builder_test.dart | 13 ++- test/transaction_test.dart | 6 +- 24 files changed, 401 insertions(+), 335 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index c310f12..3fa7902 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -7,7 +7,7 @@ import 'payments/p2pkh.dart'; import 'payments/p2wpkh.dart'; class Address { - static bool validateAddress(String address, [NetworkType nw]) { + static bool validateAddress(String address, [NetworkType? nw]) { try { addressToOutputScript(address, nw); return true; @@ -16,7 +16,7 @@ class Address { } } - static Uint8List addressToOutputScript(String address, [NetworkType nw]) { + static Uint8List addressToOutputScript(String address, [NetworkType? nw]) { NetworkType network = nw ?? bitcoin; var decodeBase58; var decodeBech32; @@ -28,7 +28,7 @@ class Address { throw new ArgumentError('Invalid version or Network mismatch'); P2PKH p2pkh = new P2PKH(data: new PaymentData(address: address), network: network); - return p2pkh.data.output; + return p2pkh.data.output!; } else { try { decodeBech32 = segwit.decode(address); @@ -40,7 +40,7 @@ class Address { throw new ArgumentError('Invalid address version'); P2WPKH p2wpkh = new P2WPKH( data: new PaymentData(address: address), network: network); - return p2wpkh.data.output; + return p2wpkh.data.output!; } } throw new ArgumentError(address + ' has no matching Script'); diff --git a/lib/src/bitcoin_flutter_base.dart b/lib/src/bitcoin_flutter_base.dart index 6de8167..d9a630d 100644 --- a/lib/src/bitcoin_flutter_base.dart +++ b/lib/src/bitcoin_flutter_base.dart @@ -7,70 +7,67 @@ import 'models/networks.dart'; import 'payments/index.dart' show PaymentData; import 'payments/p2pkh.dart'; import 'ecpair.dart'; -import 'package:meta/meta.dart'; -import 'dart:convert'; /// Checks if you are awesome. Spoiler: you are. class HDWallet { - bip32.BIP32 _bip32; - P2PKH _p2pkh; - String seed; + bip32.BIP32? _bip32; + P2PKH? _p2pkh; + String? seed; NetworkType network; - String get privKey { + String? get privKey { if (_bip32 == null) return null; try { - return HEX.encode(_bip32.privateKey); + return HEX.encode(_bip32!.privateKey!); } catch (_) { return null; } } - String get pubKey => _bip32 != null ? HEX.encode(_bip32.publicKey) : null; + String? get pubKey => _bip32 != null ? HEX.encode(_bip32!.publicKey) : null; - String get base58Priv { + String? get base58Priv { if (_bip32 == null) return null; try { - return _bip32.toBase58(); + return _bip32!.toBase58(); } catch (_) { return null; } } - String get base58 => _bip32 != null ? _bip32.neutered().toBase58() : null; + String? get base58 => _bip32 != null ? _bip32!.neutered().toBase58() : null; - String get wif { + String? get wif { if (_bip32 == null) return null; try { - return _bip32.toWIF(); + return _bip32!.toWIF(); } catch (_) { return null; } } - String get address => _p2pkh != null ? _p2pkh.data.address : null; + String? get address => _p2pkh != null ? _p2pkh!.data.address : null; - HDWallet( - {@required bip32, @required p2pkh, @required this.network, this.seed}) { + HDWallet({required bip32, required p2pkh, required this.network, this.seed}) { this._bip32 = bip32; this._p2pkh = p2pkh; } HDWallet derivePath(String path) { - final bip32 = _bip32.derivePath(path); + final bip32 = _bip32!.derivePath(path); final p2pkh = new P2PKH( data: new PaymentData(pubkey: bip32.publicKey), network: network); return HDWallet(bip32: bip32, p2pkh: p2pkh, network: network); } HDWallet derive(int index) { - final bip32 = _bip32.derive(index); + final bip32 = _bip32!.derive(index); final p2pkh = new P2PKH( data: new PaymentData(pubkey: bip32.publicKey), network: network); return HDWallet(bip32: bip32, p2pkh: p2pkh, network: network); } - factory HDWallet.fromSeed(Uint8List seed, {NetworkType network}) { + factory HDWallet.fromSeed(Uint8List seed, {NetworkType? network}) { network = network ?? bitcoin; final seedHex = HEX.encode(seed); final wallet = bip32.BIP32.fromSeed( @@ -85,7 +82,7 @@ class HDWallet { bip32: wallet, p2pkh: p2pkh, network: network, seed: seedHex); } - factory HDWallet.fromBase58(String xpub, {NetworkType network}) { + factory HDWallet.fromBase58(String xpub, {NetworkType? network}) { network = network ?? bitcoin; final wallet = bip32.BIP32.fromBase58( xpub, @@ -100,40 +97,41 @@ class HDWallet { Uint8List sign(String message) { Uint8List messageHash = magicHash(message, network); - return _bip32.sign(messageHash); + return _bip32!.sign(messageHash); } - bool verify({String message, Uint8List signature}) { + bool verify({required String message, required Uint8List signature}) { Uint8List messageHash = magicHash(message); - return _bip32.verify(messageHash, signature); + return _bip32!.verify(messageHash, signature); } } class Wallet { - ECPair _keyPair; - P2PKH _p2pkh; + ECPair? _keyPair; + P2PKH? _p2pkh; - String get privKey => - _keyPair != null ? HEX.encode(_keyPair.privateKey) : null; + String? get privKey => + _keyPair != null ? HEX.encode(_keyPair!.privateKey!) : null; - String get pubKey => _keyPair != null ? HEX.encode(_keyPair.publicKey) : null; + String? get pubKey => + _keyPair != null ? HEX.encode(_keyPair!.publicKey!) : null; - String get wif => _keyPair != null ? _keyPair.toWIF() : null; + String? get wif => _keyPair != null ? _keyPair!.toWIF() : null; - String get address => _p2pkh != null ? _p2pkh.data.address : null; + String? get address => _p2pkh != null ? _p2pkh!.data.address : null; - NetworkType network; + NetworkType? network; Wallet(this._keyPair, this._p2pkh, this.network); - factory Wallet.random([NetworkType network]) { + factory Wallet.random([NetworkType? network]) { final _keyPair = ECPair.makeRandom(network: network); final _p2pkh = new P2PKH( data: new PaymentData(pubkey: _keyPair.publicKey), network: network); return Wallet(_keyPair, _p2pkh, network); } - factory Wallet.fromWIF(String wif, [NetworkType network]) { + factory Wallet.fromWIF(String wif, [NetworkType? network]) { network = network ?? bitcoin; final _keyPair = ECPair.fromWIF(wif, network: network); final _p2pkh = new P2PKH( @@ -143,11 +141,11 @@ class Wallet { Uint8List sign(String message) { Uint8List messageHash = magicHash(message, network); - return _keyPair.sign(messageHash); + return _keyPair!.sign(messageHash); } - bool verify({String message, Uint8List signature}) { + bool verify({required String message, required Uint8List signature}) { Uint8List messageHash = magicHash(message, network); - return _keyPair.verify(messageHash, signature); + return _keyPair!.verify(messageHash, signature); } } diff --git a/lib/src/classify.dart b/lib/src/classify.dart index 9af1b34..f724ca9 100644 --- a/lib/src/classify.dart +++ b/lib/src/classify.dart @@ -16,7 +16,7 @@ const SCRIPT_TYPES = { 'WITNESS_COMMITMENT': 'witnesscommitment' }; -String classifyOutput(Uint8List script) { +String? classifyOutput(Uint8List script) { if (witnessPubKeyHash.outputCheck(script)) return SCRIPT_TYPES['P2WPKH']; if (pubkeyhash.outputCheck(script)) return SCRIPT_TYPES['P2PKH']; final chunks = bscript.decompile(script); @@ -24,7 +24,7 @@ String classifyOutput(Uint8List script) { return SCRIPT_TYPES['NONSTANDARD']; } -String classifyInput(Uint8List script) { +String? classifyInput(Uint8List script) { final chunks = bscript.decompile(script); if (chunks == null) throw new ArgumentError('Invalid script'); if (pubkeyhash.inputCheck(chunks)) return SCRIPT_TYPES['P2PKH']; @@ -32,7 +32,7 @@ String classifyInput(Uint8List script) { return SCRIPT_TYPES['NONSTANDARD']; } -String classifyWitness(List script) { +String? classifyWitness(List script) { final chunks = bscript.decompile(script); if (chunks == null) throw new ArgumentError('Invalid script'); if (witnessPubKeyHash.inputCheck(chunks)) return SCRIPT_TYPES['P2WPKH']; diff --git a/lib/src/ecpair.dart b/lib/src/ecpair.dart index fc8ce89..4ba3559 100644 --- a/lib/src/ecpair.dart +++ b/lib/src/ecpair.dart @@ -5,39 +5,40 @@ import 'package:bip32/src/utils/wif.dart' as wif; import 'models/networks.dart'; class ECPair { - Uint8List _d; - Uint8List _Q; + Uint8List? _d; + Uint8List? _Q; NetworkType network; bool compressed; - ECPair(Uint8List _d, Uint8List _Q, {network, compressed}) { - this._d = _d; - this._Q = _Q; - this.network = network ?? bitcoin; - this.compressed = compressed ?? true; - } - Uint8List get publicKey { - if (_Q == null) _Q = ecc.pointFromScalar(_d, compressed); + + ECPair(Uint8List? this._d, Uint8List? this._Q, + {NetworkType? network, bool? compressed}) + : network = network ?? bitcoin, + compressed = compressed ?? true {} + + Uint8List? get publicKey { + if (_Q == null) _Q = ecc.pointFromScalar(_d!, compressed); return _Q; } - Uint8List get privateKey => _d; + Uint8List? get privateKey => _d; + String toWIF() { if (privateKey == null) { throw new ArgumentError('Missing private key'); } return wif.encode(new wif.WIF( - version: network.wif, privateKey: privateKey, compressed: compressed)); + version: network.wif, privateKey: privateKey!, compressed: compressed)); } Uint8List sign(Uint8List hash) { - return ecc.sign(hash, privateKey); + return ecc.sign(hash, privateKey!); } bool verify(Uint8List hash, Uint8List signature) { - return ecc.verify(hash, publicKey, signature); + return ecc.verify(hash, publicKey!, signature); } - factory ECPair.fromWIF(String w, {NetworkType network}) { + factory ECPair.fromWIF(String w, {NetworkType? network}) { wif.WIF decoded = wif.decode(w); final version = decoded.version; // TODO support multi networks @@ -57,16 +58,18 @@ class ECPair { return ECPair.fromPrivateKey(decoded.privateKey, compressed: decoded.compressed, network: nw); } + factory ECPair.fromPublicKey(Uint8List publicKey, - {NetworkType network, bool compressed}) { + {NetworkType? network, bool? compressed}) { if (!ecc.isPoint(publicKey)) { throw new ArgumentError('Point is not on the curve'); } return new ECPair(null, publicKey, network: network, compressed: compressed); } + factory ECPair.fromPrivateKey(Uint8List privateKey, - {NetworkType network, bool compressed}) { + {NetworkType? network, bool? compressed}) { if (privateKey.length != 32) throw new ArgumentError( 'Expected property privateKey of type Buffer(Length: 32)'); @@ -75,8 +78,9 @@ class ECPair { return new ECPair(privateKey, null, network: network, compressed: compressed); } + factory ECPair.makeRandom( - {NetworkType network, bool compressed, Function rng}) { + {NetworkType? network, bool? compressed, Function? rng}) { final rfunc = rng ?? _randomBytes; Uint8List d; // int beginTime = DateTime.now().millisecondsSinceEpoch; @@ -90,6 +94,7 @@ class ECPair { } const int _SIZE_BYTE = 255; + Uint8List _randomBytes(int size) { final rng = Random.secure(); final bytes = Uint8List(size); diff --git a/lib/src/models/networks.dart b/lib/src/models/networks.dart index dc4771d..a9a633f 100644 --- a/lib/src/models/networks.dart +++ b/lib/src/models/networks.dart @@ -1,20 +1,18 @@ -import 'package:meta/meta.dart'; - class NetworkType { String messagePrefix; - String bech32; + String? bech32; Bip32Type bip32; int pubKeyHash; int scriptHash; int wif; NetworkType( - {@required this.messagePrefix, + {required this.messagePrefix, this.bech32, - @required this.bip32, - @required this.pubKeyHash, - @required this.scriptHash, - @required this.wif}); + required this.bip32, + required this.pubKeyHash, + required this.scriptHash, + required this.wif}); @override String toString() { @@ -26,7 +24,7 @@ class Bip32Type { int public; int private; - Bip32Type({@required this.public, @required this.private}); + Bip32Type({required this.public, required this.private}); @override String toString() { diff --git a/lib/src/payments/index.dart b/lib/src/payments/index.dart index b2b3ed9..f7af519 100644 --- a/lib/src/payments/index.dart +++ b/lib/src/payments/index.dart @@ -1,13 +1,13 @@ import 'dart:typed_data'; class PaymentData { - String address; - Uint8List hash; - Uint8List output; - Uint8List signature; - Uint8List pubkey; - Uint8List input; - List witness; + String? address; + Uint8List? hash; + Uint8List? output; + Uint8List? signature; + Uint8List? pubkey; + Uint8List? input; + List? witness; PaymentData( {this.address, diff --git a/lib/src/payments/p2pk.dart b/lib/src/payments/p2pk.dart index 796d6dd..fcaaf73 100644 --- a/lib/src/payments/p2pk.dart +++ b/lib/src/payments/p2pk.dart @@ -1,28 +1,24 @@ -import 'dart:typed_data'; -import 'package:meta/meta.dart'; import 'package:bip32/src/utils/ecurve.dart' show isPoint; -import 'package:bs58check/bs58check.dart' as bs58check; -import '../crypto.dart'; import '../models/networks.dart'; import '../payments/index.dart' show PaymentData; -import '../utils/script.dart' as bscript; import '../utils/constants/op.dart'; class P2PK { PaymentData data; NetworkType network; - P2PK({@required data, network}) { - this.network = network ?? bitcoin; - this.data = data; + + P2PK({required this.data, NetworkType? network}) + : network = network ?? bitcoin { _init(); } _init() { - if (data.output != null) { - if (data.output[data.output.length - 1] != OPS['OP_CHECKSIG']) + final output = data.output; + if (output != null) { + if (output[output.length - 1] != OPS['OP_CHECKSIG']) throw new ArgumentError('Output is invalid'); - if (!isPoint(data.output.sublist(1, -1))) + if (!isPoint(output.sublist(1, -1))) throw new ArgumentError('Output pubkey is invalid'); } if (data.input != null) { diff --git a/lib/src/payments/p2pkh.dart b/lib/src/payments/p2pkh.dart index 60e5bfd..8c2b616 100644 --- a/lib/src/payments/p2pkh.dart +++ b/lib/src/payments/p2pkh.dart @@ -1,5 +1,4 @@ import 'dart:typed_data'; -import 'package:meta/meta.dart'; import 'package:bip32/src/utils/ecurve.dart' show isPoint; import 'package:bs58check/bs58check.dart' as bs58check; @@ -12,28 +11,29 @@ import '../utils/constants/op.dart'; class P2PKH { PaymentData data; NetworkType network; - P2PKH({@required data, network}) { - this.network = network ?? bitcoin; - this.data = data; + + P2PKH({required this.data, NetworkType? network}) + : network = network ?? bitcoin { _init(); } + _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 = hash160(data.pubkey); + data.hash = 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])) @@ -45,12 +45,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 = hash160(data.pubkey); + data.hash = hash160(data.pubkey!); _getDataFromHash(); } if (data.signature == null && _chunks != null) @@ -58,7 +58,7 @@ class P2PKH { ? 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]); + data.input = bscript.compile([data.signature!, data.pubkey!]); } } @@ -66,7 +66,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) { @@ -86,7 +86,7 @@ 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'); } } diff --git a/lib/src/payments/p2wpkh.dart b/lib/src/payments/p2wpkh.dart index 04cfbe2..5c7d24c 100644 --- a/lib/src/payments/p2wpkh.dart +++ b/lib/src/payments/p2wpkh.dart @@ -1,5 +1,4 @@ import 'dart:typed_data'; -import 'package:meta/meta.dart'; import 'package:bip32/src/utils/ecurve.dart' show isPoint; import 'package:bech32/bech32.dart'; @@ -14,9 +13,9 @@ class P2WPKH { PaymentData data; NetworkType network; - P2WPKH({@required data, network}) { - this.network = network ?? bitcoin; - this.data = data; + + P2WPKH({required this.data, NetworkType? network}) + : network = network ?? bitcoin { _init(); } @@ -28,51 +27,52 @@ class P2WPKH { data.witness == null) throw new ArgumentError('Not enough data'); if (data.address != null) { - _getDataFromAddress(data.address); + _getDataFromAddress(data.address!); } if (data.hash != null) { _getDataFromHash(); } - if (data.output != null) { - if (data.output.length != 22 || - data.output[0] != OPS['OP_0'] || - data.output[1] != 20) // 0x14 + final output = data.output; + if (output != null) { + if (output.length != 22 || + output[0] != OPS['OP_0'] || + output[1] != 20) // 0x14 throw new ArgumentError('Output is invalid'); if (data.hash == null) { - data.hash = data.output.sublist(2); + data.hash = output.sublist(2); } _getDataFromHash(); } if (data.pubkey != null) { - data.hash = hash160(data.pubkey); + data.hash = hash160(data.pubkey!); _getDataFromHash(); } - if (data.witness != null) { - if (data.witness.length != 2) - throw new ArgumentError('Witness is invalid'); - if (!bscript.isCanonicalScriptSignature(data.witness[0])) + final witness = data.witness; + if (witness != null) { + if (witness.length != 2) throw new ArgumentError('Witness is invalid'); + if (!bscript.isCanonicalScriptSignature(witness[0])) throw new ArgumentError('Witness has invalid signature'); - if (!isPoint(data.witness[1])) + if (!isPoint(witness[1])) throw new ArgumentError('Witness has invalid pubkey'); - _getDataFromWitness(data.witness); + _getDataFromWitness(witness); } else if (data.pubkey != null && data.signature != null) { - data.witness = [data.signature, data.pubkey]; + data.witness = [data.signature!, data.pubkey!]; if (data.input == null) data.input = EMPTY_SCRIPT; } } - void _getDataFromWitness([List witness]) { + void _getDataFromWitness(List witness) { if (data.input == null) { data.input = EMPTY_SCRIPT; } if (data.pubkey == null) { data.pubkey = witness[1]; if (data.hash == null) { - data.hash = hash160(data.pubkey); + data.hash = hash160(data.pubkey!); } _getDataFromHash(); } @@ -81,7 +81,7 @@ class P2WPKH { void _getDataFromHash() { if (data.address == null) { - data.address = segwit.encode(Segwit(network.bech32, 0, data.hash)); + data.address = segwit.encode(Segwit(network.bech32!, 0, data.hash!)); } if (data.output == null) { data.output = bscript.compile([OPS['OP_0'], data.hash]); diff --git a/lib/src/templates/pubkey.dart b/lib/src/templates/pubkey.dart index c839e85..2cd722e 100644 --- a/lib/src/templates/pubkey.dart +++ b/lib/src/templates/pubkey.dart @@ -1,6 +1,5 @@ import 'dart:typed_data'; import '../utils/script.dart' as bscript; -import '../utils/constants/op.dart'; bool inputCheck(List chunks) { return chunks.length == 1 && bscript.isCanonicalScriptSignature(chunks[0]); @@ -8,4 +7,5 @@ bool inputCheck(List chunks) { bool outputCheck(Uint8List script) { // TODO + throw UnimplementedError(); } diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 61f8756..b5a8b8f 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -19,23 +19,25 @@ const SIGHASH_ANYONECANPAY = 0x80; const ADVANCED_TRANSACTION_MARKER = 0x00; const ADVANCED_TRANSACTION_FLAG = 0x01; final EMPTY_SCRIPT = Uint8List.fromList([]); -final EMPTY_WITNESS = new List(); +final List EMPTY_WITNESS = []; final ZERO = HEX .decode('0000000000000000000000000000000000000000000000000000000000000000'); final ONE = HEX .decode('0000000000000000000000000000000000000000000000000000000000000001'); final VALUE_UINT64_MAX = HEX.decode('ffffffffffffffff'); -final BLANK_OUTPUT = - new Output(script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX); +final BLANK_OUTPUT = new Output( + script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX as Uint8List); class Transaction { int version = 1; int locktime = 0; List ins = []; List outs = []; + Transaction(); - int addInput(Uint8List hash, int index, [int sequence, Uint8List scriptSig]) { + int addInput(Uint8List hash, int index, + [int? sequence, Uint8List? scriptSig]) { ins.add(new Input( hash: hash, index: index, @@ -51,17 +53,16 @@ class Transaction { } bool hasWitnesses() { - var witness = ins.firstWhere( - (input) => input.witness != null && input.witness.length != 0, - orElse: () => null); - return witness != null; + var witness = ins.indexWhere( + (input) => input.witness != null && input.witness!.length != 0); + return witness != -1; } setInputScript(int index, Uint8List scriptSig) { ins[index].script = scriptSig; } - setWitness(int index, List witness) { + setWitness(int index, List? witness) { ins[index].witness = witness; } @@ -77,8 +78,8 @@ class Transaction { var hashSequence = ZERO; writeSlice(slice) { - tbuffer.setRange(toffset, toffset + slice.length, slice); - toffset += slice.length; + tbuffer.setRange(toffset, toffset + slice.length as int, slice); + toffset += slice.length as int; } writeUInt8(i) { @@ -144,8 +145,8 @@ class Transaction { if ((hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { - var txOutsSize = - outs.fold(0, (sum, output) => sum + 8 + varSliceSize(output.script)); + var txOutsSize = outs.fold( + 0, (int sum, output) => sum + 8 + varSliceSize(output.script!)); tbuffer = new Uint8List(txOutsSize); bytes = tbuffer.buffer.asByteData(); toffset = 0; @@ -157,7 +158,7 @@ class Transaction { } else if ((hashType & 0x1f) == SIGHASH_SINGLE && inIndex < outs.length) { // SIGHASH_SINGLE only hash that according output var output = outs[inIndex]; - tbuffer = new Uint8List(8 + varSliceSize(output.script)); + tbuffer = new Uint8List(8 + varSliceSize(output.script!)); bytes = tbuffer.buffer.asByteData(); toffset = 0; writeUInt64(output.value); @@ -188,7 +189,7 @@ class Transaction { if (inIndex >= ins.length) return ONE; // ignore OP_CODESEPARATOR final ourScript = - bscript.compile(bscript.decompile(prevOutScript).where((x) { + bscript.compile(bscript.decompile(prevOutScript)!.where((x) { return x != OPS['OP_CODESEPARATOR']; }).toList()); final txTmp = Transaction.clone(this); @@ -248,10 +249,10 @@ class Transaction { return (hasWitness ? 10 : 8) + varuint.encodingLength(ins.length) + varuint.encodingLength(outs.length) + - ins.fold(0, (sum, input) => sum + 40 + varSliceSize(input.script)) + - outs.fold(0, (sum, output) => sum + 8 + varSliceSize(output.script)) + + ins.fold(0, (sum, input) => sum + 40 + varSliceSize(input.script!)) + + outs.fold(0, (sum, output) => sum + 8 + varSliceSize(output.script!)) + (hasWitness - ? ins.fold(0, (sum, input) => sum + vectorSize(input.witness)) + ? ins.fold(0, (sum, input) => sum + vectorSize(input.witness!)) : 0); } @@ -275,7 +276,7 @@ class Transaction { return (weight() / 4).ceil(); } - Uint8List toBuffer([Uint8List buffer, int initialOffset]) { + Uint8List toBuffer([Uint8List? buffer, int? initialOffset]) { return this._toBuffer(buffer, initialOffset, true); } @@ -304,7 +305,7 @@ class Transaction { return HEX.encode(getHash().reversed.toList()); } - _toBuffer([Uint8List buffer, initialOffset, bool _ALLOW_WITNESS = false]) { + _toBuffer([Uint8List? buffer, initialOffset, bool _ALLOW_WITNESS = false]) { // _ALLOW_WITNESS is used to separate witness part when calculating tx id if (buffer == null) buffer = new Uint8List(_byteLength(_ALLOW_WITNESS)); @@ -314,7 +315,7 @@ class Transaction { var offset = initialOffset ?? 0; writeSlice(slice) { - buffer.setRange(offset, offset + slice.length, slice); + buffer!.setRange(offset, offset + slice.length, slice); offset += slice.length; } @@ -339,7 +340,7 @@ class Transaction { } writeVarInt(i) { - varuint.encode(i, buffer, offset); + varuint.encode(i, buffer!, offset); offset += varuint.encodingLength(i); } @@ -444,7 +445,7 @@ class Transaction { return i; } - Uint8List readSlice(n) { + Uint8List readSlice(int n) { offset += n; return buffer.sublist(offset - n, offset); } @@ -517,35 +518,37 @@ class Transaction { bool noStrict = false, }) { return Transaction.fromBuffer( - HEX.decode(hex), + HEX.decode(hex) as Uint8List, noStrict: noStrict, ); } @override String toString() { + var buf = StringBuffer(); this.ins.forEach((txInput) { - print(txInput.toString()); + buf.write(txInput.toString()); }); this.outs.forEach((txOutput) { - print(txOutput.toString()); + buf.write(txOutput.toString()); }); + return buf.toString(); } } class Input { - Uint8List hash; - int index; - int sequence; - int value; - Uint8List script; - Uint8List signScript; - Uint8List prevOutScript; - String prevOutType; - bool hasWitness; - List pubkeys; - List signatures; - List witness; + Uint8List? hash; + int? index; + int? sequence; + int? value; + Uint8List? script; + Uint8List? signScript; + Uint8List? prevOutScript; + String? prevOutType; + bool hasWitness = false; + List? pubkeys; + List? signatures; + List? witness; Input( {this.hash, @@ -558,19 +561,18 @@ class Input { this.signatures, this.witness, this.prevOutType}) { - this.hasWitness = false; // Default value - if (this.hash != null && !isHash256bit(this.hash)) + if (this.hash != null && !isHash256bit(this.hash!)) 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 && !isShatoshi(this.value)) + if (this.value != null && !isShatoshi(this.value!)) throw ArgumentError('Invalid ouput value'); } factory Input.expandInput(Uint8List scriptSig, List witness, - [String type, Uint8List scriptPubKey]) { + [String? type, Uint8List? scriptPubKey]) { if (type == null || type == '') { var ssType = classifyInput(scriptSig); var wsType = classifyWitness(witness); @@ -579,45 +581,50 @@ class Input { type = ssType ?? wsType; } if (type == SCRIPT_TYPES['P2WPKH']) { - P2WPKH p2wpkh = new P2WPKH(data: new PaymentData(witness: witness)); + P2WPKH p2wpkh = P2WPKH(data: new PaymentData(witness: witness)); return new Input( prevOutScript: p2wpkh.data.output, prevOutType: SCRIPT_TYPES['P2WPKH'], - pubkeys: [p2wpkh.data.pubkey], - signatures: [p2wpkh.data.signature]); + pubkeys: [p2wpkh.data.pubkey!], + signatures: [p2wpkh.data.signature!]); } else if (type == SCRIPT_TYPES['P2PKH']) { P2PKH p2pkh = new P2PKH(data: new PaymentData(input: scriptSig)); return new Input( prevOutScript: p2pkh.data.output, prevOutType: SCRIPT_TYPES['P2PKH'], - pubkeys: [p2pkh.data.pubkey], - signatures: [p2pkh.data.signature]); + pubkeys: [p2pkh.data.pubkey!], + signatures: [p2pkh.data.signature!]); } else if (type == SCRIPT_TYPES['P2PK']) { P2PK p2pk = new P2PK(data: new PaymentData(input: scriptSig)); return new Input( prevOutType: SCRIPT_TYPES['P2PK'], pubkeys: [], - signatures: [p2pk.data.signature]); + signatures: [p2pk.data.signature!]); } + throw UnsupportedError('type "$type"'); } 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) + .toList() : 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) + .toList() : null, ); } @@ -629,11 +636,11 @@ class Input { } 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, @@ -641,42 +648,45 @@ class Output { this.pubkeys, this.signatures, this.valueBuffer}) { - if (value != null && !isShatoshi(value)) + if (value != null && !isShatoshi(value!)) throw ArgumentError('Invalid ouput value'); } - factory Output.expandOutput(Uint8List script, [Uint8List ourPubKey]) { - if (ourPubKey == null) return new Output(); + factory Output.expandOutput(Uint8List script, [Uint8List? ourPubKey]) { + if (ourPubKey == null) return Output(); var type = classifyOutput(script); if (type == SCRIPT_TYPES['P2WPKH']) { - Uint8List wpkh1 = - new P2WPKH(data: new PaymentData(output: script)).data.hash; + Uint8List wpkh1 = P2WPKH(data: PaymentData(output: script)).data.hash!; Uint8List wpkh2 = bcrypto.hash160(ourPubKey); if (wpkh1 != wpkh2) throw ArgumentError('Hash mismatch!'); - return new Output(pubkeys: [ourPubKey], signatures: [null]); + return Output(pubkeys: [ourPubKey], signatures: [null]); } else if (type == SCRIPT_TYPES['P2PKH']) { - Uint8List pkh1 = - new P2PKH(data: new PaymentData(output: script)).data.hash; + Uint8List pkh1 = P2PKH(data: PaymentData(output: script)).data.hash!; Uint8List pkh2 = bcrypto.hash160(ourPubKey); if (pkh1 != pkh2) throw ArgumentError('Hash mismatch!'); - return new Output(pubkeys: [ourPubKey], signatures: [null]); + return Output(pubkeys: [ourPubKey], signatures: [null]); } + throw UnsupportedError('type "$type"'); } 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) + .toList() : 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) + .toList() : null, ); } diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart index 2ff61bf..b038097 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -1,8 +1,5 @@ import 'dart:typed_data'; -import 'package:meta/meta.dart'; import 'package:hex/hex.dart'; -import 'package:bs58check/bs58check.dart' as bs58check; -import 'package:bech32/bech32.dart'; import 'utils/script.dart' as bscript; import 'ecpair.dart'; import 'models/networks.dart'; @@ -16,22 +13,18 @@ import 'classify.dart'; class TransactionBuilder { NetworkType network; int maximumFeeRate; - List _inputs; - Transaction _tx; + List _inputs = []; + Transaction _tx = Transaction()..version = 2; Map _prevTxSet = {}; - TransactionBuilder({NetworkType network, int maximumFeeRate}) { - this.network = network ?? bitcoin; - this.maximumFeeRate = maximumFeeRate ?? 2500; - this._inputs = []; - this._tx = new Transaction(); - this._tx.version = 2; - } + TransactionBuilder({NetworkType? network, int? maximumFeeRate}) + : network = network ?? bitcoin, + maximumFeeRate = maximumFeeRate ?? 2500 {} List get inputs => _inputs; factory TransactionBuilder.fromTransaction(Transaction transaction, - [NetworkType network]) { + [NetworkType? network]) { final txb = new TransactionBuilder(network: network); // Copy transaction fields txb.setVersion(transaction.version); @@ -39,13 +32,13 @@ class TransactionBuilder { // Copy outputs (done first to avoid signature invalidation) transaction.outs.forEach((txOut) { - txb.addOutput(txOut.script, txOut.value); + txb.addOutput(txOut.script, txOut.value!); }); transaction.ins.forEach((txIn) { txb._addInputUnsafe( - txIn.hash, - txIn.index, + txIn.hash!, + txIn.index!, new Input( sequence: txIn.sequence, script: txIn.script, @@ -73,7 +66,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)) { @@ -98,7 +91,7 @@ class TransactionBuilder { } int addInput(dynamic txHash, int vout, - [int sequence, Uint8List prevOutScript]) { + [int? sequence, Uint8List? prevOutScript]) { if (!_canModifyInputs()) { throw new ArgumentError('No, this would invalidate signatures'); } @@ -124,12 +117,12 @@ class TransactionBuilder { } sign( - {@required int vin, - @required ECPair keyPair, - Uint8List redeemScript, - int witnessValue, - Uint8List witnessScript, - int hashType}) { + {required int vin, + required ECPair keyPair, + Uint8List? redeemScript, + int? witnessValue, + Uint8List? witnessScript, + int? hashType}) { if (keyPair.network != null && keyPair.network.toString().compareTo(network.toString()) != 0) throw new ArgumentError('Inconsistent network'); @@ -154,7 +147,7 @@ class TransactionBuilder { // TODO } if (input.prevOutScript != null && input.prevOutType != null) { - var type = classifyOutput(input.prevOutScript); + var type = classifyOutput(input.prevOutScript!); if (type == SCRIPT_TYPES['P2WPKH']) { input.prevOutType = SCRIPT_TYPES['P2WPKH']; input.hasWitness = true; @@ -167,14 +160,14 @@ class TransactionBuilder { .output; } else { // DRY CODE - Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); + Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey!); input.prevOutType = SCRIPT_TYPES['P2PKH']; input.signatures = [null]; input.pubkeys = [ourPubKey]; input.signScript = prevOutScript; } } else { - Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); + Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey!); input.prevOutType = SCRIPT_TYPES['P2PKH']; input.signatures = [null]; input.pubkeys = [ourPubKey]; @@ -185,21 +178,23 @@ class TransactionBuilder { if (input.hasWitness) { signatureHash = this ._tx - .hashForWitnessV0(vin, input.signScript, input.value, hashType); + .hashForWitnessV0(vin, input.signScript!, input.value!, hashType); } else { signatureHash = - this._tx.hashForSignature(vin, input.signScript, hashType); + this._tx.hashForSignature(vin, input.signScript!, hashType); } // 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) - continue; - if (input.signatures[i] != null) + for (var i = 0; i < input.pubkeys!.length; i++) { + if (HEX + .encode(ourPubKey!) + .compareTo(HEX.encode(input.pubkeys![i] as Uint8List)) != + 0) continue; + if (input.signatures![i] != null) throw new 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; } if (!signed) throw new ArgumentError('Key pair cannot sign for this input'); @@ -226,24 +221,24 @@ 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) { if (_inputs[i].prevOutType == SCRIPT_TYPES['P2PKH']) { P2PKH payment = new P2PKH( data: new PaymentData( - pubkey: _inputs[i].pubkeys[0], - signature: _inputs[i].signatures[0]), + pubkey: _inputs[i].pubkeys![0], + signature: _inputs[i].signatures![0]), network: network); - tx.setInputScript(i, payment.data.input); + tx.setInputScript(i, payment.data.input!); tx.setWitness(i, payment.data.witness); } else if (_inputs[i].prevOutType == SCRIPT_TYPES['P2WPKH']) { P2WPKH payment = new P2WPKH( data: new PaymentData( - pubkey: _inputs[i].pubkeys[0], - signature: _inputs[i].signatures[0]), + pubkey: _inputs[i].pubkeys![0], + signature: _inputs[i].signatures![0]), network: network); - tx.setInputScript(i, payment.data.input); - tx.setWitness(i, payment.data.witness); + tx.setInputScript(i, payment.data.input!); + tx.setWitness(i, payment.data.witness!); } } else if (!allowIncomplete) { throw new ArgumentError('Transaction is not complete'); @@ -271,7 +266,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; }); @@ -283,7 +278,7 @@ class TransactionBuilder { final nOutputs = _tx.outs.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; @@ -307,9 +302,9 @@ class TransactionBuilder { // .build() will fail, but .buildIncomplete() is OK return (this._tx.outs.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) @@ -323,8 +318,8 @@ 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) { @@ -338,21 +333,21 @@ class TransactionBuilder { throw new ArgumentError('Duplicate TxOut: ' + prevTxOut); if (options.script != null) { input = - Input.expandInput(options.script, options.witness ?? EMPTY_WITNESS); + Input.expandInput(options.script!, options.witness ?? EMPTY_WITNESS); } else { input = new Input(); } if (options.value != null) input.value = options.value; if (input.prevOutScript == null && options.prevOutScript != null) { if (input.pubkeys == null && input.signatures == null) { - var expanded = Output.expandOutput(options.prevOutScript); - if (expanded.pubkeys != null && !expanded.pubkeys.isEmpty) { + var expanded = Output.expandOutput(options.prevOutScript!); + if (expanded.pubkeys != null && !expanded.pubkeys!.isEmpty) { input.pubkeys = expanded.pubkeys; input.signatures = expanded.signatures; } } input.prevOutScript = options.prevOutScript; - input.prevOutType = classifyOutput(options.prevOutScript); + input.prevOutType = classifyOutput(options.prevOutScript!); } int vin = _tx.addInput(hash, vout, options.sequence, options.script); _inputs.add(input); @@ -369,9 +364,9 @@ class TransactionBuilder { Map get prevTxSet => _prevTxSet; } -Uint8List pubkeyToOutputScript(Uint8List pubkey, [NetworkType nw]) { +Uint8List pubkeyToOutputScript(Uint8List pubkey, [NetworkType? nw]) { NetworkType network = nw ?? bitcoin; P2PKH p2pkh = new P2PKH(data: new PaymentData(pubkey: pubkey), network: network); - return p2pkh.data.output; + return p2pkh.data.output!; } diff --git a/lib/src/utils/magic_hash.dart b/lib/src/utils/magic_hash.dart index f99dba4..2c3fd56 100644 --- a/lib/src/utils/magic_hash.dart +++ b/lib/src/utils/magic_hash.dart @@ -4,9 +4,9 @@ import '../../src/crypto.dart'; import 'varuint.dart'; import '../../src/models/networks.dart'; -Uint8List magicHash(String message, [NetworkType network]) { +Uint8List magicHash(String message, [NetworkType? network]) { network = network ?? bitcoin; - Uint8List messagePrefix = utf8.encode(network.messagePrefix); + Uint8List messagePrefix = utf8.encode(network.messagePrefix) as Uint8List; int messageVISize = encodingLength(message.length); int length = messagePrefix.length + messageVISize + message.length; Uint8List buffer = new Uint8List(length); diff --git a/lib/src/utils/push_data.dart b/lib/src/utils/push_data.dart index f608a94..d3dcf0c 100644 --- a/lib/src/utils/push_data.dart +++ b/lib/src/utils/push_data.dart @@ -2,15 +2,16 @@ import 'dart:typed_data'; import 'constants/op.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}); } @@ -23,30 +24,30 @@ EncodedPushData encode(Uint8List buffer, number, offset) { // 8 bit } else if (size == 2) { - buffer.buffer.asByteData().setUint8(offset, OPS['OP_PUSHDATA1']); + buffer.buffer.asByteData().setUint8(offset, OPS['OP_PUSHDATA1']!); buffer.buffer.asByteData().setUint8(offset + 1, number); // 16 bit } else if (size == 3) { - buffer.buffer.asByteData().setUint8(offset, OPS['OP_PUSHDATA2']); + buffer.buffer.asByteData().setUint8(offset, OPS['OP_PUSHDATA2']!); buffer.buffer.asByteData().setUint16(offset + 1, number, Endian.little); // 32 bit } else { - buffer.buffer.asByteData().setUint8(offset, OPS['OP_PUSHDATA4']); + buffer.buffer.asByteData().setUint8(offset, OPS['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; // ~6 bit - if (opcode < OPS['OP_PUSHDATA1']) { + if (opcode < OPS['OP_PUSHDATA1']!) { number = opcode; size = 1; @@ -76,5 +77,11 @@ DecodedPushData decode(Uint8List bf, int offset) { } int encodingLength(i) { - return i < OPS['OP_PUSHDATA1'] ? 1 : i <= 0xff ? 2 : i <= 0xffff ? 3 : 5; + return i < OPS['OP_PUSHDATA1'] + ? 1 + : i <= 0xff + ? 2 + : i <= 0xffff + ? 3 + : 5; } diff --git a/lib/src/utils/script.dart b/lib/src/utils/script.dart index ac6678b..9cc935c 100644 --- a/lib/src/utils/script.dart +++ b/lib/src/utils/script.dart @@ -11,12 +11,12 @@ final OP_INT_BASE = OPS['OP_RESERVED']; final ZERO = Uint8List.fromList([0]); Uint8List compile(List chunks) { - final bufferSize = chunks.fold(0, (acc, chunk) { + final dynamic bufferSize = chunks.fold(0, (int 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; + return acc + pushData.encodingLength(chunk.length) + chunk.length as int; }); var buffer = new Uint8List(bufferSize); @@ -33,8 +33,8 @@ Uint8List compile(List chunks) { } pushData.EncodedPushData epd = pushData.encode(buffer, chunk.length, offset); - offset += epd.size; - buffer = epd.buffer; + offset += epd.size!; + buffer = epd.buffer!; buffer.setRange(offset, offset + chunk.length, chunk); offset += chunk.length; // opcode @@ -49,7 +49,7 @@ Uint8List compile(List chunks) { return buffer; } -List decompile(dynamic buffer) { +List? decompile(dynamic buffer) { List chunks = []; if (buffer == null) return chunks; @@ -65,13 +65,13 @@ List decompile(dynamic 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); @@ -101,7 +101,7 @@ Uint8List fromASM(String asm) { String toASM(List c) { List chunks; if (c is Uint8List) { - chunks = decompile(c); + chunks = decompile(c)!; } else { chunks = c; } @@ -117,10 +117,10 @@ String toASM(List c) { }).join(' '); } -int asMinimalOP(Uint8List buffer) { +int? asMinimalOP(Uint8List buffer) { if (buffer.length == 0) return OPS['OP_0']; if (buffer.length != 1) return null; - if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; + if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE! + buffer[0]; if (buffer[0] == 0x81) return OPS['OP_1NEGATE']; return null; } @@ -179,7 +179,7 @@ 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 as int) + (lenS as int)); // 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] signature[0] = 0x30; diff --git a/lib/src/utils/varuint.dart b/lib/src/utils/varuint.dart index 04aff4c..62d1316 100644 --- a/lib/src/utils/varuint.dart +++ b/lib/src/utils/varuint.dart @@ -1,7 +1,7 @@ import 'check_types.dart'; import 'dart:typed_data'; -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); @@ -59,7 +59,11 @@ int encodingLength(int number) { if (!isUint(number, 53)) throw ArgumentError("Expected UInt53"); return (number < 0xfd ? 1 - : number <= 0xffff ? 3 : number <= 0xffffffff ? 5 : 9); + : number <= 0xffff + ? 3 + : number <= 0xffffffff + ? 5 + : 9); } int readUInt64LE(ByteData bytes, int offset) { diff --git a/pubspec.yaml b/pubspec.yaml index 062977e..2dda81f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,16 +5,30 @@ homepage: https://github.com/anicdh author: andoan environment: - sdk: ">=2.3.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" dependencies: bip39: ^1.0.3 bip32: ^1.0.5 pointycastle: ^1.0.2 - hex: ^0.1.2 + hex: ^0.2.0 bs58check: ^1.0.1 meta: ^1.2.3 - bech32: 0.1.2 + bech32: ^0.2.1 dev_dependencies: test: ^1.15.4 + +dependency_overrides: + bip32: + git: + url: git://github.com/cochainio/bip32-dart + ref: master + bip39: + git: + url: git://github.com/cochainio/bip39 + ref: master + pointycastle: + git: + url: git://github.com/cochainio/pc-dart + ref: master diff --git a/test/ecpair_test.dart b/test/ecpair_test.dart index ec70e54..9ad00f2 100644 --- a/test/ecpair_test.dart +++ b/test/ecpair_test.dart @@ -6,8 +6,9 @@ import 'dart:convert'; import '../lib/src/ecpair.dart' show ECPair; import '../lib/src/models/networks.dart' as NETWORKS; -final ONE = HEX - .decode('0000000000000000000000000000000000000000000000000000000000000001'); +final ONE = HEX.decode( + '0000000000000000000000000000000000000000000000000000000000000001') + as Uint8List; main() { final fixtures = json.decode( @@ -29,14 +30,14 @@ main() { }); (fixtures['valid'] as List).forEach((f) { test('derives public key for ${f['WIF']}', () { - final d = HEX.decode(f['d']); + final d = HEX.decode(f['d']) as Uint8List; final keyPair = ECPair.fromPrivateKey(d, compressed: f['compressed']); - expect(HEX.encode(keyPair.publicKey), f['Q']); + expect(HEX.encode(keyPair.publicKey!), f['Q']); }); }); (fixtures['invalid']['fromPrivateKey'] as List).forEach((f) { test('throws ' + f['exception'], () { - final d = HEX.decode(f['d']); + final d = HEX.decode(f['d']) as Uint8List; try { expect(ECPair.fromPrivateKey(d), isArgumentError); } catch (err) { @@ -48,7 +49,7 @@ main() { group('fromPublicKey', () { (fixtures['invalid']['fromPublicKey'] as List).forEach((f) { test('throws ' + f['exception'], () { - final Q = HEX.decode(f['Q']); + final Q = HEX.decode(f['Q']) as Uint8List; try { expect(ECPair.fromPublicKey(Q), isArgumentError); } catch (err) { @@ -62,7 +63,7 @@ main() { test('imports ${f['WIF']}', () { final keyPair = ECPair.fromWIF(f['WIF']); var network = _getNetwork(f); - expect(HEX.encode(keyPair.privateKey), f['d']); + expect(HEX.encode(keyPair.privateKey!), f['d']); expect(keyPair.compressed, f['compressed']); expect(keyPair.network, network); }); @@ -121,7 +122,7 @@ main() { group('.network', () { (fixtures['valid'] as List).forEach((f) { test('return ${f['network']} for ${f['WIF']}', () { - NETWORKS.NetworkType network = _getNetwork(f); + var network = _getNetwork(f); final keyPair = ECPair.fromWIF(f['WIF']); expect(keyPair.network, network); }); @@ -130,14 +131,13 @@ main() { }); } -NETWORKS.NetworkType _getNetwork(f) { - var network; +NETWORKS.NetworkType? _getNetwork(f) { if (f['network'] != null) { if (f['network'] == 'bitcoin') { - network = NETWORKS.bitcoin; + return NETWORKS.bitcoin; } else if (f['network'] == 'testnet') { - network = NETWORKS.testnet; + return NETWORKS.testnet; } } - return network; + return null; } diff --git a/test/integration/addresses_test.dart b/test/integration/addresses_test.dart index ebe72db..ed9ff2c 100644 --- a/test/integration/addresses_test.dart +++ b/test/integration/addresses_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import '../../lib/src/models/networks.dart' as NETWORKS; import '../../lib/src/ecpair.dart' show ECPair; import '../../lib/src/payments/index.dart' show PaymentData; @@ -30,7 +32,7 @@ main() { }); test('can generate an address from a SHA256 hash', () { final hash = new SHA256Digest() - .process(utf8.encode('correct horse battery staple')); + .process(utf8.encode('correct horse battery staple') as Uint8List); final keyPair = ECPair.fromPrivateKey(hash); final address = new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)) diff --git a/test/integration/bip32_test.dart b/test/integration/bip32_test.dart index 22119f0..44e39f4 100644 --- a/test/integration/bip32_test.dart +++ b/test/integration/bip32_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:bitcoin_flutter/src/models/networks.dart'; import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:bitcoin_flutter/src/payments/p2pkh.dart'; @@ -43,7 +45,8 @@ void main() { test('can create a BIP32, bitcoin, account 0, external address', () { const path = "m/0'/0/0"; final root = bip32.BIP32.fromSeed(HEX.decode( - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd')); + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd') + as Uint8List); final child1 = root.derivePath(path); // option 2, manually final child1b = root.deriveHardened(0).derive(0).derive(0); @@ -52,7 +55,8 @@ void main() { }); test('can create a BIP44, bitcoin, account 0, external address', () { final root = bip32.BIP32.fromSeed(HEX.decode( - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd')); + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd') + as Uint8List); final child1 = root.derivePath("m/44'/0'/0'/0/0"); // option 2, manually final child1b = root @@ -88,7 +92,7 @@ void main() { } String getAddress(node, [network]) { - return P2PKH(data: new PaymentData(pubkey: node.publicKey), network: network) + return P2PKH(data: PaymentData(pubkey: node.publicKey), network: network) .data - .address; + .address!; } diff --git a/test/payments/p2pkh_test.dart b/test/payments/p2pkh_test.dart index 63721e7..b496112 100644 --- a/test/payments/p2pkh_test.dart +++ b/test/payments/p2pkh_test.dart @@ -8,7 +8,8 @@ import 'package:hex/hex.dart'; import 'dart:typed_data'; main() { - final fixtures = json.decode(new File("./test/fixtures/p2pkh.json").readAsStringSync(encoding: utf8)); + final fixtures = json.decode( + new File("./test/fixtures/p2pkh.json").readAsStringSync(encoding: utf8)); group('(valid case)', () { (fixtures["valid"] as List).forEach((f) { test(f['description'] + ' as expected', () { @@ -37,14 +38,17 @@ main() { }); group('(invalid case)', () { (fixtures["invalid"] as List).forEach((f) { - test('throws ' + f['exception'] + (f['description'] != null ? ('for ' + f['description']) : ''), () { + test( + 'throws ' + + f['exception'] + + (f['description'] != null ? ('for ' + f['description']) : ''), + () { final arguments = _preformPaymentData(f['arguments']); try { expect(new P2PKH(data: arguments), isArgumentError); - } catch(err) { + } catch (err) { expect((err as ArgumentError).message, f['exception']); } - }); }); }); @@ -54,13 +58,23 @@ PaymentData _preformPaymentData(dynamic x) { final address = x['address']; final hash = x['hash'] != null ? HEX.decode(x['hash']) : null; final input = x['input'] != null ? bscript.fromASM(x['input']) : null; - final output = x['output'] != null ? bscript.fromASM(x['output']) : x['outputHex'] != null ? HEX.decode(x['outputHex']) : null; + final output = x['output'] != null + ? bscript.fromASM(x['output']) + : x['outputHex'] != null + ? HEX.decode(x['outputHex']) + : null; final pubkey = x['pubkey'] != null ? HEX.decode(x['pubkey']) : null; final signature = x['signature'] != null ? HEX.decode(x['signature']) : null; - return new PaymentData(address: address, hash: hash, input: input, output: output, pubkey: pubkey, signature: signature); + return new PaymentData( + address: address, + hash: hash as Uint8List?, + input: input, + output: output as Uint8List?, + pubkey: pubkey as Uint8List?, + signature: signature as Uint8List?); } -String _toString(dynamic x) { +String? _toString(dynamic x) { if (x == null) { return null; } diff --git a/test/payments/p2wpkh_test.dart b/test/payments/p2wpkh_test.dart index 4adb17a..8f8c84a 100644 --- a/test/payments/p2wpkh_test.dart +++ b/test/payments/p2wpkh_test.dart @@ -8,8 +8,8 @@ import 'package:hex/hex.dart'; import 'dart:typed_data'; main() { - - final fixtures = json.decode(new File("./test/fixtures/p2wpkh.json").readAsStringSync(encoding: utf8)); + final fixtures = json.decode( + new File("./test/fixtures/p2wpkh.json").readAsStringSync(encoding: utf8)); group('(valid case)', () { (fixtures["valid"] as List).forEach((f) { @@ -43,31 +43,49 @@ main() { group('(invalid case)', () { (fixtures["invalid"] as List).forEach((f) { - test('throws ' + f['exception'] + (f['description'] != null ? ('for ' + f['description']) : ''), () { + test( + 'throws ' + + f['exception'] + + (f['description'] != null ? ('for ' + f['description']) : ''), + () { final arguments = _preformPaymentData(f['arguments']); try { expect(new P2WPKH(data: arguments), isArgumentError); - } catch(err) { + } catch (err) { expect((err as ArgumentError).message, f['exception']); } - }); }); }); } PaymentData _preformPaymentData(dynamic x) { - final address = x['address']; - final hash = x['hash'] != null ? HEX.decode(x['hash']) : null; - final input = x['input'] != null ? bscript.fromASM(x['input']) : null; - final witness = x['witness'] != null ? (x['witness'] as List).map((e) => HEX.decode(e.toString()) as Uint8List).toList() : null; - final output = x['output'] != null ? bscript.fromASM(x['output']) : x['outputHex'] != null ? HEX.decode(x['outputHex']) : null; - final pubkey = x['pubkey'] != null ? HEX.decode(x['pubkey']) : null; + final address = x['address']; + final hash = x['hash'] != null ? HEX.decode(x['hash']) : null; + final input = x['input'] != null ? bscript.fromASM(x['input']) : null; + final witness = x['witness'] != null + ? (x['witness'] as List) + .map((e) => HEX.decode(e.toString()) as Uint8List) + .toList() + : null; + final output = x['output'] != null + ? bscript.fromASM(x['output']) + : x['outputHex'] != null + ? HEX.decode(x['outputHex']) + : null; + final pubkey = x['pubkey'] != null ? HEX.decode(x['pubkey']) : null; final signature = x['signature'] != null ? HEX.decode(x['signature']) : null; - return new PaymentData(address: address, hash: hash, input: input, output: output, pubkey: pubkey, signature: signature, witness: witness); + return new PaymentData( + address: address, + hash: hash as Uint8List?, + input: input, + output: output as Uint8List?, + pubkey: pubkey as Uint8List?, + signature: signature as Uint8List?, + witness: witness); } -String _toString(dynamic x) { +String? _toString(dynamic x) { if (x == null) { return null; } diff --git a/test/transaction_builder_test.dart b/test/transaction_builder_test.dart index 99c1e53..4582179 100644 --- a/test/transaction_builder_test.dart +++ b/test/transaction_builder_test.dart @@ -31,7 +31,7 @@ constructSign(f, TransactionBuilder txb) { return txb; } -TransactionBuilder construct(f, [bool dontSign]) { +TransactionBuilder construct(f, [bool? dontSign]) { final network = NETWORKS[f['network']]; final txb = new TransactionBuilder(network: network); if (f['version'] != null) txb.setVersion(f['version']); @@ -71,7 +71,8 @@ main() { .readAsStringSync(encoding: utf8)); group('TransactionBuilder', () { final keyPair = ECPair.fromPrivateKey(HEX.decode( - '0000000000000000000000000000000000000000000000000000000000000001')); + '0000000000000000000000000000000000000000000000000000000000000001') + as Uint8List); final scripts = [ '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', '1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP' @@ -109,11 +110,11 @@ main() { final txAfter = f['incomplete'] ? txb.buildIncomplete() : txb.build(); for (var i = 0; i < txAfter.ins.length; i++) { - test(bscript.toASM(txAfter.ins[i].script), + test(bscript.toASM(txAfter.ins[i].script!), f['inputs'][i]['scriptSigAfter']); } for (var i = 0; i < txAfter.outs.length; i++) { - test(bscript.toASM(txAfter.outs[i].script), + test(bscript.toASM(txAfter.outs[i].script!), f['outputs'][i]['script']); } }); @@ -131,7 +132,7 @@ main() { }); }); group('addInput', () { - TransactionBuilder txb; + late TransactionBuilder txb; setUp(() { txb = new TransactionBuilder(); }); @@ -186,7 +187,7 @@ main() { }); }); group('addOutput', () { - TransactionBuilder txb; + late TransactionBuilder txb; setUp(() { txb = new TransactionBuilder(); }); diff --git a/test/transaction_test.dart b/test/transaction_test.dart index f817c57..be7d6a5 100644 --- a/test/transaction_test.dart +++ b/test/transaction_test.dart @@ -72,13 +72,13 @@ main() { test('defaults to empty script, and 0xffffffff SEQUENCE number', () { final tx = new Transaction(); tx.addInput(prevTxHash, 0); - expect(tx.ins[0].script.length, 0); + expect(tx.ins[0].script!.length, 0); expect(tx.ins[0].sequence, 0xffffffff); }); (fixtures['invalid']['addInput'] as List).forEach((f) { test('throws on ' + f['exception'], () { final tx = new Transaction(); - final hash = HEX.decode(f['hash']); + final hash = HEX.decode(f['hash']) as Uint8List; try { expect(tx.addInput(hash, f['index']), isArgumentError); } catch (err) { @@ -152,7 +152,7 @@ Transaction fromRaw(raw, [isWitness]) { tx.locktime = raw['locktime']; (raw['ins'] as List).asMap().forEach((indx, txIn) { - final txHash = HEX.decode(txIn['hash']); + final txHash = HEX.decode(txIn['hash']) as Uint8List; var scriptSig; if (txIn['data'] != null) {