diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7c60ee4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "bitcoin_flutter", + "request": "launch", + "type": "dart", + "program": "example/bitcoin_flutter_example.dart", + } + ] +} \ No newline at end of file diff --git a/lib/bitcoin_flutter.dart b/lib/bitcoin_flutter.dart index a77de46..005543c 100644 --- a/lib/bitcoin_flutter.dart +++ b/lib/bitcoin_flutter.dart @@ -1,13 +1,12 @@ /// Support for doing something awesome. /// /// More dartdocs go here. +// @dart=2.12 library bitcoin_flutter; export 'src/bitcoin_flutter_base.dart'; export 'src/models/networks.dart'; -export 'src/transaction.dart'; export 'src/address.dart'; -export 'src/transaction_builder.dart'; export 'src/ecpair.dart'; export 'src/payments/p2pkh.dart'; export 'src/payments/p2wpkh.dart'; diff --git a/lib/src/address.dart b/lib/src/address.dart index c310f12..2736d88 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; @@ -24,25 +24,21 @@ class Address { decodeBase58 = bs58check.decode(address); } catch (err) {} if (decodeBase58 != null) { - if (decodeBase58[0] != network.pubKeyHash) - throw new ArgumentError('Invalid version or Network mismatch'); - P2PKH p2pkh = - new P2PKH(data: new PaymentData(address: address), network: network); - return p2pkh.data.output; + if (decodeBase58[0] != network.pubKeyHash) throw ArgumentError('Invalid version or Network mismatch'); + P2PKH p2pkh = P2PKH(data: PaymentData(address: address), network: network); + return p2pkh.data.output!; } else { try { decodeBech32 = segwit.decode(address); } catch (err) {} if (decodeBech32 != null) { - if (network.bech32 != decodeBech32.hrp) - throw new ArgumentError('Invalid prefix or Network mismatch'); - if (decodeBech32.version != 0) - throw new ArgumentError('Invalid address version'); - P2WPKH p2wpkh = new P2WPKH( - data: new PaymentData(address: address), network: network); - return p2wpkh.data.output; + if (network.bech32 != decodeBech32.hrp) throw ArgumentError('Invalid prefix or Network mismatch'); + if (decodeBech32.version != 0) throw ArgumentError('Invalid address version'); + P2WPKH p2wpkh = P2WPKH(data: PaymentData(address: address), network: network); + if (p2wpkh.data.output == null) throw ArgumentError('p2wpkh.data.output is null'); + return p2wpkh.data.output!; } } - throw new ArgumentError(address + ' has no matching Script'); + throw ArgumentError(address + ' has no matching Script'); } } diff --git a/lib/src/bitcoin_flutter_base.dart b/lib/src/bitcoin_flutter_base.dart index 6de8167..8d9cca4 100644 --- a/lib/src/bitcoin_flutter_base.dart +++ b/lib/src/bitcoin_flutter_base.dart @@ -1,143 +1,127 @@ // TODO: Put public facing types in this file. import 'dart:typed_data'; + +import 'package:bip32/bip32.dart' as bip32; import 'package:bitcoin_flutter/src/utils/magic_hash.dart'; import 'package:hex/hex.dart'; -import 'package:bip32/bip32.dart' as bip32; + +import 'ecpair.dart'; 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 { - if (_bip32 == null) return null; + String? get privKey { + if (_bip32?.privateKey == 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?.publicKey != 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 p2pkh = new P2PKH( - data: new PaymentData(pubkey: bip32.publicKey), network: network); + final bip32 = _bip32!.derivePath(path); + final p2pkh = P2PKH(data: PaymentData(pubkey: bip32.publicKey), network: network); return HDWallet(bip32: bip32, p2pkh: p2pkh, network: network); } - HDWallet derive(int index) { - final bip32 = _bip32.derive(index); - final p2pkh = new P2PKH( - data: new PaymentData(pubkey: bip32.publicKey), network: network); + HDWallet? derive(int index) { + if (_bip32 == null) return null; + final bip32 = _bip32!.derive(index); + final p2pkh = P2PKH(data: 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( - seed, - bip32.NetworkType( - bip32: bip32.Bip32Type( - public: network.bip32.public, private: network.bip32.private), - wif: network.wif)); - final p2pkh = new P2PKH( - data: new PaymentData(pubkey: wallet.publicKey), network: network); - return HDWallet( - bip32: wallet, p2pkh: p2pkh, network: network, seed: seedHex); + seed, bip32.NetworkType(bip32: bip32.Bip32Type(public: network.bip32.public, private: network.bip32.private), wif: network.wif)); + final p2pkh = P2PKH(data: PaymentData(pubkey: wallet.publicKey), network: network); + return 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, - bip32.NetworkType( - bip32: bip32.Bip32Type( - public: network.bip32.public, private: network.bip32.private), - wif: network.wif)); - final p2pkh = new P2PKH( - data: new PaymentData(pubkey: wallet.publicKey), network: network); + xpub, bip32.NetworkType(bip32: bip32.Bip32Type(public: network.bip32.public, private: network.bip32.private), wif: network.wif)); + final p2pkh = P2PKH(data: PaymentData(pubkey: wallet.publicKey), network: network); return HDWallet(bip32: wallet, p2pkh: p2pkh, network: network, seed: null); } 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; + P2PKH? _p2pkh; - String get privKey => - _keyPair != null ? HEX.encode(_keyPair.privateKey) : null; + String? get privKey => _keyPair.privateKey != null ? HEX.encode(_keyPair.privateKey!) : null; - String get pubKey => _keyPair != null ? HEX.encode(_keyPair.publicKey) : null; + String? get pubKey => HEX.encode(_keyPair.publicKey); - String get wif => _keyPair != null ? _keyPair.toWIF() : null; + String? get wif => _keyPair.toWIF(); - String get address => _p2pkh != null ? _p2pkh.data.address : null; + String? get address => _p2pkh != null ? _p2pkh!.data.address : null; 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); + final _p2pkh = P2PKH(data: 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( - data: new PaymentData(pubkey: _keyPair.publicKey), network: network); + final _p2pkh = P2PKH(data: PaymentData(pubkey: _keyPair.publicKey), network: network); return Wallet(_keyPair, _p2pkh, network); } @@ -146,7 +130,7 @@ class Wallet { 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); } diff --git a/lib/src/classify.dart b/lib/src/classify.dart index 9af1b34..754457d 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); @@ -27,14 +27,14 @@ String classifyOutput(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']; - if (pubkey.inputCheck(chunks)) return SCRIPT_TYPES['P2PK']; - return SCRIPT_TYPES['NONSTANDARD']; + if (pubkeyhash.inputCheck(chunks)) return SCRIPT_TYPES['P2PKH']!; + if (pubkey.inputCheck(chunks)) return SCRIPT_TYPES['P2PK']!; + return SCRIPT_TYPES['NONSTANDARD']!; } 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']; - return SCRIPT_TYPES['NONSTANDARD']; + if (witnessPubKeyHash.inputCheck(chunks)) return SCRIPT_TYPES['P2WPKH']!; + return SCRIPT_TYPES['NONSTANDARD']!; } diff --git a/lib/src/ecpair.dart b/lib/src/ecpair.dart index fc8ce89..d1e0ebf 100644 --- a/lib/src/ecpair.dart +++ b/lib/src/ecpair.dart @@ -5,39 +5,38 @@ import 'package:bip32/src/utils/wif.dart' as wif; import 'models/networks.dart'; class ECPair { - Uint8List _d; - Uint8List _Q; - NetworkType network; - bool compressed; - ECPair(Uint8List _d, Uint8List _Q, {network, compressed}) { + late Uint8List? _d; + late Uint8List? _Q; + late NetworkType network; + late 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); - return _Q; + 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)); + return wif.encode(new wif.WIF(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); } - 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 @@ -54,29 +53,20 @@ class ECPair { throw new ArgumentError('Unknown network version'); } } - return ECPair.fromPrivateKey(decoded.privateKey, - compressed: decoded.compressed, network: nw); + return ECPair.fromPrivateKey(decoded.privateKey, compressed: decoded.compressed, network: nw); } - factory ECPair.fromPublicKey(Uint8List publicKey, - {NetworkType network, bool compressed}) { + factory ECPair.fromPublicKey(Uint8List publicKey, {NetworkType? network, bool? compressed}) { if (!ecc.isPoint(publicKey)) { - throw new ArgumentError('Point is not on the curve'); + throw ArgumentError('Point is not on the curve'); } - return new ECPair(null, publicKey, - network: network, compressed: compressed); + return ECPair(null, publicKey, network: network, compressed: compressed); } - factory ECPair.fromPrivateKey(Uint8List privateKey, - {NetworkType network, bool compressed}) { - if (privateKey.length != 32) - throw new ArgumentError( - 'Expected property privateKey of type Buffer(Length: 32)'); - if (!ecc.isPrivate(privateKey)) - throw new ArgumentError('Private key not in range [1, n)'); - return new ECPair(privateKey, null, - network: network, compressed: compressed); + factory ECPair.fromPrivateKey(Uint8List privateKey, {NetworkType? network, bool? compressed}) { + if (privateKey.length != 32) throw new ArgumentError('Expected property privateKey of type Buffer(Length: 32)'); + if (!ecc.isPrivate(privateKey)) throw new ArgumentError('Private key not in range [1, n)'); + return new ECPair(privateKey, null, network: network, compressed: compressed); } - factory ECPair.makeRandom( - {NetworkType network, bool compressed, Function rng}) { + factory ECPair.makeRandom({NetworkType? network, bool? compressed, Function? rng}) { final rfunc = rng ?? _randomBytes; Uint8List d; // int beginTime = DateTime.now().millisecondsSinceEpoch; diff --git a/lib/src/models/networks.dart b/lib/src/models/networks.dart index dc4771d..6f5c335 100644 --- a/lib/src/models/networks.dart +++ b/lib/src/models/networks.dart @@ -1,20 +1,19 @@ -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 +25,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..1770d8b 100644 --- a/lib/src/payments/index.dart +++ b/lib/src/payments/index.dart @@ -1,22 +1,23 @@ 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, - this.hash, - this.output, - this.pubkey, - this.input, - this.signature, - this.witness}); + PaymentData({ + this.address, + this.hash, + this.output, + this.pubkey, + this.input, + this.signature, + this.witness, + }); @override String toString() { diff --git a/lib/src/payments/p2pk.dart b/lib/src/payments/p2pk.dart index 796d6dd..f1eacfc 100644 --- a/lib/src/payments/p2pk.dart +++ b/lib/src/payments/p2pk.dart @@ -1,28 +1,23 @@ -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}) { + final PaymentData data; + late NetworkType network; + P2PK({required this.data, NetworkType? network}) { this.network = network ?? bitcoin; - this.data = data; _init(); } _init() { if (data.output != null) { - if (data.output[data.output.length - 1] != OPS['OP_CHECKSIG']) + if (data.output![data.output!.length - 1] != OPS['OP_CHECKSIG']) throw new ArgumentError('Output is invalid'); - if (!isPoint(data.output.sublist(1, -1))) + if (!isPoint(data.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..cab00db 100644 --- a/lib/src/payments/p2pkh.dart +++ b/lib/src/payments/p2pkh.dart @@ -1,92 +1,76 @@ 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'; +import '../utils/script.dart' as bscript; class P2PKH { PaymentData data; - NetworkType network; - P2PKH({@required data, network}) { + NetworkType? network; + P2PKH({required this.data, network}) { this.network = network ?? bitcoin; - this.data = data; _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)) - throw new ArgumentError('Output is invalid'); - data.hash = data.output.sublist(3, 23); + if (!isValidOutput(data.output!)) throw new ArgumentError('Output is invalid'); + 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])) - throw new ArgumentError('Input has invalid signature'); - if (!isPoint(_chunks[1])) - throw new ArgumentError('Input has invalid pubkey'); + 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]) { + 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) ? Uint8List.fromList([_chunks[1]]) : _chunks[1]; + data.hash = hash160(data.pubkey!); _getDataFromHash(); } - if (data.signature == null && _chunks != null) - data.signature = (_chunks[0] is int) - ? new Uint8List.fromList([_chunks[0]]) - : _chunks[0]; + if (data.signature == null && _chunks != null) 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]); } } void _getDataFromHash() { - if (data.address == null) { + if (data.address == null && network?.pubKeyHash != null && data.hash != null) { final payload = new Uint8List(21); - payload.buffer.asByteData().setUint8(0, network.pubKeyHash); - payload.setRange(1, payload.length, data.hash); + payload.buffer.asByteData().setUint8(0, network!.pubKeyHash); + payload.setRange(1, payload.length, data.hash!); data.address = bs58check.encode(payload); } if (data.output == null) { - data.output = bscript.compile([ - OPS['OP_DUP'], - OPS['OP_HASH160'], - data.hash, - OPS['OP_EQUALVERIFY'], - OPS['OP_CHECKSIG'] - ]); + data.output = bscript.compile([OPS['OP_DUP'], OPS['OP_HASH160'], data.hash, OPS['OP_EQUALVERIFY'], OPS['OP_CHECKSIG']]); } } void _getDataFromAddress(String address) { Uint8List payload = bs58check.decode(address); final version = payload.buffer.asByteData().getUint8(0); - if (version != network.pubKeyHash) - throw new ArgumentError('Invalid version or Network mismatch'); + 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'); } } @@ -100,16 +84,8 @@ isValidOutput(Uint8List data) { } // Backward compatibility -@Deprecated( - "The 'P2PKHData' class is deprecated. Use the 'PaymentData' package instead.") +@Deprecated("The 'P2PKHData' class is deprecated. Use the 'PaymentData' package instead.") class P2PKHData extends PaymentData { P2PKHData({address, hash, output, pubkey, input, signature, witness}) - : super( - address: address, - hash: hash, - output: output, - pubkey: pubkey, - input: input, - signature: signature, - witness: witness); + : super(address: address, hash: hash, output: output, pubkey: pubkey, input: input, signature: signature, witness: witness); } diff --git a/lib/src/payments/p2wpkh.dart b/lib/src/payments/p2wpkh.dart index 04cfbe2..d3e6a81 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'; @@ -12,23 +11,19 @@ import '../utils/constants/op.dart'; class P2WPKH { final EMPTY_SCRIPT = Uint8List.fromList([]); - PaymentData data; - NetworkType network; - P2WPKH({@required data, network}) { + final PaymentData data; + late NetworkType network; + P2WPKH({required this.data, NetworkType? network}) { this.network = network ?? bitcoin; - this.data = data; _init(); } _init() { - if (data.address == null && - data.hash == null && - data.output == null && - data.pubkey == null && - data.witness == null) throw new ArgumentError('Not enough data'); + if (data.address == null && data.hash == null && data.output == null && data.pubkey == null && data.witness == null) + throw ArgumentError('Not enough data'); if (data.address != null) { - _getDataFromAddress(data.address); + _getDataFromAddress(data.address!); } if (data.hash != null) { @@ -36,43 +31,38 @@ class P2WPKH { } if (data.output != null) { - if (data.output.length != 22 || - data.output[0] != OPS['OP_0'] || - data.output[1] != 20) // 0x14 - throw new ArgumentError('Output is invalid'); + if (data.output!.length != 22 || data.output![0] != OPS['OP_0'] || data.output![1] != 20) // 0x14 + throw ArgumentError('Output is invalid'); if (data.hash == null) { - data.hash = data.output.sublist(2); + data.hash = data.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])) - throw new ArgumentError('Witness has invalid signature'); - if (!isPoint(data.witness[1])) - throw new ArgumentError('Witness has invalid pubkey'); - _getDataFromWitness(data.witness); + if (data.witness!.length != 2) throw ArgumentError('Witness is invalid'); + if (!bscript.isCanonicalScriptSignature(data.witness![0])) throw ArgumentError('Witness has invalid signature'); + if (!isPoint(data.witness![1])) throw ArgumentError('Witness has invalid pubkey'); + _getDataFromWitness(data.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(); } @@ -80,8 +70,8 @@ class P2WPKH { } void _getDataFromHash() { - if (data.address == null) { - data.address = segwit.encode(Segwit(network.bech32, 0, data.hash)); + if (data.address == null && network.bech32 != null && data.hash != null) { + data.address = segwit.encode(Segwit(network.bech32!, 0, data.hash!)); } if (data.output == null) { data.output = bscript.compile([OPS['OP_0'], data.hash]); @@ -91,17 +81,16 @@ class P2WPKH { void _getDataFromAddress(String address) { try { Segwit _address = segwit.decode(address); - if (network.bech32 != _address.hrp) - throw new ArgumentError('Invalid prefix or Network mismatch'); + if (network.bech32 != _address.hrp) throw ArgumentError('Invalid prefix or Network mismatch'); if (_address.version != 0) // Only support version 0 now; - throw new ArgumentError('Invalid address version'); + throw ArgumentError('Invalid address version'); data.hash = Uint8List.fromList(_address.program); } on InvalidHrp { - throw new ArgumentError('Invalid prefix or Network mismatch'); + throw ArgumentError('Invalid prefix or Network mismatch'); } on InvalidProgramLength { - throw new ArgumentError('Invalid address data'); + throw ArgumentError('Invalid address data'); } on InvalidWitnessVersion { - throw new ArgumentError('Invalid witness address version'); + throw ArgumentError('Invalid witness address version'); } } } diff --git a/lib/src/templates/pubkey.dart b/lib/src/templates/pubkey.dart index c839e85..ef703de 100644 --- a/lib/src/templates/pubkey.dart +++ b/lib/src/templates/pubkey.dart @@ -1,11 +1,10 @@ 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]); } -bool outputCheck(Uint8List script) { +bool? outputCheck(Uint8List script) { // TODO } diff --git a/lib/src/templates/pubkeyhash.dart b/lib/src/templates/pubkeyhash.dart index 88af094..d60eddf 100644 --- a/lib/src/templates/pubkeyhash.dart +++ b/lib/src/templates/pubkeyhash.dart @@ -3,10 +3,7 @@ import '../utils/script.dart' as bscript; import '../utils/constants/op.dart'; bool inputCheck(List chunks) { - return chunks != null && - chunks.length == 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - bscript.isCanonicalPubKey(chunks[1]); + return chunks.length == 2 && bscript.isCanonicalScriptSignature(chunks[0]) && bscript.isCanonicalPubKey(chunks[1]); } bool outputCheck(Uint8List script) { diff --git a/lib/src/templates/witnesspubkeyhash.dart b/lib/src/templates/witnesspubkeyhash.dart index ec660d7..9562edb 100644 --- a/lib/src/templates/witnesspubkeyhash.dart +++ b/lib/src/templates/witnesspubkeyhash.dart @@ -3,10 +3,7 @@ import '../utils/script.dart' as bscript; import '../utils/constants/op.dart'; bool inputCheck(List chunks) { - return chunks != null && - chunks.length == 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - bscript.isCanonicalPubKey(chunks[1]); + return chunks.length == 2 && bscript.isCanonicalScriptSignature(chunks[0]) && bscript.isCanonicalPubKey(chunks[1]); } bool outputCheck(Uint8List script) { diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart deleted file mode 100644 index 61f8756..0000000 --- a/lib/src/transaction.dart +++ /dev/null @@ -1,719 +0,0 @@ -import 'dart:typed_data'; -import 'package:hex/hex.dart'; -import 'payments/index.dart' show PaymentData; -import 'payments/p2pkh.dart' show P2PKH; -import 'payments/p2pk.dart' show P2PK; -import 'payments/p2wpkh.dart' show P2WPKH; -import 'crypto.dart' as bcrypto; -import 'classify.dart'; -import 'utils/check_types.dart'; -import 'utils/script.dart' as bscript; -import 'utils/constants/op.dart'; -import 'utils/varuint.dart' as varuint; - -const DEFAULT_SEQUENCE = 0xffffffff; -const SIGHASH_ALL = 0x01; -const SIGHASH_NONE = 0x02; -const SIGHASH_SINGLE = 0x03; -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 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); - -class Transaction { - int version = 1; - int locktime = 0; - List ins = []; - List outs = []; - Transaction(); - - int addInput(Uint8List hash, int index, [int sequence, Uint8List scriptSig]) { - ins.add(new Input( - hash: hash, - index: index, - sequence: sequence ?? DEFAULT_SEQUENCE, - script: scriptSig ?? EMPTY_SCRIPT, - witness: EMPTY_WITNESS)); - return ins.length - 1; - } - - int addOutput(Uint8List scriptPubKey, int value) { - outs.add(new Output(script: scriptPubKey, value: value)); - return outs.length - 1; - } - - bool hasWitnesses() { - var witness = ins.firstWhere( - (input) => input.witness != null && input.witness.length != 0, - orElse: () => null); - return witness != null; - } - - setInputScript(int index, Uint8List scriptSig) { - ins[index].script = scriptSig; - } - - setWitness(int index, List witness) { - ins[index].witness = witness; - } - - hashForWitnessV0( - int inIndex, Uint8List prevOutScript, int value, int hashType) { - var tbuffer = Uint8List.fromList([]); - var toffset = 0; - // Any changes made to the ByteData will also change the buffer, and vice versa. - // https://api.dart.dev/stable/2.7.1/dart-typed_data/ByteBuffer/asByteData.html - ByteData bytes = tbuffer.buffer.asByteData(); - var hashOutputs = ZERO; - var hashPrevouts = ZERO; - var hashSequence = ZERO; - - writeSlice(slice) { - tbuffer.setRange(toffset, toffset + slice.length, slice); - toffset += slice.length; - } - - writeUInt8(i) { - bytes.setUint8(toffset, i); - toffset++; - } - - writeUInt32(i) { - bytes.setUint32(toffset, i, Endian.little); - toffset += 4; - } - - writeInt32(i) { - bytes.setInt32(toffset, i, Endian.little); - toffset += 4; - } - - writeUInt64(i) { - bytes.setUint64(toffset, i, Endian.little); - toffset += 8; - } - - writeVarInt(i) { - varuint.encode(i, tbuffer, toffset); - toffset += varuint.encodingLength(i); - } - - writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - - writeVector(vector) { - writeVarInt(vector.length); - vector.forEach((buf) { - writeVarSlice(buf); - }); - } - - if ((hashType & SIGHASH_ANYONECANPAY) == 0) { - tbuffer = new Uint8List(36 * this.ins.length); - bytes = tbuffer.buffer.asByteData(); - toffset = 0; - - ins.forEach((txIn) { - writeSlice(txIn.hash); - writeUInt32(txIn.index); - }); - hashPrevouts = bcrypto.hash256(tbuffer); - } - - if ((hashType & SIGHASH_ANYONECANPAY) == 0 && - (hashType & 0x1f) != SIGHASH_SINGLE && - (hashType & 0x1f) != SIGHASH_NONE) { - tbuffer = new Uint8List(4 * this.ins.length); - bytes = tbuffer.buffer.asByteData(); - toffset = 0; - ins.forEach((txIn) { - writeUInt32(txIn.sequence); - }); - hashSequence = bcrypto.hash256(tbuffer); - } - - if ((hashType & 0x1f) != SIGHASH_SINGLE && - (hashType & 0x1f) != SIGHASH_NONE) { - var txOutsSize = - outs.fold(0, (sum, output) => sum + 8 + varSliceSize(output.script)); - tbuffer = new Uint8List(txOutsSize); - bytes = tbuffer.buffer.asByteData(); - toffset = 0; - outs.forEach((txOut) { - writeUInt64(txOut.value); - writeVarSlice(txOut.script); - }); - hashOutputs = bcrypto.hash256(tbuffer); - } 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)); - bytes = tbuffer.buffer.asByteData(); - toffset = 0; - writeUInt64(output.value); - writeVarSlice(output.script); - hashOutputs = bcrypto.hash256(tbuffer); - } - - tbuffer = new Uint8List(156 + varSliceSize(prevOutScript)); - bytes = tbuffer.buffer.asByteData(); - toffset = 0; - var input = ins[inIndex]; - writeUInt32(version); - writeSlice(hashPrevouts); - writeSlice(hashSequence); - writeSlice(input.hash); - writeUInt32(input.index); - writeVarSlice(prevOutScript); - writeUInt64(value); - writeUInt32(input.sequence); - writeSlice(hashOutputs); - writeUInt32(this.locktime); - writeUInt32(hashType); - - return bcrypto.hash256(tbuffer); - } - - hashForSignature(int inIndex, Uint8List prevOutScript, int hashType) { - if (inIndex >= ins.length) return ONE; - // ignore OP_CODESEPARATOR - final ourScript = - bscript.compile(bscript.decompile(prevOutScript).where((x) { - return x != OPS['OP_CODESEPARATOR']; - }).toList()); - final txTmp = Transaction.clone(this); - // SIGHASH_NONE: ignore all outputs? (wildcard payee) - if ((hashType & 0x1f) == SIGHASH_NONE) { - txTmp.outs = []; - // ignore sequence numbers (except at inIndex) - for (var i = 0; i < txTmp.ins.length; i++) { - if (i != inIndex) { - txTmp.ins[i].sequence = 0; - } - } - - // 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 >= outs.length) return ONE; - - // truncate outputs after - txTmp.outs.length = inIndex + 1; - - // 'blank' outputs before - for (var i = 0; i < inIndex; i++) { - txTmp.outs[i] = BLANK_OUTPUT; - } - // ignore sequence numbers (except at inIndex) - for (var i = 0; i < txTmp.ins.length; i++) { - if (i != inIndex) { - txTmp.ins[i].sequence = 0; - } - } - } - - // SIGHASH_ANYONECANPAY: ignore inputs entirely? - if (hashType & SIGHASH_ANYONECANPAY != 0) { - txTmp.ins = [txTmp.ins[inIndex]]; - txTmp.ins[0].script = ourScript; - // SIGHASH_ALL: only ignore input scripts - } else { - // 'blank' others input scripts - txTmp.ins.forEach((input) { - input.script = EMPTY_SCRIPT; - }); - txTmp.ins[inIndex].script = ourScript; - } - // serialize and hash - final buffer = Uint8List(txTmp.virtualSize() + 4); - buffer.buffer - .asByteData() - .setUint32(buffer.length - 4, hashType, Endian.little); - txTmp._toBuffer(buffer, 0); - return bcrypto.hash256(buffer); - } - - _byteLength(_ALLOW_WITNESS) { - var hasWitness = _ALLOW_WITNESS && hasWitnesses(); - 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)) + - (hasWitness - ? ins.fold(0, (sum, input) => sum + vectorSize(input.witness)) - : 0); - } - - int vectorSize(List someVector) { - var length = someVector.length; - return varuint.encodingLength(length) + - someVector.fold(0, (sum, witness) => sum + varSliceSize(witness)); - } - - int weight() { - var base = _byteLength(false); - var total = _byteLength(true); - return base * 3 + total; - } - - int byteLength() { - return _byteLength(true); - } - - int virtualSize() { - return (weight() / 4).ceil(); - } - - Uint8List toBuffer([Uint8List buffer, int initialOffset]) { - return this._toBuffer(buffer, initialOffset, true); - } - - String toHex() { - return HEX.encode(this.toBuffer()); - } - - bool isCoinbaseHash(buffer) { - isHash256bit(buffer); - for (var i = 0; i < 32; ++i) { - if (buffer[i] != 0) return false; - } - return true; - } - - bool isCoinbase() { - return ins.length == 1 && isCoinbaseHash(ins[0].hash); - } - - Uint8List getHash() { - // if (isCoinbase()) return Uint8List.fromList(List.generate(32, (i) => 0)); - return bcrypto.hash256(_toBuffer(null, null, false)); - } - - String getId() { - return HEX.encode(getHash().reversed.toList()); - } - - _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)); - - // Any changes made to the ByteData will also change the buffer, and vice versa. - // https://api.dart.dev/stable/2.7.1/dart-typed_data/ByteBuffer/asByteData.html - var bytes = buffer.buffer.asByteData(); - var offset = initialOffset ?? 0; - - writeSlice(slice) { - buffer.setRange(offset, offset + slice.length, slice); - offset += slice.length; - } - - writeUInt8(i) { - bytes.setUint8(offset, i); - offset++; - } - - writeUInt32(i) { - bytes.setUint32(offset, i, Endian.little); - offset += 4; - } - - writeInt32(i) { - bytes.setInt32(offset, i, Endian.little); - offset += 4; - } - - writeUInt64(i) { - bytes.setUint64(offset, i, Endian.little); - offset += 8; - } - - writeVarInt(i) { - varuint.encode(i, buffer, offset); - offset += varuint.encodingLength(i); - } - - writeVarSlice(slice) { - writeVarInt(slice.length); - writeSlice(slice); - } - - writeVector(vector) { - writeVarInt(vector.length); - vector.forEach((buf) { - writeVarSlice(buf); - }); - } - - // Start writeBuffer - writeInt32(version); - - if (_ALLOW_WITNESS && hasWitnesses()) { - writeUInt8(ADVANCED_TRANSACTION_MARKER); - writeUInt8(ADVANCED_TRANSACTION_FLAG); - } - - writeVarInt(this.ins.length); - - ins.forEach((txIn) { - writeSlice(txIn.hash); - writeUInt32(txIn.index); - writeVarSlice(txIn.script); - writeUInt32(txIn.sequence); - }); - - writeVarInt(this.outs.length); - - outs.forEach((txOut) { - if (txOut.valueBuffer == null) { - writeUInt64(txOut.value); - } else { - writeSlice(txOut.valueBuffer); - } - writeVarSlice(txOut.script); - }); - - if (_ALLOW_WITNESS && hasWitnesses()) { - ins.forEach((txInt) { - writeVector(txInt.witness); - }); - } - - writeUInt32(this.locktime); - // End writeBuffer - - // avoid slicing unless necessary - if (initialOffset != null) return buffer.sublist(initialOffset, offset); - - return buffer; - } - - factory Transaction.clone(Transaction _tx) { - Transaction tx = new Transaction(); - tx.version = _tx.version; - tx.locktime = _tx.locktime; - tx.ins = _tx.ins.map((input) { - return Input.clone(input); - }).toList(); - tx.outs = _tx.outs.map((output) { - return Output.clone(output); - }).toList(); - return tx; - } - - factory Transaction.fromBuffer( - Uint8List buffer, { - bool noStrict = false, - }) { - var offset = 0; - // Any changes made to the ByteData will also change the buffer, and vice versa. - // https://api.dart.dev/stable/2.7.1/dart-typed_data/ByteBuffer/asByteData.html - ByteData bytes = buffer.buffer.asByteData(); - - int readUInt8() { - final i = bytes.getUint8(offset); - offset++; - return i; - } - - int readUInt32() { - final i = bytes.getUint32(offset, Endian.little); - offset += 4; - return i; - } - - int readInt32() { - final i = bytes.getInt32(offset, Endian.little); - offset += 4; - return i; - } - - int readUInt64() { - final i = bytes.getUint64(offset, Endian.little); - offset += 8; - return i; - } - - Uint8List readSlice(n) { - offset += n; - return buffer.sublist(offset - n, offset); - } - - int readVarInt() { - final vi = varuint.decode(buffer, offset); - offset += varuint.encodingLength(vi); - return vi; - } - - Uint8List readVarSlice() { - return readSlice(readVarInt()); - } - - List readVector() { - var count = readVarInt(); - List vector = []; - for (var i = 0; i < count; ++i) { - vector.add(readVarSlice()); - } - return vector; - } - - final tx = new Transaction(); - tx.version = readInt32(); - - final marker = readUInt8(); - final flag = readUInt8(); - - var hasWitnesses = false; - if (marker == ADVANCED_TRANSACTION_MARKER && - flag == ADVANCED_TRANSACTION_FLAG) { - hasWitnesses = true; - } else { - offset -= 2; // Reset offset if not segwit tx - } - - final vinLen = readVarInt(); - for (var i = 0; i < vinLen; ++i) { - tx.ins.add(new Input( - hash: readSlice(32), - index: readUInt32(), - script: readVarSlice(), - sequence: readUInt32())); - } - - final voutLen = readVarInt(); - for (var i = 0; i < voutLen; ++i) { - tx.outs.add(new Output(value: readUInt64(), script: readVarSlice())); - } - - if (hasWitnesses) { - for (var i = 0; i < vinLen; ++i) { - tx.ins[i].witness = readVector(); - } - } - - tx.locktime = readUInt32(); - - if (noStrict) return tx; - - if (offset != buffer.length) - throw new ArgumentError('Transaction has unexpected data'); - - return tx; - } - - factory Transaction.fromHex( - String hex, { - bool noStrict = false, - }) { - return Transaction.fromBuffer( - HEX.decode(hex), - noStrict: noStrict, - ); - } - - @override - String toString() { - this.ins.forEach((txInput) { - print(txInput.toString()); - }); - this.outs.forEach((txOutput) { - print(txOutput.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; - - Input( - {this.hash, - this.index, - this.script, - this.sequence, - this.value, - this.prevOutScript, - this.pubkeys, - this.signatures, - this.witness, - this.prevOutType}) { - this.hasWitness = false; // Default value - if (this.hash != null && !isHash256bit(this.hash)) - throw new ArgumentError('Invalid input hash'); - if (this.index != null && !isUint(this.index, 32)) - throw new ArgumentError('Invalid input index'); - if (this.sequence != null && !isUint(this.sequence, 32)) - throw new ArgumentError('Invalid input sequence'); - if (this.value != null && !isShatoshi(this.value)) - throw ArgumentError('Invalid ouput value'); - } - - factory Input.expandInput(Uint8List scriptSig, List witness, - [String type, Uint8List scriptPubKey]) { - if (type == null || type == '') { - var ssType = classifyInput(scriptSig); - var wsType = classifyWitness(witness); - if (ssType == SCRIPT_TYPES['NONSTANDARD']) ssType = null; - if (wsType == SCRIPT_TYPES['NONSTANDARD']) wsType = null; - type = ssType ?? wsType; - } - if (type == SCRIPT_TYPES['P2WPKH']) { - P2WPKH p2wpkh = new 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]); - } 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]); - } 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]); - } - } - - factory Input.clone(Input input) { - return new Input( - hash: input.hash != null ? Uint8List.fromList(input.hash) : null, - index: input.index, - script: input.script != null ? Uint8List.fromList(input.script) : null, - sequence: input.sequence, - value: input.value, - prevOutScript: input.prevOutScript != null - ? Uint8List.fromList(input.prevOutScript) - : null, - pubkeys: input.pubkeys != 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, - ); - } - - @override - String toString() { - return 'Input{hash: $hash, index: $index, sequence: $sequence, value: $value, script: $script, signScript: $signScript, prevOutScript: $prevOutScript, pubkeys: $pubkeys, signatures: $signatures, witness: $witness, prevOutType: $prevOutType}'; - } -} - -class Output { - Uint8List script; - int value; - Uint8List valueBuffer; - List pubkeys; - List signatures; - - Output( - {this.script, - this.value, - this.pubkeys, - this.signatures, - this.valueBuffer}) { - if (value != null && !isShatoshi(value)) - throw ArgumentError('Invalid ouput value'); - } - - factory Output.expandOutput(Uint8List script, [Uint8List ourPubKey]) { - if (ourPubKey == null) return new Output(); - var type = classifyOutput(script); - if (type == SCRIPT_TYPES['P2WPKH']) { - Uint8List wpkh1 = - new P2WPKH(data: new PaymentData(output: script)).data.hash; - Uint8List wpkh2 = bcrypto.hash160(ourPubKey); - if (wpkh1 != wpkh2) throw ArgumentError('Hash mismatch!'); - return new Output(pubkeys: [ourPubKey], signatures: [null]); - } else if (type == SCRIPT_TYPES['P2PKH']) { - Uint8List pkh1 = - new P2PKH(data: new PaymentData(output: script)).data.hash; - Uint8List pkh2 = bcrypto.hash160(ourPubKey); - if (pkh1 != pkh2) throw ArgumentError('Hash mismatch!'); - return new Output(pubkeys: [ourPubKey], signatures: [null]); - } - } - - factory Output.clone(Output output) { - return new Output( - script: output.script != null ? Uint8List.fromList(output.script) : null, - value: output.value, - valueBuffer: output.valueBuffer != null - ? Uint8List.fromList(output.valueBuffer) - : null, - pubkeys: output.pubkeys != 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, - ); - } - - @override - String toString() { - return 'Output{script: $script, value: $value, valueBuffer: $valueBuffer, pubkeys: $pubkeys, signatures: $signatures}'; - } -} - -bool isCoinbaseHash(Uint8List buffer) { - if (!isHash256bit(buffer)) throw new ArgumentError('Invalid hash'); - for (var i = 0; i < 32; ++i) { - if (buffer[i] != 0) return false; - } - return true; -} - -bool _isP2PKHInput(script) { - final chunks = bscript.decompile(script); - return chunks != null && - chunks.length == 2 && - bscript.isCanonicalScriptSignature(chunks[0]) && - bscript.isCanonicalPubKey(chunks[1]); -} - -bool _isP2PKHOutput(script) { - final buffer = bscript.compile(script); - return buffer.length == 25 && - buffer[0] == OPS['OP_DUP'] && - buffer[1] == OPS['OP_HASH160'] && - buffer[2] == 0x14 && - buffer[23] == OPS['OP_EQUALVERIFY'] && - buffer[24] == OPS['OP_CHECKSIG']; -} - -int varSliceSize(Uint8List someScript) { - final length = someScript.length; - return varuint.encodingLength(length) + length; -} diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart deleted file mode 100644 index 2ff61bf..0000000 --- a/lib/src/transaction_builder.dart +++ /dev/null @@ -1,377 +0,0 @@ -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'; -import 'transaction.dart'; -import 'address.dart'; -import 'payments/index.dart' show PaymentData; -import 'payments/p2pkh.dart'; -import 'payments/p2wpkh.dart'; -import 'classify.dart'; - -class TransactionBuilder { - NetworkType network; - int maximumFeeRate; - List _inputs; - Transaction _tx; - 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; - } - - List get inputs => _inputs; - - factory TransactionBuilder.fromTransaction(Transaction transaction, - [NetworkType network]) { - final txb = new TransactionBuilder(network: network); - // Copy transaction fields - txb.setVersion(transaction.version); - txb.setLockTime(transaction.locktime); - - // Copy outputs (done first to avoid signature invalidation) - transaction.outs.forEach((txOut) { - txb.addOutput(txOut.script, txOut.value); - }); - - transaction.ins.forEach((txIn) { - txb._addInputUnsafe( - txIn.hash, - txIn.index, - new Input( - sequence: txIn.sequence, - script: txIn.script, - witness: txIn.witness)); - }); - - // fix some things not possible through the public API - // print(txb.toString()); - // txb.__INPUTS.forEach((input, i) => { - // fixMultisigOrder(input, transaction, i); - // }); - - return txb; - } - - setVersion(int version) { - if (version < 0 || version > 0xFFFFFFFF) - throw ArgumentError('Expected Uint32'); - _tx.version = version; - } - - setLockTime(int locktime) { - if (locktime < 0 || locktime > 0xFFFFFFFF) - throw ArgumentError('Expected Uint32'); - // if any signatures exist, throw - if (this._inputs.map((input) { - if (input.signatures == null) return false; - return input.signatures.map((s) { - return s != null; - }).contains(true); - }).contains(true)) { - throw new ArgumentError('No, this would invalidate signatures'); - } - _tx.locktime = locktime; - } - - int addOutput(dynamic data, int value) { - var scriptPubKey; - if (data is String) { - scriptPubKey = Address.addressToOutputScript(data, this.network); - } else if (data is Uint8List) { - scriptPubKey = data; - } else { - throw new ArgumentError('Address invalid'); - } - if (!_canModifyOutputs()) { - throw new ArgumentError('No, this would invalidate signatures'); - } - return _tx.addOutput(scriptPubKey, value); - } - - int addInput(dynamic txHash, int vout, - [int sequence, Uint8List prevOutScript]) { - if (!_canModifyInputs()) { - throw new ArgumentError('No, this would invalidate signatures'); - } - Uint8List hash; - var value; - if (txHash is String) { - hash = Uint8List.fromList(HEX.decode(txHash).reversed.toList()); - } else if (txHash is Uint8List) { - hash = txHash; - } else if (txHash is Transaction) { - final txOut = txHash.outs[vout]; - prevOutScript = txOut.script; - value = txOut.value; - hash = txHash.getHash(); - } else { - throw new ArgumentError('txHash invalid'); - } - return _addInputUnsafe( - hash, - vout, - new Input( - sequence: sequence, prevOutScript: prevOutScript, value: value)); - } - - sign( - {@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'); - if (vin >= _inputs.length) - throw new ArgumentError('No input at index: $vin'); - hashType = hashType ?? SIGHASH_ALL; - if (this._needsOutputs(hashType)) - throw new ArgumentError('Transaction needs outputs'); - final input = _inputs[vin]; - final ourPubKey = keyPair.publicKey; - if (!_canSign(input)) { - if (witnessValue != null) { - input.value = witnessValue; - } - if (redeemScript != null && witnessScript != null) { - // TODO p2wsh - } - if (redeemScript != null) { - // TODO - } - if (witnessScript != null) { - // TODO - } - if (input.prevOutScript != null && input.prevOutType != null) { - var type = classifyOutput(input.prevOutScript); - if (type == SCRIPT_TYPES['P2WPKH']) { - input.prevOutType = SCRIPT_TYPES['P2WPKH']; - input.hasWitness = true; - input.signatures = [null]; - input.pubkeys = [ourPubKey]; - input.signScript = new P2PKH( - data: new PaymentData(pubkey: ourPubKey), - network: this.network) - .data - .output; - } else { - // DRY CODE - Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); - input.prevOutType = SCRIPT_TYPES['P2PKH']; - input.signatures = [null]; - input.pubkeys = [ourPubKey]; - input.signScript = prevOutScript; - } - } else { - Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); - input.prevOutType = SCRIPT_TYPES['P2PKH']; - input.signatures = [null]; - input.pubkeys = [ourPubKey]; - input.signScript = prevOutScript; - } - } - var signatureHash; - if (input.hasWitness) { - signatureHash = this - ._tx - .hashForWitnessV0(vin, input.signScript, input.value, hashType); - } else { - signatureHash = - 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) - throw new ArgumentError('Signature already exists'); - final signature = keyPair.sign(signatureHash); - input.signatures[i] = bscript.encodeSignature(signature, hashType); - signed = true; - } - if (!signed) throw new ArgumentError('Key pair cannot sign for this input'); - } - - Transaction build() { - return _build(false); - } - - Transaction buildIncomplete() { - return _build(true); - } - - Transaction _build(bool allowIncomplete) { - if (!allowIncomplete) { - if (_tx.ins.length == 0) - throw new ArgumentError('Transaction has no inputs'); - if (_tx.outs.length == 0) - throw new ArgumentError('Transaction has no outputs'); - } - - final tx = Transaction.clone(_tx); - - 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) { - if (_inputs[i].prevOutType == SCRIPT_TYPES['P2PKH']) { - P2PKH payment = new P2PKH( - data: new PaymentData( - pubkey: _inputs[i].pubkeys[0], - signature: _inputs[i].signatures[0]), - network: network); - 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]), - network: network); - tx.setInputScript(i, payment.data.input); - tx.setWitness(i, payment.data.witness); - } - } else if (!allowIncomplete) { - throw new ArgumentError('Transaction is not complete'); - } - } - - if (!allowIncomplete) { - // do not rely on this, its merely a last resort - if (_overMaximumFees(tx.virtualSize())) { - throw new ArgumentError('Transaction has absurd fees'); - } - } - - return tx; - } - - bool _overMaximumFees(int bytes) { - int incoming = _inputs.fold(0, (cur, acc) => cur + (acc.value ?? 0)); - int outgoing = _tx.outs.fold(0, (cur, acc) => cur + (acc.value ?? 0)); - int fee = incoming - outgoing; - int feeRate = fee ~/ bytes; - return feeRate > maximumFeeRate; - } - - bool _canModifyInputs() { - return _inputs.every((input) { - if (input.signatures == null) return true; - return input.signatures.every((signature) { - if (signature == null) return true; - return _signatureHashType(signature) & SIGHASH_ANYONECANPAY != 0; - }); - }); - } - - bool _canModifyOutputs() { - final nInputs = _tx.ins.length; - final nOutputs = _tx.outs.length; - return _inputs.every((input) { - if (input.signatures == null) return true; - return input.signatures.every((signature) { - if (signature == null) return true; - final hashType = _signatureHashType(signature); - final hashTypeMod = hashType & 0x1f; - if (hashTypeMod == SIGHASH_NONE) return true; - if (hashTypeMod == SIGHASH_SINGLE) { - // if SIGHASH_SINGLE is set, and nInputs > nOutputs - // some signatures would be invalidated by the addition - // of more outputs - return nInputs <= nOutputs; - } - return false; - }); - }); - } - - bool _needsOutputs(int signingHashType) { - if (signingHashType == SIGHASH_ALL) { - return this._tx.outs.length == 0; - } - // if inputs are being signed with SIGHASH_NONE, we don't strictly need outputs - // .build() will fail, but .buildIncomplete() is OK - return (this._tx.outs.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 - }).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; - } - - _addInputUnsafe(Uint8List hash, int vout, Input options) { - String txHash = HEX.encode(hash); - Input input; - if (isCoinbaseHash(hash)) { - throw new ArgumentError('coinbase inputs not supported'); - } - final prevTxOut = '$txHash:$vout'; - if (_prevTxSet[prevTxOut] != null) - throw new ArgumentError('Duplicate TxOut: ' + prevTxOut); - if (options.script != null) { - input = - 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) { - input.pubkeys = expanded.pubkeys; - input.signatures = expanded.signatures; - } - } - input.prevOutScript = options.prevOutScript; - input.prevOutType = classifyOutput(options.prevOutScript); - } - int vin = _tx.addInput(hash, vout, options.sequence, options.script); - _inputs.add(input); - _prevTxSet[prevTxOut] = true; - return vin; - } - - int _signatureHashType(Uint8List buffer) { - return buffer.buffer.asByteData().getUint8(buffer.length - 1); - } - - Transaction get tx => _tx; - - Map get prevTxSet => _prevTxSet; -} - -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; -} diff --git a/lib/src/utils/magic_hash.dart b/lib/src/utils/magic_hash.dart index f99dba4..118211e 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]) { - network = network ?? bitcoin; - Uint8List messagePrefix = utf8.encode(network.messagePrefix); +Uint8List magicHash(String message, [NetworkType? network]) { + final _network = network ?? bitcoin; + List messagePrefix = utf8.encode(_network.messagePrefix); 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..a28ae43 100644 --- a/lib/src/utils/push_data.dart +++ b/lib/src/utils/push_data.dart @@ -5,14 +5,14 @@ class DecodedPushData { int opcode; int number; int size; - DecodedPushData({this.opcode, this.number, this.size}); + DecodedPushData({required this.opcode, required this.number, required this.size}); } class EncodedPushData { int size; Uint8List buffer; - EncodedPushData({this.size, this.buffer}); + EncodedPushData({required this.size, required this.buffer}); } EncodedPushData encode(Uint8List buffer, number, offset) { @@ -23,30 +23,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; diff --git a/lib/src/utils/script.dart b/lib/src/utils/script.dart index ac6678b..6886a39 100644 --- a/lib/src/utils/script.dart +++ b/lib/src/utils/script.dart @@ -5,20 +5,25 @@ import 'constants/op.dart'; import 'push_data.dart' as pushData; import 'check_types.dart'; -Map REVERSE_OPS = - OPS.map((String string, int number) => new MapEntry(number, string)); +Map REVERSE_OPS = OPS.map((String string, int number) => MapEntry(number, string)); final OP_INT_BASE = OPS['OP_RESERVED']; final ZERO = Uint8List.fromList([0]); Uint8List compile(List chunks) { final bufferSize = chunks.fold(0, (acc, chunk) { - if (chunk is int) return acc + 1; + num _acc = 0; + try { + _acc = acc as num; + } catch (e) { + print(e); + } + if (chunk is int) return _acc + 1; if (chunk.length == 1 && asMinimalOP(chunk) != null) { - return acc + 1; + return _acc + 1; } - return acc + pushData.encodingLength(chunk.length) + chunk.length; + return _acc + pushData.encodingLength(chunk.length) + chunk.length; }); - var buffer = new Uint8List(bufferSize); + var buffer = Uint8List(bufferSize.toInt()); var offset = 0; chunks.forEach((chunk) { @@ -31,8 +36,7 @@ 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); @@ -44,12 +48,11 @@ Uint8List compile(List chunks) { } }); - if (offset != buffer.length) - throw new ArgumentError("Could not decode chunks"); + if (offset != buffer.length) throw ArgumentError("Could not decode chunks"); return buffer; } -List decompile(dynamic buffer) { +List? decompile(dynamic buffer) { List chunks = []; if (buffer == null) return chunks; @@ -98,13 +101,15 @@ Uint8List fromASM(String asm) { }).toList()); } -String toASM(List c) { - List chunks; +String? toASM(List c) { + List? chunks; if (c is Uint8List) { chunks = decompile(c); } else { chunks = c; } + + if (chunks == null) return null; return chunks.map((chunk) { // data? if (chunk is Uint8List) { @@ -117,11 +122,11 @@ String toASM(List c) { }).join(' '); } -int asMinimalOP(Uint8List buffer) { - if (buffer.length == 0) return OPS['OP_0']; +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] == 0x81) return OPS['OP_1NEGATE']; + if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE! + buffer[0]; + if (buffer[0] == 0x81) return OPS['OP_1NEGATE']!; return null; } @@ -160,26 +165,23 @@ 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; } Uint8List bip66encode(r, s) { - var lenR = r.length; - var lenS = s.length; - if (lenR == 0) throw new ArgumentError('R length is zero'); - if (lenS == 0) throw new ArgumentError('S length is zero'); - if (lenR > 33) throw new ArgumentError('R length is too long'); - 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'); - - var signature = new Uint8List(6 + lenR + lenS); + final int lenR = r.length; + final int lenS = s.length; + if (lenR == 0) throw ArgumentError('R length is zero'); + if (lenS == 0) throw ArgumentError('S length is zero'); + if (lenR > 33) throw ArgumentError('R length is too long'); + if (lenS > 33) throw ArgumentError('S length is too long'); + if (r[0] & 0x80 != 0) throw ArgumentError('R value is negative'); + if (s[0] & 0x80 != 0) throw ArgumentError('S value is negative'); + if (lenR > 1 && (r[0] == 0x00) && r[1] & 0x80 == 0) throw ArgumentError('R value excessively padded'); + if (lenS > 1 && (s[0] == 0x00) && s[1] & 0x80 == 0) throw ArgumentError('S value excessively padded'); + + var signature = Uint8List(6 + lenR + lenS); // 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] signature[0] = 0x30; @@ -197,10 +199,9 @@ 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 & ~0x80; - if (hashTypeMod <= 0 || hashTypeMod >= 4) - throw new ArgumentError('Invalid hashType $hashType'); + if (hashTypeMod <= 0 || hashTypeMod >= 4) throw ArgumentError('Invalid hashType $hashType'); - final hashTypeBuffer = new Uint8List(1); + final hashTypeBuffer = Uint8List(1); hashTypeBuffer.buffer.asByteData().setUint8(0, hashType); final r = toDER(signature.sublist(0, 32)); final s = toDER(signature.sublist(32, 64)); diff --git a/lib/src/utils/varuint.dart b/lib/src/utils/varuint.dart index 04aff4c..1cf1030 100644 --- a/lib/src/utils/varuint.dart +++ b/lib/src/utils/varuint.dart @@ -1,10 +1,10 @@ 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)); + buffer = buffer ?? Uint8List(encodingLength(number)); offset = offset ?? 0; ByteData bytes = buffer.buffer.asByteData(); // 8 bit @@ -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 062977e..26ba484 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,16 +5,16 @@ homepage: https://github.com/anicdh author: andoan environment: - sdk: ">=2.3.0 <3.0.0" + sdk: ">=2.13.0 <3.0.0" dependencies: - bip39: ^1.0.3 - bip32: ^1.0.5 - pointycastle: ^1.0.2 - hex: ^0.1.2 - bs58check: ^1.0.1 - meta: ^1.2.3 - bech32: 0.1.2 + bip39: ^1.0.6 + bip32: ^2.0.0 + pointycastle: ^3.1.3 + hex: ^0.2.0 + bs58check: ^1.0.2 + meta: ^1.7.0 + bech32: ^0.2.1 dev_dependencies: - test: ^1.15.4 + test: ^1.17.10 diff --git a/test/ecpair_test.dart b/test/ecpair_test.dart index ec70e54..2fbdf63 100644 --- a/test/ecpair_test.dart +++ b/test/ecpair_test.dart @@ -6,31 +6,28 @@ 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'); main() { - final fixtures = json.decode( - new File('test/fixtures/ecpair.json').readAsStringSync(encoding: utf8)); + final fixtures = json.decode(File('test/fixtures/ecpair.json').readAsStringSync(encoding: utf8)); group('ECPair', () { group('fromPrivateKey', () { test('defaults to compressed', () { - final keyPair = ECPair.fromPrivateKey(ONE); + final keyPair = ECPair.fromPrivateKey(Uint8List.fromList(ONE)); expect(keyPair.compressed, true); }); test('supports the uncompressed option', () { - final keyPair = ECPair.fromPrivateKey(ONE, compressed: false); + final keyPair = ECPair.fromPrivateKey(Uint8List.fromList(ONE), compressed: false); expect(keyPair.compressed, false); }); test('supports the network option', () { - final keyPair = ECPair.fromPrivateKey(ONE, - network: NETWORKS.testnet, compressed: false); + final keyPair = ECPair.fromPrivateKey(Uint8List.fromList(ONE), network: NETWORKS.testnet, compressed: false); expect(keyPair.network, NETWORKS.testnet); }); (fixtures['valid'] as List).forEach((f) { test('derives public key for ${f['WIF']}', () { final d = HEX.decode(f['d']); - final keyPair = ECPair.fromPrivateKey(d, compressed: f['compressed']); + final keyPair = ECPair.fromPrivateKey(Uint8List.fromList(d), compressed: f['compressed']); expect(HEX.encode(keyPair.publicKey), f['Q']); }); }); @@ -38,7 +35,7 @@ main() { test('throws ' + f['exception'], () { final d = HEX.decode(f['d']); try { - expect(ECPair.fromPrivateKey(d), isArgumentError); + expect(ECPair.fromPrivateKey(Uint8List.fromList(d)), isArgumentError); } catch (err) { expect((err as ArgumentError).message, f['exception']); } @@ -50,7 +47,7 @@ main() { test('throws ' + f['exception'], () { final Q = HEX.decode(f['Q']); try { - expect(ECPair.fromPublicKey(Q), isArgumentError); + expect(ECPair.fromPublicKey(Uint8List.fromList(Q)), isArgumentError); } catch (err) { expect((err as ArgumentError).message, f['exception']); } @@ -62,7 +59,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(Uint8List.fromList(keyPair.privateKey!)), f['d']); expect(keyPair.compressed, f['compressed']); expect(keyPair.network, network); }); @@ -101,8 +98,7 @@ main() { expect(keyPair.network, NETWORKS.bitcoin); }); test('supports the options parameter', () { - final keyPair = - ECPair.makeRandom(compressed: false, network: NETWORKS.testnet); + final keyPair = ECPair.makeRandom(compressed: false, network: NETWORKS.testnet); expect(keyPair.compressed, false); expect(keyPair.network, NETWORKS.testnet); }); diff --git a/test/integration/addresses_test.dart b/test/integration/addresses_test.dart index ebe72db..586fc60 100644 --- a/test/integration/addresses_test.dart +++ b/test/integration/addresses_test.dart @@ -1,7 +1,9 @@ +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; -import '../../lib/src/payments/p2pkh.dart' show P2PKH, P2PKHData; +import '../../lib/src/payments/p2pkh.dart' show P2PKH; import '../../lib/src/payments/p2wpkh.dart' show P2WPKH; import 'package:pointycastle/digests/sha256.dart'; import 'dart:convert'; @@ -22,72 +24,44 @@ main() { group('bitcoinjs-lib (addresses)', () { test('can generate a random address', () { final keyPair = ECPair.makeRandom(rng: rng); - final address = - new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)) - .data - .address; + final address = new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)).data.address; expect(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64'); }); test('can generate an address from a SHA256 hash', () { - final hash = new SHA256Digest() - .process(utf8.encode('correct horse battery staple')); + final hash = SHA256Digest().process(Uint8List.fromList(utf8.encode('correct horse battery staple'))); final keyPair = ECPair.fromPrivateKey(hash); - final address = - new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)) - .data - .address; + final address = new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)).data.address; expect(address, '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8'); }); test('can import an address via WIF', () { - final keyPair = ECPair.fromWIF( - 'Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct'); - final address = - new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)) - .data - .address; + final keyPair = ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct'); + final address = new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)).data.address; expect(address, '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31'); }); test('can generate a Testnet address', () { final testnet = NETWORKS.testnet; final keyPair = ECPair.makeRandom(network: testnet, rng: rng); final wif = keyPair.toWIF(); - final address = new P2PKH( - data: new PaymentData(pubkey: keyPair.publicKey), - network: testnet) - .data - .address; + final address = new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey), network: testnet).data.address; expect(address, 'mubSzQNtZfDj1YdNP6pNDuZy6zs6GDn61L'); expect(wif, 'cRgnQe9MUu1JznntrLaoQpB476M8PURvXVQB5R2eqms5tXnzNsrr'); }); test('can generate a Litecoin address', () { final keyPair = ECPair.makeRandom(network: litecoin, rng: rng); final wif = keyPair.toWIF(); - final address = new P2PKH( - data: new PaymentData(pubkey: keyPair.publicKey), - network: litecoin) - .data - .address; + final address = new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey), network: litecoin).data.address; expect(address, 'LZJSxZbjqJ2XVEquqfqHg1RQTDdfST5PTn'); expect(wif, 'T7A4PUSgTDHecBxW1ZiYFrDNRih2o7M8Gf9xpoCgudPF9gDiNvuS'); }); test('can generate a SegWit address', () { - final keyPair = ECPair.fromWIF( - 'KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn'); - final address = - new P2WPKH(data: new PaymentData(pubkey: keyPair.publicKey)) - .data - .address; + final keyPair = ECPair.fromWIF('KwDiBf89QgGbjEhKnhXJuH7LrciVrZi3qYjgd9M7rFU73sVHnoWn'); + final address = new P2WPKH(data: new PaymentData(pubkey: keyPair.publicKey)).data.address; expect(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'); }); test('can generate a SegWit testnet address', () { final testnet = NETWORKS.testnet; - final keyPair = ECPair.fromWIF( - 'cPaJYBMDLjQp5gSUHnBfhX4Rgj95ekBS6oBttwQLw3qfsKKcDfuB'); - final address = new P2WPKH( - data: new PaymentData(pubkey: keyPair.publicKey), - network: testnet) - .data - .address; + final keyPair = ECPair.fromWIF('cPaJYBMDLjQp5gSUHnBfhX4Rgj95ekBS6oBttwQLw3qfsKKcDfuB'); + final address = new P2WPKH(data: new PaymentData(pubkey: keyPair.publicKey), network: testnet).data.address; expect(address, 'tb1qgmp0h7lvexdxx9y05pmdukx09xcteu9sx2h4ya'); }); }); diff --git a/test/integration/bip32_test.dart b/test/integration/bip32_test.dart index 22119f0..f5babf3 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'; @@ -9,21 +11,13 @@ import 'package:bip32/bip32.dart' as bip32; void main() { group('bitcoin-dart (BIP32)', () { test('can import a BIP32 testnet xpriv and export to WIF', () { - const xpriv = - 'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK'; - final node = bip32.BIP32.fromBase58( - xpriv, - bip32.NetworkType( - wif: testnet.wif, - bip32: new bip32.Bip32Type( - public: testnet.bip32.public, - private: testnet.bip32.private))); - expect( - node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7'); + const xpriv = 'tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK'; + final node = bip32.BIP32.fromBase58(xpriv, + bip32.NetworkType(wif: testnet.wif, bip32: new bip32.Bip32Type(public: testnet.bip32.public, private: testnet.bip32.private))); + expect(node.toWIF(), 'cQfoY67cetFNunmBUX5wJiw3VNoYx3gG9U9CAofKE6BfiV1fSRw7'); }); test('can export a BIP32 xpriv, then import it', () { - const mnemonic = - 'praise you muffin lion enable neck grocery crumble super myself license ghost'; + const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'; final seed = bip39.mnemonicToSeed(mnemonic); final node = bip32.BIP32.fromSeed(seed); final string = node.toBase58(); @@ -32,18 +26,15 @@ void main() { expect(node.toWIF(), restored.toWIF()); // same private key }); test('can export a BIP32 xpub', () { - const mnemonic = - 'praise you muffin lion enable neck grocery crumble super myself license ghost'; + const mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'; final seed = bip39.mnemonicToSeed(mnemonic); final node = bip32.BIP32.fromSeed(seed); final string = node.neutered().toBase58(); - expect(string, - 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n'); + expect(string, 'xpub661MyMwAqRbcGhVeaVfEBA25e3cP9DsJQZoE8iep5fZSxy3TnPBNBgWnMZx56oreNc48ZoTkQfatNJ9VWnQ7ZcLZcVStpaXLTeG8bGrzX3n'); }); test('can create a BIP32, bitcoin, account 0, external address', () { const path = "m/0'/0/0"; - final root = bip32.BIP32.fromSeed(HEX.decode( - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd')); + final root = bip32.BIP32.fromSeed(Uint8List.fromList(HEX.decode('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'))); final child1 = root.derivePath(path); // option 2, manually final child1b = root.deriveHardened(0).derive(0).derive(0); @@ -51,16 +42,10 @@ void main() { expect(getAddress(child1b), '1JHyB1oPXufr4FXkfitsjgNB5yRY9jAaa7'); }); test('can create a BIP44, bitcoin, account 0, external address', () { - final root = bip32.BIP32.fromSeed(HEX.decode( - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd')); + final root = bip32.BIP32.fromSeed(Uint8List.fromList(HEX.decode('dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd'))); final child1 = root.derivePath("m/44'/0'/0'/0/0"); // option 2, manually - final child1b = root - .deriveHardened(44) - .deriveHardened(0) - .deriveHardened(0) - .derive(0) - .derive(0); + final child1b = root.deriveHardened(44).deriveHardened(0).deriveHardened(0).derive(0).derive(0); expect(getAddress(child1), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au'); expect(getAddress(child1b), '12Tyvr1U8A3ped6zwMEU5M8cx3G38sP5Au'); }); @@ -68,27 +53,20 @@ void main() { test('can create a BIP49, bitcoin testnet, account 0, external address', () { }); */ test('can use BIP39 to generate BIP32 addresses', () { - final mnemonic = - 'praise you muffin lion enable neck grocery crumble super myself license ghost'; + final mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'; assert(bip39.validateMnemonic(mnemonic)); final seed = bip39.mnemonicToSeed(mnemonic); final root = bip32.BIP32.fromSeed(seed); // receive addresses - expect(getAddress(root.derivePath("m/0'/0/0")), - '1AVQHbGuES57wD68AJi7Gcobc3RZrfYWTC'); - expect(getAddress(root.derivePath("m/0'/0/1")), - '1Ad6nsmqDzbQo5a822C9bkvAfrYv9mc1JL'); + expect(getAddress(root.derivePath("m/0'/0/0")), '1AVQHbGuES57wD68AJi7Gcobc3RZrfYWTC'); + expect(getAddress(root.derivePath("m/0'/0/1")), '1Ad6nsmqDzbQo5a822C9bkvAfrYv9mc1JL'); // change addresses - expect(getAddress(root.derivePath("m/0'/1/0")), - '1349KVc5NgedaK7DvuD4xDFxL86QN1Hvdn'); - expect(getAddress(root.derivePath("m/0'/1/1")), - '1EAvj4edpsWcSer3duybAd4KiR4bCJW5J6'); + expect(getAddress(root.derivePath("m/0'/1/0")), '1349KVc5NgedaK7DvuD4xDFxL86QN1Hvdn'); + expect(getAddress(root.derivePath("m/0'/1/1")), '1EAvj4edpsWcSer3duybAd4KiR4bCJW5J6'); }); }); } -String getAddress(node, [network]) { - return P2PKH(data: new PaymentData(pubkey: node.publicKey), network: network) - .data - .address; +String? getAddress(node, [network]) { + return P2PKH(data: new PaymentData(pubkey: node.publicKey), network: network).data.address; } diff --git a/test/integration/transactions_test.dart b/test/integration/transactions_test.dart deleted file mode 100644 index f5a6db6..0000000 --- a/test/integration/transactions_test.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:test/test.dart'; -import 'package:hex/hex.dart'; -import '../../lib/src/ecpair.dart'; -import '../../lib/src/transaction_builder.dart'; -import '../../lib/src/models/networks.dart' as NETWORKS; -import '../../lib/src/payments/p2wpkh.dart' show P2WPKH; -import '../../lib/src/payments/index.dart' show PaymentData; - -main() { - group('bitcoinjs-lib (transactions)', () { - test('can create a 1-to-1 Transaction', () { - final alice = ECPair.fromWIF( - 'L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy'); - final txb = new TransactionBuilder(); - - txb.setVersion(1); - txb.addInput( - '61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', - 0); // Alice's previous transaction output, has 15000 satoshis - txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000); - // (in)15000 - (out)12000 = (fee)3000, this is the miner fee - - txb.sign(vin: 0, keyPair: alice); - - // prepare for broadcast to the Bitcoin network, see 'can broadcast a Transaction' below - expect(txb.build().toHex(), - '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000'); - }); - - test('can create a 2-to-2 Transaction', () { - final alice = ECPair.fromWIF( - 'L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1'); - final bob = ECPair.fromWIF( - 'KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z'); - - final txb = new TransactionBuilder(); - txb.setVersion(1); - txb.addInput( - 'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c', - 6); // Alice's previous transaction output, has 200000 satoshis - txb.addInput( - '7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730', - 0); // Bob's previous transaction output, has 300000 satoshis - txb.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000); - txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000); - // (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee - - txb.sign( - vin: 1, - keyPair: - bob); // Bob signs his input, which was the second input (1th) - txb.sign( - vin: 0, - keyPair: - alice); // Alice signs her input, which was the first input (0th) - - // prepare for broadcast to the Bitcoin network, see 'can broadcast a Transaction' below - expect(txb.build().toHex(), - '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000'); - }); - - test('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', - () { - final alice = ECPair.fromWIF( - 'cUNfunNKXNNJDvUvsjxz5tznMR6ob1g5K6oa4WGbegoQD3eqf4am', - network: NETWORKS.testnet); - final p2wpkh = new P2WPKH( - data: new PaymentData(pubkey: alice.publicKey), - network: NETWORKS.testnet) - .data; - final txb = new TransactionBuilder(network: NETWORKS.testnet); - txb.setVersion(1); - txb.addInput( - '53676626f5042d42e15313492ab7e708b87559dc0a8c74b7140057af51a2ed5b', - 0, - null, - p2wpkh - .output); // Alice's previous transaction output, has 200000 satoshis - txb.addOutput('tb1qchsmnkk5c8wsjg8vxecmsntynpmkxme0yvh2yt', 1000000); - txb.addOutput('tb1qn40fftdp6z2lvzmsz4s0gyks3gq86y2e8svgap', 8995000); - - txb.sign(vin: 0, keyPair: alice, witnessValue: 10000000); - // // prepare for broadcast to the Bitcoin network, see 'can broadcast a Transaction' below - expect(txb.build().toHex(), - '010000000001015beda251af570014b7748c0adc5975b808e7b72a491353e1422d04f5266667530000000000ffffffff0240420f0000000000160014c5e1b9dad4c1dd0920ec3671b84d649877636f2fb8408900000000001600149d5e94ada1d095f60b701560f412d08a007d11590247304402203c4670ff81d352924af311552e0379861268bebb2222eeb0e66b3cdd1d4345b60220585b57982d958208cdd52f4ead4ecb86cfa9ff7740c2f6933e77135f1cc4c58f012102f9f43a191c6031a5ffae27c5f9911218e78857923284ac1154abc2cc008544b200000000'); - }); - }); -} diff --git a/test/payments/p2pkh_test.dart b/test/payments/p2pkh_test.dart index 63721e7..0c0290e 100644 --- a/test/payments/p2pkh_test.dart +++ b/test/payments/p2pkh_test.dart @@ -41,10 +41,9 @@ main() { 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 +53,24 @@ 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: Uint8List.fromList(hash!), + input: input, + output: Uint8List.fromList(output!), + pubkey: Uint8List.fromList(pubkey!), + signature: Uint8List.fromList(signature!), + ); } -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..9fd8806 100644 --- a/test/payments/p2wpkh_test.dart +++ b/test/payments/p2wpkh_test.dart @@ -8,7 +8,6 @@ import 'package:hex/hex.dart'; import 'dart:typed_data'; main() { - final fixtures = json.decode(new File("./test/fixtures/p2wpkh.json").readAsStringSync(encoding: utf8)); group('(valid case)', () { @@ -47,27 +46,38 @@ main() { 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: Uint8List.fromList(hash!), + input: Uint8List.fromList(input!), + output: Uint8List.fromList(output!), + pubkey: Uint8List.fromList(pubkey!), + signature: Uint8List.fromList(signature!), + 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 deleted file mode 100644 index 99c1e53..0000000 --- a/test/transaction_builder_test.dart +++ /dev/null @@ -1,374 +0,0 @@ -import 'package:test/test.dart'; -import 'dart:io'; -import 'dart:convert'; -import 'dart:typed_data'; -import 'package:hex/hex.dart'; -import '../lib/src/models/networks.dart'; -import '../lib/src/ecpair.dart'; -import '../lib/src/transaction.dart'; -import '../lib/src/address.dart'; -import '../lib/src/transaction_builder.dart'; -import '../lib/src/utils/script.dart' as bscript; -import '../lib/src/payments/index.dart' show PaymentData; -import '../lib/src/payments/p2pkh.dart'; - -final NETWORKS = {'bitcoin': bitcoin, 'testnet': testnet}; - -constructSign(f, TransactionBuilder txb) { - final network = NETWORKS[f['network']]; - final inputs = f['inputs'] as List; - for (var i = 0; i < inputs.length; i++) { - if (inputs[i]['signs'] == null) continue; - (inputs[i]['signs'] as List).forEach((sign) { - ECPair keyPair = ECPair.fromWIF(sign['keyPair'], network: network); - txb.sign( - vin: i, - keyPair: keyPair, - witnessValue: sign['value'], - hashType: sign['hashType']); - }); - } - return txb; -} - -TransactionBuilder construct(f, [bool dontSign]) { - final network = NETWORKS[f['network']]; - final txb = new TransactionBuilder(network: network); - if (f['version'] != null) txb.setVersion(f['version']); - (f['inputs'] as List).forEach((input) { - var prevTx; - if (input['txRaw'] != null) { - final constructed = construct(input['txRaw']); - if (input['txRaw']['incomplete']) - prevTx = constructed.buildIncomplete(); - else - prevTx = constructed.build(); - } else if (input['txHex'] != null) { - prevTx = Transaction.fromHex(input['txHex']); - } else { - prevTx = input['txId']; - } - var prevTxScript; - if (input['prevTxScript'] != null) { - prevTxScript = bscript.fromASM(input['prevTxScript']); - } - txb.addInput(prevTx, input['vout'], input['sequence'], prevTxScript); - }); - (f['outputs'] as List).forEach((output) { - if (output['address'] != null) { - txb.addOutput(output['address'], output['value']); - } else { - txb.addOutput(bscript.fromASM(output['script']), output['value']); - } - }); - if (dontSign != null && dontSign) return txb; - return constructSign(f, txb); -} - -main() { - final fixtures = json.decode( - new File('test/fixtures/transaction_builder.json') - .readAsStringSync(encoding: utf8)); - group('TransactionBuilder', () { - final keyPair = ECPair.fromPrivateKey(HEX.decode( - '0000000000000000000000000000000000000000000000000000000000000001')); - final scripts = [ - '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', - '1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP' - ].map((x) => Address.addressToOutputScript(x)); - final txHash = HEX.decode( - '0e7cea811c0be9f73c0aca591034396e7264473fc25c1ca45195d7417b36cbe2'); - group('fromTransaction', () { - (fixtures['valid']['build'] as List).forEach((f) { - test('returns TransactionBuilder, with ${f['description']}', () { - final network = NETWORKS[f['network'] ?? 'bitcoin']; - final tx = Transaction.fromHex(f['txHex']); - final txb = TransactionBuilder.fromTransaction(tx, network); - final txAfter = - f['incomplete'] != null ? txb.buildIncomplete() : txb.build(); - expect(txAfter.toHex(), f['txHex']); - expect(txb.network, network); - }); - }); - (fixtures['valid']['fromTransaction'] as List).forEach((f) { - test('returns TransactionBuilder, with ${f['description']}', () { - final tx = new Transaction(); - f['inputs'] as List - ..forEach((input) { - final txHash2 = Uint8List.fromList( - HEX.decode(input['txId']).reversed.toList()); - tx.addInput(txHash2, input['vout'], null, - bscript.fromASM(input['scriptSig'])); - }); - f['outputs'] as List - ..forEach((output) { - tx.addOutput(bscript.fromASM(output['script']), output['value']); - }); - - final txb = TransactionBuilder.fromTransaction(tx); - final txAfter = f['incomplete'] ? txb.buildIncomplete() : txb.build(); - - for (var i = 0; i < txAfter.ins.length; i++) { - 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), - f['outputs'][i]['script']); - } - }); - }); - fixtures['invalid']['fromTransaction'] as List - ..forEach((f) { - test('throws ${f['exception']}', () { - final tx = Transaction.fromHex(f['txHex']); - try { - expect(TransactionBuilder.fromTransaction(tx), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, f['exception']); - } - }); - }); - }); - group('addInput', () { - TransactionBuilder txb; - setUp(() { - txb = new TransactionBuilder(); - }); - test('accepts a txHash, index [and sequence number]', () { - final vin = txb.addInput(txHash, 1, 54); - expect(vin, 0); - final txIn = txb.tx.ins[0]; - expect(txIn.hash, txHash); - expect(txIn.index, 1); - expect(txIn.sequence, 54); - expect(txb.inputs[0].prevOutScript, null); - }); - test('accepts a txHash, index [, sequence number and scriptPubKey]', () { - final vin = txb.addInput(txHash, 1, 54, scripts.elementAt(1)); - expect(vin, 0); - final txIn = txb.tx.ins[0]; - expect(txIn.hash, txHash); - expect(txIn.index, 1); - expect(txIn.sequence, 54); - expect(txb.inputs[0].prevOutScript, scripts.elementAt(1)); - }); - test('accepts a prevTx, index [and sequence number]', () { - final prevTx = new Transaction(); - prevTx.addOutput(scripts.elementAt(0), 0); - prevTx.addOutput(scripts.elementAt(1), 1); - - final vin = txb.addInput(prevTx, 1, 54); - expect(vin, 0); - - final txIn = txb.tx.ins[0]; - expect(txIn.hash, prevTx.getHash()); - expect(txIn.index, 1); - expect(txIn.sequence, 54); - expect(txb.inputs[0].prevOutScript, scripts.elementAt(1)); - }); - test('returns the input index', () { - expect(txb.addInput(txHash, 0), 0); - expect(txb.addInput(txHash, 1), 1); - }); - test( - 'throws if SIGHASH_ALL has been used to sign any existing scriptSigs', - () { - txb.addInput(txHash, 0); - txb.addOutput(scripts.elementAt(0), 1000); - txb.sign(vin: 0, keyPair: keyPair); - try { - expect(txb.addInput(txHash, 0), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, - 'No, this would invalidate signatures'); - } - }); - }); - group('addOutput', () { - TransactionBuilder txb; - setUp(() { - txb = new TransactionBuilder(); - }); - test('accepts an address string and value', () { - final address = - new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)) - .data - .address; - final vout = txb.addOutput(address, 1000); - expect(vout, 0); - final txout = txb.tx.outs[0]; - expect(txout.script, scripts.elementAt(0)); - expect(txout.value, 1000); - }); - test('accepts a ScriptPubKey and value', () { - final vout = txb.addOutput(scripts.elementAt(0), 1000); - expect(vout, 0); - final txout = txb.tx.outs[0]; - expect(txout.script, scripts.elementAt(0)); - expect(txout.value, 1000); - }); - test('throws if address is of the wrong network', () { - try { - expect(txb.addOutput('2NGHjvjw83pcVFgMcA7QvSMh2c246rxLVz9', 1000), - isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, - 'Invalid version or Network mismatch'); - } - }); - test('add second output after signed first input with SIGHASH_NONE', () { - txb.addInput(txHash, 0); - txb.addOutput(scripts.elementAt(0), 2000); - txb.sign(vin: 0, keyPair: keyPair, hashType: SIGHASH_NONE); - expect(txb.addOutput(scripts.elementAt(1), 9000), 1); - }); - test('add first output after signed first input with SIGHASH_NONE', () { - txb.addInput(txHash, 0); - txb.sign(vin: 0, keyPair: keyPair, hashType: SIGHASH_NONE); - expect(txb.addOutput(scripts.elementAt(0), 2000), 0); - }); - test('add second output after signed first input with SIGHASH_SINGLE', - () { - txb.addInput(txHash, 0); - txb.addOutput(scripts.elementAt(0), 2000); - txb.sign(vin: 0, keyPair: keyPair, hashType: SIGHASH_SINGLE); - expect(txb.addOutput(scripts.elementAt(1), 9000), 1); - }); - test('add first output after signed first input with SIGHASH_SINGLE', () { - txb.addInput(txHash, 0); - txb.sign(vin: 0, keyPair: keyPair, hashType: SIGHASH_SINGLE); - try { - expect(txb.addOutput(scripts.elementAt(0), 2000), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, - 'No, this would invalidate signatures'); - } - }); - test( - 'throws if SIGHASH_ALL has been used to sign any existing scriptSigs', - () { - txb.addInput(txHash, 0); - txb.addOutput(scripts.elementAt(0), 2000); - txb.sign(vin: 0, keyPair: keyPair); - try { - expect(txb.addOutput(scripts.elementAt(1), 9000), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, - 'No, this would invalidate signatures'); - } - }); - }); - group('setLockTime', () { - test('throws if if there exist any scriptSigs', () { - final txb = new TransactionBuilder(); - txb.addInput(txHash, 0); - txb.addOutput(scripts.elementAt(0), 100); - txb.sign(vin: 0, keyPair: keyPair); - try { - expect(txb.setLockTime(65535), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, - 'No, this would invalidate signatures'); - } - }); - }); - group('sign', () { - fixtures['invalid']['sign'] as List - ..forEach((f) { - test( - 'throws ${f['exception']} ${f['description'] != null ? f['description'] : ''}', - () { - final txb = construct(f, true); - var threw = false; - final inputs = f['inputs'] as List; - for (var i = 0; i < inputs.length; i++) { - inputs[i]['signs'] as List - ..forEach((sign) { - final keyPairNetwork = - NETWORKS[sign['network'] ?? f['network']]; - final keyPair2 = - ECPair.fromWIF(sign['keyPair'], network: keyPairNetwork); - if (sign['throws'] != null && sign['throws']) { - try { - expect( - txb.sign( - vin: i, - keyPair: keyPair2, - hashType: sign['hashType']), - isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, f['exception']); - } - threw = true; - } else { - txb.sign( - vin: i, keyPair: keyPair2, hashType: sign['hashType']); - } - }); - } - expect(threw, true); - }); - }); - }); - group('build', () { - fixtures['valid']['build'] as List - ..forEach((f) { - test('builds ${f['description']}', () { - final txb = construct(f); - final tx = - f['incomplete'] != null ? txb.buildIncomplete() : txb.build(); - - expect(tx.toHex(), f['txHex']); - }); - }); - fixtures['invalid']['build'] as List - ..forEach((f) { - group('for ${f['description'] ?? f['exception']}', () { - test('throws ${f['exception']}', () { - try { - TransactionBuilder txb; - if (f['txHex'] != null) { - txb = TransactionBuilder.fromTransaction( - Transaction.fromHex(f['txHex'])); - } else { - txb = construct(f); - } - expect(txb.build(), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, f['exception']); - } - }); - // if throws on incomplete too, enforce that - if (f['incomplete'] != null && f['incomplete']) { - test('throws ${f['exception']}', () { - try { - TransactionBuilder txb; - if (f['txHex'] != null) { - txb = TransactionBuilder.fromTransaction( - Transaction.fromHex(f['txHex'])); - } else { - txb = construct(f); - } - expect(txb.buildIncomplete(), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, f['exception']); - } - }); - } else { - test('does not throw if buildIncomplete', () { - TransactionBuilder txb; - if (f['txHex'] != null) { - txb = TransactionBuilder.fromTransaction( - Transaction.fromHex(f['txHex'])); - } else { - txb = construct(f); - } - txb.buildIncomplete(); - }); - } - }); - }); - }); - }); -} diff --git a/test/transaction_test.dart b/test/transaction_test.dart deleted file mode 100644 index f817c57..0000000 --- a/test/transaction_test.dart +++ /dev/null @@ -1,184 +0,0 @@ -import 'package:test/test.dart'; -import 'dart:io'; -import 'dart:convert'; -import 'package:hex/hex.dart'; -import 'dart:typed_data'; -import '../lib/src/utils/script.dart' as bscript; -import '../lib/src/transaction.dart'; - -main() { - final fixtures = json.decode(new File('test/fixtures/transaction.json') - .readAsStringSync(encoding: utf8)); - final valids = (fixtures['valid'] as List); - - group('Transaction', () { - group('fromBuffer/fromHex', () { - valids.forEach(importExport); - (fixtures['hashForSignature'] as List).forEach(importExport); - (fixtures['invalid']['fromBuffer'] as List).forEach((f) { - test('throws on ${f['exception']}', () { - try { - expect(Transaction.fromHex(f['hex']), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, f['exception']); - } - }); - }); - - test('.version should be interpreted as an int32le', () { - final txHex = 'ffffffff0000ffffffff'; - final tx = Transaction.fromHex(txHex); - expect(-1, tx.version); - }); - }); - - group('toBuffer/toHex', () { - valids.forEach((f) { - test('exports ${f['description']} (${f['id']})', () { - Transaction actual = fromRaw(f['raw'], false); - expect(actual.toHex(), f['hex']); - }); - if (f['whex'] != null && f['whex'] != '') { - test('exports ${f['description']} (${f['id']}) as witness', () { - Transaction actual = fromRaw(f['raw'], true); - expect(actual.toHex(), f['whex']); - }); - } - }); - }); - - group('weight/virtualSize', () { - test('computes virtual size', () { - valids.forEach((f) { - final txHex = - (f['whex'] != null && f['whex'] != '') ? f['whex'] : f['hex']; - final transaction = Transaction.fromHex(txHex); - expect(transaction.virtualSize(), f['virtualSize']); - }); - }); - }); - - group('addInput', () { - var prevTxHash; - setUp(() { - prevTxHash = HEX.decode( - 'ffffffff00ffff000000000000000000000000000000000000000000101010ff'); - }); - test('returns an index', () { - final tx = new Transaction(); - expect(tx.addInput(prevTxHash, 0), 0); - expect(tx.addInput(prevTxHash, 0), 1); - }); - 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].sequence, 0xffffffff); - }); - (fixtures['invalid']['addInput'] as List).forEach((f) { - test('throws on ' + f['exception'], () { - final tx = new Transaction(); - final hash = HEX.decode(f['hash']); - try { - expect(tx.addInput(hash, f['index']), isArgumentError); - } catch (err) { - expect((err as ArgumentError).message, f['exception']); - } - }); - }); - }); - - test('addOutput returns an index', () { - final tx = new Transaction(); - expect(tx.addOutput(new Uint8List(0), 0), 0); - expect(tx.addOutput(new Uint8List(0), 0), 1); - }); - - group('getHash/getId', () { - verify(f) { - test('should return the id for ${f['id']} (${f['description']})', () { - final txHex = - (f['whex'] != null && f['whex'] != '') ? f['whex'] : f['hex']; - final tx = Transaction.fromHex(txHex); - expect(HEX.encode(tx.getHash()), f['hash']); - expect(tx.getId(), f['id']); - }); - } - - valids.forEach(verify); - }); - - group('isCoinbase', () { - verify(f) { - test( - 'should return ${f['coinbase']} for ${f['id']} (${f['description']})', - () { - final tx = Transaction.fromHex(f['hex']); - expect(tx.isCoinbase(), f['coinbase']); - }); - } - - valids.forEach(verify); - }); - - group('hashForSignature', () { - (fixtures['hashForSignature'] as List).forEach((f) { - test( - 'should return ${f['hash']} for ${f['description'] != null ? 'case "' + f['description'] + '"' : f['script']}', - () { - final tx = Transaction.fromHex(f['txHex']); - final script = bscript.fromASM(f['script']); - expect( - HEX.encode(tx.hashForSignature(f['inIndex'], script, f['type'])), - f['hash']); - }); - }); - }); - }); -} - -importExport(dynamic f) { - final id = f['id'] ?? f['hash']; - final txHex = f['hex'] ?? f['txHex']; - test('imports ${f['description']} ($id)', () { - final actual = Transaction.fromHex(txHex); - expect(actual.toHex(), txHex); - }); -} - -Transaction fromRaw(raw, [isWitness]) { - final tx = new Transaction(); - tx.version = raw['version']; - tx.locktime = raw['locktime']; - - (raw['ins'] as List).asMap().forEach((indx, txIn) { - final txHash = HEX.decode(txIn['hash']); - var scriptSig; - - if (txIn['data'] != null) { - scriptSig = HEX.decode(txIn['data']); - } else if (txIn['script'] != null && txIn['script'] != '') { - scriptSig = bscript.fromASM(txIn['script']); - } - tx.addInput(txHash, txIn['index'], txIn['sequence'], scriptSig); - - if (isWitness) { - var witness = (txIn['witness'] as List) - .map((e) => HEX.decode(e.toString()) as Uint8List) - .toList(); - tx.setWitness(indx, witness); - } - }); - - (raw['outs'] as List).forEach((txOut) { - var script; - if (txOut['data'] != null) { - script = HEX.decode(txOut['data']); - } else if (txOut['script'] != null) { - script = bscript.fromASM(txOut['script']); - } - tx.addOutput(script, txOut['value']); - }); - - return tx; -}