From 74bf4ee6639602ffdd99d9684e7360a3b60f115b Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Sun, 12 Mar 2023 17:41:46 +0530 Subject: [PATCH 01/12] migrated to sound null safety --- lib/src/address.dart | 4 +- lib/src/bitcoin_flutter_base.dart | 60 ++++---- lib/src/classify.dart | 6 +- lib/src/ecpair.dart | 34 ++--- lib/src/models/networks.dart | 14 +- lib/src/payments/index.dart | 14 +- lib/src/payments/p2pk.dart | 10 +- lib/src/payments/p2pkh.dart | 24 +-- lib/src/payments/p2wpkh.dart | 34 ++--- lib/src/templates/pubkey.dart | 6 +- lib/src/templates/pubkeyhash.dart | 2 +- lib/src/templates/witnesspubkeyhash.dart | 2 +- lib/src/transaction.dart | 178 ++++++++++++----------- lib/src/transaction_builder.dart | 152 +++++++++---------- lib/src/utils/magic_hash.dart | 4 +- lib/src/utils/push_data.dart | 24 +-- lib/src/utils/script.dart | 50 +++---- lib/src/utils/varuint.dart | 4 +- pubspec.yaml | 3 +- test/ecpair_test.dart | 20 +-- test/integration/addresses_test.dart | 4 +- test/integration/bip32_test.dart | 8 +- test/payments/p2pkh_test.dart | 4 +- test/payments/p2wpkh_test.dart | 4 +- test/transaction_builder_test.dart | 32 ++-- test/transaction_test.dart | 20 +-- 26 files changed, 366 insertions(+), 351 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index c310f12..66d78a8 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; diff --git a/lib/src/bitcoin_flutter_base.dart b/lib/src/bitcoin_flutter_base.dart index 6de8167..406b7ec 100644 --- a/lib/src/bitcoin_flutter_base.dart +++ b/lib/src/bitcoin_flutter_base.dart @@ -12,65 +12,65 @@ import 'dart:convert'; /// Checks if you are awesome. Spoiler: you are. class HDWallet { - bip32.BIP32 _bip32; - P2PKH _p2pkh; - String seed; + bip32.BIP32? _bip32; + P2PKH? _p2pkh; + String? seed; NetworkType network; - String get privKey { + String? get privKey { if (_bip32 == null) return null; try { - return HEX.encode(_bip32.privateKey); + return HEX.encode(_bip32!.privateKey!); } catch (_) { return null; } } - String get pubKey => _bip32 != null ? HEX.encode(_bip32.publicKey) : null; + String? get pubKey => _bip32 != null ? HEX.encode(_bip32!.publicKey) : null; - String get base58Priv { + String? get base58Priv { if (_bip32 == null) return null; try { - return _bip32.toBase58(); + return _bip32!.toBase58(); } catch (_) { return null; } } - String get base58 => _bip32 != null ? _bip32.neutered().toBase58() : null; + String? get base58 => _bip32 != null ? _bip32!.neutered().toBase58() : null; - String get wif { + String? get wif { if (_bip32 == null) return null; try { - return _bip32.toWIF(); + return _bip32!.toWIF(); } catch (_) { return null; } } - String get address => _p2pkh != null ? _p2pkh.data.address : null; + String? get address => _p2pkh != null ? _p2pkh!.data.address : null; HDWallet( - {@required bip32, @required p2pkh, @required this.network, this.seed}) { + {required bip32, required p2pkh, required this.network, this.seed}) { this._bip32 = bip32; this._p2pkh = p2pkh; } HDWallet derivePath(String path) { - final bip32 = _bip32.derivePath(path); + final bip32 = _bip32!.derivePath(path); final p2pkh = new P2PKH( data: new PaymentData(pubkey: bip32.publicKey), network: network); return HDWallet(bip32: bip32, p2pkh: p2pkh, network: network); } HDWallet derive(int index) { - final bip32 = _bip32.derive(index); + final bip32 = _bip32!.derive(index); final p2pkh = new P2PKH( data: new PaymentData(pubkey: bip32.publicKey), network: network); return HDWallet(bip32: bip32, p2pkh: p2pkh, network: network); } - factory HDWallet.fromSeed(Uint8List seed, {NetworkType network}) { + factory HDWallet.fromSeed(Uint8List seed, {NetworkType? network}) { network = network ?? bitcoin; final seedHex = HEX.encode(seed); final wallet = bip32.BIP32.fromSeed( @@ -85,7 +85,7 @@ class HDWallet { bip32: wallet, p2pkh: p2pkh, network: network, seed: seedHex); } - factory HDWallet.fromBase58(String xpub, {NetworkType network}) { + factory HDWallet.fromBase58(String xpub, {NetworkType? network}) { network = network ?? bitcoin; final wallet = bip32.BIP32.fromBase58( xpub, @@ -98,14 +98,14 @@ class HDWallet { return HDWallet(bip32: wallet, p2pkh: p2pkh, network: network, seed: null); } - Uint8List sign(String message) { + 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); } } @@ -113,27 +113,27 @@ class Wallet { ECPair _keyPair; P2PKH _p2pkh; - String get privKey => - _keyPair != null ? HEX.encode(_keyPair.privateKey) : null; + String? get privKey => + _keyPair != null ? HEX.encode(_keyPair.privateKey!) : null; - String get pubKey => _keyPair != null ? HEX.encode(_keyPair.publicKey) : null; + String? get pubKey => _keyPair != null ? HEX.encode(_keyPair.publicKey!) : null; - String get wif => _keyPair != null ? _keyPair.toWIF() : null; + String? get wif => _keyPair != null ? _keyPair.toWIF() : null; - String get address => _p2pkh != null ? _p2pkh.data.address : null; + String? get address => _p2pkh != null ? _p2pkh.data.address : null; - NetworkType network; + NetworkType? network; Wallet(this._keyPair, this._p2pkh, this.network); - factory Wallet.random([NetworkType network]) { + factory Wallet.random([NetworkType? network]) { final _keyPair = ECPair.makeRandom(network: network); final _p2pkh = new P2PKH( data: new PaymentData(pubkey: _keyPair.publicKey), network: network); return Wallet(_keyPair, _p2pkh, network); } - factory Wallet.fromWIF(String wif, [NetworkType network]) { + factory Wallet.fromWIF(String wif, [NetworkType? network]) { network = network ?? bitcoin; final _keyPair = ECPair.fromWIF(wif, network: network); final _p2pkh = new P2PKH( @@ -146,7 +146,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..771e06c 100644 --- a/lib/src/classify.dart +++ b/lib/src/classify.dart @@ -16,7 +16,7 @@ const SCRIPT_TYPES = { 'WITNESS_COMMITMENT': 'witnesscommitment' }; -String classifyOutput(Uint8List script) { +String? classifyOutput(Uint8List script) { if (witnessPubKeyHash.outputCheck(script)) return SCRIPT_TYPES['P2WPKH']; if (pubkeyhash.outputCheck(script)) return SCRIPT_TYPES['P2PKH']; final chunks = bscript.decompile(script); @@ -24,7 +24,7 @@ String classifyOutput(Uint8List script) { return SCRIPT_TYPES['NONSTANDARD']; } -String classifyInput(Uint8List script) { +String? classifyInput(Uint8List? script) { final chunks = bscript.decompile(script); if (chunks == null) throw new ArgumentError('Invalid script'); if (pubkeyhash.inputCheck(chunks)) return SCRIPT_TYPES['P2PKH']; @@ -32,7 +32,7 @@ String classifyInput(Uint8List script) { return SCRIPT_TYPES['NONSTANDARD']; } -String classifyWitness(List script) { +String? classifyWitness(List script) { final chunks = bscript.decompile(script); if (chunks == null) throw new ArgumentError('Invalid script'); if (witnessPubKeyHash.inputCheck(chunks)) return SCRIPT_TYPES['P2WPKH']; diff --git a/lib/src/ecpair.dart b/lib/src/ecpair.dart index fc8ce89..afc23d7 100644 --- a/lib/src/ecpair.dart +++ b/lib/src/ecpair.dart @@ -5,39 +5,39 @@ 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}) { + Uint8List? _d; + Uint8List? _Q; + NetworkType? network; + bool? compressed; + ECPair(Uint8List? _d, Uint8List? _Q, {network, compressed}) { this._d = _d; this._Q = _Q; this.network = network ?? bitcoin; this.compressed = compressed ?? true; } - Uint8List get publicKey { - if (_Q == null) _Q = ecc.pointFromScalar(_d, compressed); + Uint8List? get publicKey { + if (_Q == null) _Q = ecc.pointFromScalar(_d!, compressed!); return _Q; } - Uint8List get privateKey => _d; + Uint8List? get privateKey => _d; String toWIF() { if (privateKey == null) { throw new ArgumentError('Missing private key'); } return wif.encode(new wif.WIF( - version: network.wif, privateKey: privateKey, compressed: compressed)); + version: network!.wif, privateKey: privateKey!, compressed: compressed!)); } Uint8List sign(Uint8List hash) { - return ecc.sign(hash, privateKey); + return ecc.sign(hash, privateKey!); } bool verify(Uint8List hash, Uint8List signature) { - return ecc.verify(hash, publicKey, signature); + return ecc.verify(hash, publicKey!, signature); } - factory ECPair.fromWIF(String w, {NetworkType network}) { + factory ECPair.fromWIF(String w, {NetworkType? network}) { wif.WIF decoded = wif.decode(w); final version = decoded.version; // TODO support multi networks @@ -58,7 +58,7 @@ class ECPair { compressed: decoded.compressed, network: nw); } factory ECPair.fromPublicKey(Uint8List publicKey, - {NetworkType network, bool compressed}) { + {NetworkType? network, bool? compressed}) { if (!ecc.isPoint(publicKey)) { throw new ArgumentError('Point is not on the curve'); } @@ -66,7 +66,7 @@ class ECPair { network: network, compressed: compressed); } factory ECPair.fromPrivateKey(Uint8List privateKey, - {NetworkType network, bool compressed}) { + {NetworkType? network, bool? compressed}) { if (privateKey.length != 32) throw new ArgumentError( 'Expected property privateKey of type Buffer(Length: 32)'); @@ -76,13 +76,13 @@ class ECPair { network: network, compressed: compressed); } factory ECPair.makeRandom( - {NetworkType network, bool compressed, Function rng}) { + {NetworkType? network, bool? compressed, Function? rng}) { final rfunc = rng ?? _randomBytes; - Uint8List d; + Uint8List? d; // int beginTime = DateTime.now().millisecondsSinceEpoch; do { d = rfunc(32); - if (d.length != 32) throw ArgumentError('Expected Buffer(Length: 32)'); + if (d!.length != 32) throw ArgumentError('Expected Buffer(Length: 32)'); // if (DateTime.now().millisecondsSinceEpoch - beginTime > 5000) throw ArgumentError('Timeout'); } while (!ecc.isPrivate(d)); return ECPair.fromPrivateKey(d, network: network, compressed: compressed); diff --git a/lib/src/models/networks.dart b/lib/src/models/networks.dart index dc4771d..a70ac72 100644 --- a/lib/src/models/networks.dart +++ b/lib/src/models/networks.dart @@ -2,19 +2,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 +26,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..b7f41cb 100644 --- a/lib/src/payments/index.dart +++ b/lib/src/payments/index.dart @@ -1,13 +1,13 @@ import 'dart:typed_data'; class PaymentData { - String address; - Uint8List hash; - Uint8List output; - Uint8List signature; - Uint8List pubkey; - Uint8List input; - List witness; + String? address; + Uint8List? hash; + Uint8List? output; + Uint8List? signature; + Uint8List? pubkey; + Uint8List? input; + List? witness; PaymentData( {this.address, diff --git a/lib/src/payments/p2pk.dart b/lib/src/payments/p2pk.dart index 796d6dd..7f6ead1 100644 --- a/lib/src/payments/p2pk.dart +++ b/lib/src/payments/p2pk.dart @@ -10,9 +10,9 @@ import '../utils/script.dart' as bscript; import '../utils/constants/op.dart'; class P2PK { - PaymentData data; - NetworkType network; - P2PK({@required data, network}) { + late PaymentData data; + NetworkType? network; + P2PK({required data, network}) { this.network = network ?? bitcoin; this.data = data; _init(); @@ -20,9 +20,9 @@ class P2PK { _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..8f9f96f 100644 --- a/lib/src/payments/p2pkh.dart +++ b/lib/src/payments/p2pkh.dart @@ -10,30 +10,30 @@ import '../utils/script.dart' as bscript; import '../utils/constants/op.dart'; class P2PKH { - PaymentData data; - NetworkType network; - P2PKH({@required data, network}) { + late PaymentData data; + late NetworkType network; + P2PKH({required 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)) + if (!isValidOutput(data.output!)) throw new ArgumentError('Output is invalid'); - data.hash = data.output.sublist(3, 23); + data.hash = data.output!.sublist(3, 23); _getDataFromHash(); } else if (data.pubkey != null) { - data.hash = hash160(data.pubkey); + data.hash = hash160(data.pubkey!); _getDataFromHash(); _getDataFromChunk(); } else if (data.input != null) { - List _chunks = bscript.decompile(data.input); + List _chunks = bscript.decompile(data.input)!; _getDataFromChunk(_chunks); if (_chunks.length != 2) throw new ArgumentError('Input is invalid'); if (!bscript.isCanonicalScriptSignature(_chunks[0])) @@ -45,12 +45,12 @@ class P2PKH { } } - void _getDataFromChunk([List _chunks]) { + void _getDataFromChunk([List? _chunks]) { if (data.pubkey == null && _chunks != null) { data.pubkey = (_chunks[1] is int) ? new Uint8List.fromList([_chunks[1]]) : _chunks[1]; - data.hash = hash160(data.pubkey); + data.hash = hash160(data.pubkey!); _getDataFromHash(); } if (data.signature == null && _chunks != null) @@ -66,7 +66,7 @@ class P2PKH { if (data.address == null) { final payload = new Uint8List(21); payload.buffer.asByteData().setUint8(0, network.pubKeyHash); - payload.setRange(1, payload.length, data.hash); + payload.setRange(1, payload.length, data.hash!); data.address = bs58check.encode(payload); } if (data.output == null) { @@ -86,7 +86,7 @@ class P2PKH { if (version != network.pubKeyHash) throw new ArgumentError('Invalid version or Network mismatch'); data.hash = payload.sublist(1); - if (data.hash.length != 20) throw new ArgumentError('Invalid address'); + if (data.hash!.length != 20) throw new ArgumentError('Invalid address'); } } diff --git a/lib/src/payments/p2wpkh.dart b/lib/src/payments/p2wpkh.dart index 04cfbe2..8653d3f 100644 --- a/lib/src/payments/p2wpkh.dart +++ b/lib/src/payments/p2wpkh.dart @@ -12,9 +12,9 @@ import '../utils/constants/op.dart'; class P2WPKH { final EMPTY_SCRIPT = Uint8List.fromList([]); - PaymentData data; - NetworkType network; - P2WPKH({@required data, network}) { + late PaymentData data; + late NetworkType network; + P2WPKH({required data, network}) { this.network = network ?? bitcoin; this.data = data; _init(); @@ -28,7 +28,7 @@ class P2WPKH { data.witness == null) throw new ArgumentError('Not enough data'); if (data.address != null) { - _getDataFromAddress(data.address); + _getDataFromAddress(data.address!); } if (data.hash != null) { @@ -36,27 +36,27 @@ class P2WPKH { } if (data.output != null) { - if (data.output.length != 22 || - data.output[0] != OPS['OP_0'] || - data.output[1] != 20) // 0x14 + if (data.output!.length != 22 || + data.output![0] != OPS['OP_0'] || + data.output![1] != 20) // 0x14 throw new 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) + if (data.witness!.length != 2) throw new ArgumentError('Witness is invalid'); - if (!bscript.isCanonicalScriptSignature(data.witness[0])) + if (!bscript.isCanonicalScriptSignature(data.witness![0]!)) throw new ArgumentError('Witness has invalid signature'); - if (!isPoint(data.witness[1])) + if (!isPoint(data.witness![1]!)) throw new ArgumentError('Witness has invalid pubkey'); _getDataFromWitness(data.witness); } else if (data.pubkey != null && data.signature != null) { @@ -65,23 +65,23 @@ class P2WPKH { } } - void _getDataFromWitness([List witness]) { + void _getDataFromWitness([List? witness]) { if (data.input == null) { data.input = EMPTY_SCRIPT; } if (data.pubkey == null) { - data.pubkey = witness[1]; + data.pubkey = witness![1]; if (data.hash == null) { - data.hash = hash160(data.pubkey); + data.hash = hash160(data.pubkey!); } _getDataFromHash(); } - if (data.signature == null) data.signature = witness[0]; + if (data.signature == null) data.signature = witness![0]; } void _getDataFromHash() { if (data.address == null) { - data.address = segwit.encode(Segwit(network.bech32, 0, data.hash)); + data.address = segwit.encode(Segwit(network.bech32!, 0, data.hash!)); } if (data.output == null) { data.output = bscript.compile([OPS['OP_0'], data.hash]); diff --git a/lib/src/templates/pubkey.dart b/lib/src/templates/pubkey.dart index c839e85..d74594f 100644 --- a/lib/src/templates/pubkey.dart +++ b/lib/src/templates/pubkey.dart @@ -6,6 +6,6 @@ bool inputCheck(List chunks) { return chunks.length == 1 && bscript.isCanonicalScriptSignature(chunks[0]); } -bool outputCheck(Uint8List script) { - // TODO -} +// bool outputCheck(Uint8List script) { +// // TODO +// } diff --git a/lib/src/templates/pubkeyhash.dart b/lib/src/templates/pubkeyhash.dart index 88af094..bdcf096 100644 --- a/lib/src/templates/pubkeyhash.dart +++ b/lib/src/templates/pubkeyhash.dart @@ -10,7 +10,7 @@ bool inputCheck(List chunks) { } bool outputCheck(Uint8List script) { - final buffer = bscript.compile(script); + final buffer = bscript.compile(script)!; return buffer.length == 25 && buffer[0] == OPS['OP_DUP'] && buffer[1] == OPS['OP_HASH160'] && diff --git a/lib/src/templates/witnesspubkeyhash.dart b/lib/src/templates/witnesspubkeyhash.dart index ec660d7..200a693 100644 --- a/lib/src/templates/witnesspubkeyhash.dart +++ b/lib/src/templates/witnesspubkeyhash.dart @@ -10,6 +10,6 @@ bool inputCheck(List chunks) { } bool outputCheck(Uint8List script) { - final buffer = bscript.compile(script); + final buffer = bscript.compile(script)!; return buffer.length == 22 && buffer[0] == OPS['OP_0'] && buffer[1] == 0x14; } diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 61f8756..8867543 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; +import 'package:collection/collection.dart' show IterableExtension; import 'package:hex/hex.dart'; import 'payments/index.dart' show PaymentData; import 'payments/p2pkh.dart' show P2PKH; @@ -19,23 +20,24 @@ const SIGHASH_ANYONECANPAY = 0x80; const ADVANCED_TRANSACTION_MARKER = 0x00; const ADVANCED_TRANSACTION_FLAG = 0x01; final EMPTY_SCRIPT = Uint8List.fromList([]); -final EMPTY_WITNESS = new List(); +final List? EMPTY_WITNESS = []; final ZERO = HEX .decode('0000000000000000000000000000000000000000000000000000000000000000'); final ONE = HEX .decode('0000000000000000000000000000000000000000000000000000000000000001'); final VALUE_UINT64_MAX = HEX.decode('ffffffffffffffff'); -final BLANK_OUTPUT = - new Output(script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX); +final BLANK_OUTPUT = new Output( + script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX as Uint8List?); class Transaction { - int version = 1; - int locktime = 0; + int? version = 1; + int? locktime = 0; List ins = []; List outs = []; Transaction(); - int addInput(Uint8List hash, int index, [int sequence, Uint8List scriptSig]) { + int addInput(Uint8List hash, int? index, + [int? sequence, Uint8List? scriptSig]) { ins.add(new Input( hash: hash, index: index, @@ -45,23 +47,22 @@ class Transaction { return ins.length - 1; } - int addOutput(Uint8List scriptPubKey, int value) { + 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); + var witness = ins.firstWhereOrNull( + (input) => input.witness != null && input.witness!.length != 0); return witness != null; } - setInputScript(int index, Uint8List scriptSig) { + setInputScript(int index, Uint8List? scriptSig) { ins[index].script = scriptSig; } - setWitness(int index, List witness) { + setWitness(int index, List? witness) { ins[index].witness = witness; } @@ -77,8 +78,8 @@ class Transaction { var hashSequence = ZERO; writeSlice(slice) { - tbuffer.setRange(toffset, toffset + slice.length, slice); - toffset += slice.length; + tbuffer.setRange(toffset, toffset + slice.length as int, slice); + toffset += slice.length as int; } writeUInt8(i) { @@ -144,8 +145,8 @@ class Transaction { if ((hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { - var txOutsSize = - outs.fold(0, (sum, output) => sum + 8 + varSliceSize(output.script)); + var txOutsSize = outs.fold( + 0, (dynamic sum, output) => sum + 8 + varSliceSize(output.script!)); tbuffer = new Uint8List(txOutsSize); bytes = tbuffer.buffer.asByteData(); toffset = 0; @@ -157,7 +158,7 @@ class Transaction { } else if ((hashType & 0x1f) == SIGHASH_SINGLE && inIndex < outs.length) { // SIGHASH_SINGLE only hash that according output var output = outs[inIndex]; - tbuffer = new Uint8List(8 + varSliceSize(output.script)); + tbuffer = new Uint8List(8 + varSliceSize(output.script!)); bytes = tbuffer.buffer.asByteData(); toffset = 0; writeUInt64(output.value); @@ -184,16 +185,16 @@ class Transaction { return bcrypto.hash256(tbuffer); } - hashForSignature(int inIndex, Uint8List prevOutScript, int hashType) { + 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) { + 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) { + if ((hashType! & 0x1f) == SIGHASH_NONE) { txTmp.outs = []; // ignore sequence numbers (except at inIndex) for (var i = 0; i < txTmp.ins.length; i++) { @@ -248,20 +249,23 @@ class Transaction { return (hasWitness ? 10 : 8) + varuint.encodingLength(ins.length) + varuint.encodingLength(outs.length) + - ins.fold(0, (sum, input) => sum + 40 + varSliceSize(input.script)) + - outs.fold(0, (sum, output) => sum + 8 + varSliceSize(output.script)) + + ins.fold(0, (sum, input) => sum + 40 + varSliceSize(input.script!)) + + outs.fold(0, (sum, output) => sum + 8 + varSliceSize(output.script!)) + (hasWitness - ? ins.fold(0, (sum, input) => sum + vectorSize(input.witness)) + ? ins.fold(0, (sum, input) => sum + vectorSize(input.witness!)) : 0); } - int vectorSize(List someVector) { + int vectorSize(List someVector) { var length = someVector.length; return varuint.encodingLength(length) + - someVector.fold(0, (sum, witness) => sum + varSliceSize(witness)); + someVector.fold( + 0, + ((sum, witness) => sum + varSliceSize(witness!) as int) as int + Function(int, Uint8List?)); } - int weight() { + int? weight() { var base = _byteLength(false); var total = _byteLength(true); return base * 3 + total; @@ -272,10 +276,10 @@ class Transaction { } int virtualSize() { - return (weight() / 4).ceil(); + return (weight()! / 4).ceil(); } - Uint8List toBuffer([Uint8List buffer, int initialOffset]) { + Uint8List toBuffer([Uint8List? buffer, int? initialOffset]) { return this._toBuffer(buffer, initialOffset, true); } @@ -304,7 +308,7 @@ class Transaction { return HEX.encode(getHash().reversed.toList()); } - _toBuffer([Uint8List buffer, initialOffset, bool _ALLOW_WITNESS = false]) { + _toBuffer([Uint8List? buffer, initialOffset, bool _ALLOW_WITNESS = false]) { // _ALLOW_WITNESS is used to separate witness part when calculating tx id if (buffer == null) buffer = new Uint8List(_byteLength(_ALLOW_WITNESS)); @@ -314,7 +318,7 @@ class Transaction { var offset = initialOffset ?? 0; writeSlice(slice) { - buffer.setRange(offset, offset + slice.length, slice); + buffer!.setRange(offset, offset + slice.length, slice); offset += slice.length; } @@ -445,8 +449,8 @@ class Transaction { } Uint8List readSlice(n) { - offset += n; - return buffer.sublist(offset - n, offset); + offset += n as int; + return buffer.sublist(offset - n as int, offset); } int readVarInt() { @@ -517,35 +521,35 @@ class Transaction { bool noStrict = false, }) { return Transaction.fromBuffer( - HEX.decode(hex), + HEX.decode(hex) as Uint8List, noStrict: noStrict, ); } - @override - String toString() { - this.ins.forEach((txInput) { - print(txInput.toString()); - }); - this.outs.forEach((txOutput) { - print(txOutput.toString()); - }); - } + // @override + // 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; + Uint8List? hash; + int? index; + int? sequence; + int? value; + Uint8List? script; + Uint8List? signScript; + Uint8List? prevOutScript; + String? prevOutType; + late bool hasWitness; + List? pubkeys; + List? signatures; + List? witness; Input( {this.hash, @@ -559,18 +563,18 @@ class Input { this.witness, this.prevOutType}) { this.hasWitness = false; // Default value - if (this.hash != null && !isHash256bit(this.hash)) + if (this.hash != null && !isHash256bit(this.hash!)) throw new ArgumentError('Invalid input hash'); - if (this.index != null && !isUint(this.index, 32)) + if (this.index != null && !isUint(this.index!, 32)) throw new ArgumentError('Invalid input index'); - if (this.sequence != null && !isUint(this.sequence, 32)) + if (this.sequence != null && !isUint(this.sequence!, 32)) throw new ArgumentError('Invalid input sequence'); - if (this.value != null && !isShatoshi(this.value)) + if (this.value != null && !isShatoshi(this.value!)) throw ArgumentError('Invalid ouput value'); } - factory Input.expandInput(Uint8List scriptSig, List witness, - [String type, Uint8List scriptPubKey]) { + factory Input.expandInput(Uint8List? scriptSig, List witness, + [String? type, Uint8List? scriptPubKey]) { if (type == null || type == '') { var ssType = classifyInput(scriptSig); var wsType = classifyWitness(witness); @@ -599,25 +603,28 @@ class Input { pubkeys: [], signatures: [p2pk.data.signature]); } + throw Exception("conditions failed!"); } factory Input.clone(Input input) { return new Input( - hash: input.hash != null ? Uint8List.fromList(input.hash) : null, + hash: input.hash != null ? Uint8List.fromList(input.hash!) : null, index: input.index, - script: input.script != null ? Uint8List.fromList(input.script) : null, + script: input.script != null ? Uint8List.fromList(input.script!) : null, sequence: input.sequence, value: input.value, prevOutScript: input.prevOutScript != null - ? Uint8List.fromList(input.prevOutScript) + ? Uint8List.fromList(input.prevOutScript!) : null, pubkeys: input.pubkeys != null - ? input.pubkeys.map( - (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) + ? input.pubkeys!.map((pubkey) => + pubkey != null ? Uint8List.fromList(pubkey) : null) + as List? : null, signatures: input.signatures != null - ? input.signatures.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) + ? input.signatures!.map((signature) => + signature != null ? Uint8List.fromList(signature) : null) + as List? : null, ); } @@ -629,11 +636,11 @@ class Input { } class Output { - Uint8List script; - int value; - Uint8List valueBuffer; - List pubkeys; - List signatures; + Uint8List? script; + int? value; + Uint8List? valueBuffer; + List? pubkeys; + List? signatures; Output( {this.script, @@ -641,42 +648,45 @@ class Output { this.pubkeys, this.signatures, this.valueBuffer}) { - if (value != null && !isShatoshi(value)) + if (value != null && !isShatoshi(value!)) throw ArgumentError('Invalid ouput value'); } - factory Output.expandOutput(Uint8List script, [Uint8List ourPubKey]) { + factory Output.expandOutput(Uint8List? script, [Uint8List? ourPubKey]) { if (ourPubKey == null) return new Output(); - var type = classifyOutput(script); + var type = classifyOutput(script!); if (type == SCRIPT_TYPES['P2WPKH']) { - Uint8List wpkh1 = + 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 = + 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]); } + throw Exception("conditions failed!"); } factory Output.clone(Output output) { return new Output( - script: output.script != null ? Uint8List.fromList(output.script) : null, + script: output.script != null ? Uint8List.fromList(output.script!) : null, value: output.value, valueBuffer: output.valueBuffer != null - ? Uint8List.fromList(output.valueBuffer) + ? Uint8List.fromList(output.valueBuffer!) : null, pubkeys: output.pubkeys != null - ? output.pubkeys.map( - (pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) + ? output.pubkeys!.map((pubkey) => + pubkey != null ? Uint8List.fromList(pubkey) : null) + as List? : null, signatures: output.signatures != null - ? output.signatures.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) + ? output.signatures!.map((signature) => + signature != null ? Uint8List.fromList(signature) : null) + as List? : null, ); } @@ -704,7 +714,7 @@ bool _isP2PKHInput(script) { } bool _isP2PKHOutput(script) { - final buffer = bscript.compile(script); + final buffer = bscript.compile(script)!; return buffer.length == 25 && buffer[0] == OPS['OP_DUP'] && buffer[1] == OPS['OP_HASH160'] && diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart index 7c76f55..768a76e 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -18,28 +18,28 @@ import 'classify.dart'; const int MAX_OP_RETURN_SIZE = 100; class TransactionBuilder { - NetworkType network; - int maximumFeeRate; - List _inputs; - Transaction _tx; + NetworkType? network; + late int maximumFeeRate; + List? _inputs; + Transaction? _tx; Map _prevTxSet = {}; - TransactionBuilder({NetworkType network, int maximumFeeRate}) { + TransactionBuilder({NetworkType? network, int? maximumFeeRate}) { this.network = network ?? bitcoin; this.maximumFeeRate = maximumFeeRate ?? 2500; this._inputs = []; this._tx = new Transaction(); - this._tx.version = 2; + this._tx!.version = 2; } - List get inputs => _inputs; + List? get inputs => _inputs; factory TransactionBuilder.fromTransaction(Transaction transaction, - [NetworkType network]) { + [NetworkType? network]) { final txb = new TransactionBuilder(network: network); // Copy transaction fields - txb.setVersion(transaction.version); - txb.setLockTime(transaction.locktime); + txb.setVersion(transaction.version!); + txb.setLockTime(transaction.locktime!); // Copy outputs (done first to avoid signature invalidation) transaction.outs.forEach((txOut) { @@ -48,7 +48,7 @@ class TransactionBuilder { transaction.ins.forEach((txIn) { txb._addInputUnsafe( - txIn.hash, + txIn.hash!, txIn.index, new Input( sequence: txIn.sequence, @@ -68,25 +68,25 @@ class TransactionBuilder { setVersion(int version) { if (version < 0 || version > 0xFFFFFFFF) throw ArgumentError('Expected Uint32'); - _tx.version = version; + _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 (this._inputs!.map((input) { if (input.signatures == null) return false; - return input.signatures.map((s) { + return input.signatures!.map((s) { return s != null; }).contains(true); }).contains(true)) { throw new ArgumentError('No, this would invalidate signatures'); } - _tx.locktime = locktime; + _tx!.locktime = locktime; } - int addOutput(dynamic data, int value) { + int addOutput(dynamic data, int? value) { var scriptPubKey; if (data is String) { scriptPubKey = Address.addressToOutputScript(data, this.network); @@ -98,7 +98,7 @@ class TransactionBuilder { if (!_canModifyOutputs()) { throw new ArgumentError('No, this would invalidate signatures'); } - return _tx.addOutput(scriptPubKey, value); + return _tx!.addOutput(scriptPubKey, value); } int addOutputData(dynamic data) { @@ -117,11 +117,11 @@ class TransactionBuilder { if (!_canModifyOutputs()) { throw new ArgumentError('No, this would invalidate signatures'); } - return _tx.addOutput(scriptPubKey, 0); + return _tx!.addOutput(scriptPubKey, 0); } - int addInput(dynamic txHash, int vout, - [int sequence, Uint8List prevOutScript]) { + int addInput(dynamic txHash, int? vout, + [int? sequence, Uint8List? prevOutScript]) { if (!_canModifyInputs()) { throw new ArgumentError('No, this would invalidate signatures'); } @@ -132,7 +132,7 @@ class TransactionBuilder { } else if (txHash is Uint8List) { hash = txHash; } else if (txHash is Transaction) { - final txOut = txHash.outs[vout]; + final txOut = txHash.outs[vout!]; prevOutScript = txOut.script; value = txOut.value; hash = txHash.getHash(); @@ -147,21 +147,21 @@ class TransactionBuilder { } sign( - {@required int vin, - @required ECPair keyPair, - Uint8List redeemScript, - int witnessValue, - Uint8List witnessScript, - int hashType}) { + {required int vin, + required ECPair keyPair, + Uint8List? redeemScript, + int? witnessValue, + Uint8List? witnessScript, + int? hashType}) { if (keyPair.network != null && keyPair.network.toString().compareTo(network.toString()) != 0) throw new ArgumentError('Inconsistent network'); - if (vin >= _inputs.length) + 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 input = _inputs![vin]; final ourPubKey = keyPair.publicKey; if (!_canSign(input)) { if (witnessValue != null) { @@ -177,7 +177,7 @@ class TransactionBuilder { // TODO } if (input.prevOutScript != null && input.prevOutType != null) { - var type = classifyOutput(input.prevOutScript); + var type = classifyOutput(input.prevOutScript!); if (type == SCRIPT_TYPES['P2WPKH']) { input.prevOutType = SCRIPT_TYPES['P2WPKH']; input.hasWitness = true; @@ -190,14 +190,14 @@ class TransactionBuilder { .output; } else { // DRY CODE - Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); + Uint8List? prevOutScript = pubkeyToOutputScript(ourPubKey); input.prevOutType = SCRIPT_TYPES['P2PKH']; input.signatures = [null]; input.pubkeys = [ourPubKey]; input.signScript = prevOutScript; } } else { - Uint8List prevOutScript = pubkeyToOutputScript(ourPubKey); + Uint8List? prevOutScript = pubkeyToOutputScript(ourPubKey); input.prevOutType = SCRIPT_TYPES['P2PKH']; input.signatures = [null]; input.pubkeys = [ourPubKey]; @@ -207,22 +207,22 @@ class TransactionBuilder { var signatureHash; if (input.hasWitness) { signatureHash = this - ._tx - .hashForWitnessV0(vin, input.signScript, input.value, hashType); + ._tx! + .hashForWitnessV0(vin, input.signScript!, input.value!, hashType); } else { signatureHash = - this._tx.hashForSignature(vin, input.signScript, hashType); + this._tx!.hashForSignature(vin, input.signScript, hashType); } // enforce in order signing of public keys var signed = false; - for (var i = 0; i < input.pubkeys.length; i++) { - if (HEX.encode(ourPubKey).compareTo(HEX.encode(input.pubkeys[i])) != 0) + for (var i = 0; i < input.pubkeys!.length; i++) { + if (HEX.encode(ourPubKey!).compareTo(HEX.encode(input.pubkeys![i]!)) != 0) continue; - if (input.signatures[i] != null) + if (input.signatures![i] != null) throw new ArgumentError('Signature already exists'); final signature = keyPair.sign(signatureHash); - input.signatures[i] = bscript.encodeSignature(signature, hashType); + input.signatures![i] = bscript.encodeSignature(signature, hashType); signed = true; } if (!signed) throw new ArgumentError('Key pair cannot sign for this input'); @@ -238,32 +238,32 @@ class TransactionBuilder { Transaction _build(bool allowIncomplete) { if (!allowIncomplete) { - if (_tx.ins.length == 0) + if (_tx!.ins.length == 0) throw new ArgumentError('Transaction has no inputs'); - if (_tx.outs.length == 0) + if (_tx!.outs.length == 0) throw new ArgumentError('Transaction has no outputs'); } - final tx = Transaction.clone(_tx); + 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']) { + 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]), + 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']) { + } else if (_inputs![i].prevOutType == SCRIPT_TYPES['P2WPKH']) { P2WPKH payment = new P2WPKH( data: new PaymentData( - pubkey: _inputs[i].pubkeys[0], - signature: _inputs[i].signatures[0]), + pubkey: _inputs![i].pubkeys![0], + signature: _inputs![i].signatures![0]), network: network); tx.setInputScript(i, payment.data.input); tx.setWitness(i, payment.data.witness); @@ -284,17 +284,17 @@ class TransactionBuilder { } 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 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) { + return _inputs!.every((input) { if (input.signatures == null) return true; - return input.signatures.every((signature) { + return input.signatures!.every((signature) { if (signature == null) return true; return _signatureHashType(signature) & SIGHASH_ANYONECANPAY != 0; }); @@ -302,11 +302,11 @@ class TransactionBuilder { } bool _canModifyOutputs() { - final nInputs = _tx.ins.length; - final nOutputs = _tx.outs.length; - return _inputs.every((input) { + 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) { + return input.signatures!.every((signature) { if (signature == null) return true; final hashType = _signatureHashType(signature); final hashTypeMod = hashType & 0x1f; @@ -324,15 +324,15 @@ class TransactionBuilder { bool _needsOutputs(int signingHashType) { if (signingHashType == SIGHASH_ALL) { - return this._tx.outs.length == 0; + 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 (this._tx!.outs.length == 0) && + _inputs!.map((input) { + if (input.signatures == null || input.signatures!.length == 0) return false; - return input.signatures.map((signature) { + return input.signatures!.map((signature) { if (signature == null) return false; // no signature, no issue final hashType = _signatureHashType(signature); if (hashType & SIGHASH_NONE != 0) @@ -346,11 +346,11 @@ class TransactionBuilder { return input.pubkeys != null && input.signScript != null && input.signatures != null && - input.signatures.length == input.pubkeys.length && - input.pubkeys.length > 0; + input.signatures!.length == input.pubkeys!.length && + input.pubkeys!.length > 0; } - _addInputUnsafe(Uint8List hash, int vout, Input options) { + _addInputUnsafe(Uint8List hash, int? vout, Input options) { String txHash = HEX.encode(hash); Input input; if (isCoinbaseHash(hash)) { @@ -361,7 +361,7 @@ class TransactionBuilder { throw new ArgumentError('Duplicate TxOut: ' + prevTxOut); if (options.script != null) { input = - Input.expandInput(options.script, options.witness ?? EMPTY_WITNESS); + Input.expandInput(options.script, options.witness ?? EMPTY_WITNESS!); } else { input = new Input(); } @@ -369,16 +369,16 @@ class TransactionBuilder { 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) { + if (expanded.pubkeys != null && !expanded.pubkeys!.isEmpty) { input.pubkeys = expanded.pubkeys; input.signatures = expanded.signatures; } } input.prevOutScript = options.prevOutScript; - input.prevOutType = classifyOutput(options.prevOutScript); + input.prevOutType = classifyOutput(options.prevOutScript!); } - int vin = _tx.addInput(hash, vout, options.sequence, options.script); - _inputs.add(input); + int vin = _tx!.addInput(hash, vout, options.sequence, options.script); + _inputs!.add(input); _prevTxSet[prevTxOut] = true; return vin; } @@ -387,12 +387,12 @@ class TransactionBuilder { return buffer.buffer.asByteData().getUint8(buffer.length - 1); } - Transaction get tx => _tx; + Transaction? get tx => _tx; Map get prevTxSet => _prevTxSet; } -Uint8List pubkeyToOutputScript(Uint8List pubkey, [NetworkType nw]) { +Uint8List? pubkeyToOutputScript(Uint8List? pubkey, [NetworkType? nw]) { NetworkType network = nw ?? bitcoin; P2PKH p2pkh = new P2PKH(data: new PaymentData(pubkey: pubkey), network: network); diff --git a/lib/src/utils/magic_hash.dart b/lib/src/utils/magic_hash.dart index f99dba4..2c3fd56 100644 --- a/lib/src/utils/magic_hash.dart +++ b/lib/src/utils/magic_hash.dart @@ -4,9 +4,9 @@ import '../../src/crypto.dart'; import 'varuint.dart'; import '../../src/models/networks.dart'; -Uint8List magicHash(String message, [NetworkType network]) { +Uint8List magicHash(String message, [NetworkType? network]) { network = network ?? bitcoin; - Uint8List messagePrefix = utf8.encode(network.messagePrefix); + Uint8List messagePrefix = utf8.encode(network.messagePrefix) as Uint8List; int messageVISize = encodingLength(message.length); int length = messagePrefix.length + messageVISize + message.length; Uint8List buffer = new Uint8List(length); diff --git a/lib/src/utils/push_data.dart b/lib/src/utils/push_data.dart index f608a94..a664735 100644 --- a/lib/src/utils/push_data.dart +++ b/lib/src/utils/push_data.dart @@ -2,51 +2,51 @@ import 'dart:typed_data'; import 'constants/op.dart'; class DecodedPushData { - int opcode; - int number; - int size; + int? opcode; + int? number; + int? size; DecodedPushData({this.opcode, this.number, this.size}); } class EncodedPushData { - int size; - Uint8List buffer; + int? size; + Uint8List? buffer; EncodedPushData({this.size, this.buffer}); } -EncodedPushData encode(Uint8List buffer, number, offset) { +EncodedPushData encode(Uint8List? buffer, number, offset) { var size = encodingLength(number); // ~6 bit if (size == 1) { - buffer.buffer.asByteData().setUint8(offset, number); + buffer!.buffer.asByteData().setUint8(offset, number); // 8 bit } else if (size == 2) { - buffer.buffer.asByteData().setUint8(offset, 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..96e6de5 100644 --- a/lib/src/utils/script.dart +++ b/lib/src/utils/script.dart @@ -10,15 +10,15 @@ Map REVERSE_OPS = final OP_INT_BASE = OPS['OP_RESERVED']; final ZERO = Uint8List.fromList([0]); -Uint8List compile(List chunks) { - final bufferSize = chunks.fold(0, (acc, chunk) { +Uint8List? compile(List chunks) { + final bufferSize = chunks.fold(0, (dynamic acc, chunk) { if (chunk is int) return acc + 1; if (chunk.length == 1 && asMinimalOP(chunk) != null) { return acc + 1; } return acc + pushData.encodingLength(chunk.length) + chunk.length; }); - var buffer = new Uint8List(bufferSize); + Uint8List? buffer = new Uint8List(bufferSize); var offset = 0; chunks.forEach((chunk) { @@ -27,29 +27,29 @@ Uint8List compile(List chunks) { // adhere to BIP62.3, minimal push policy final opcode = asMinimalOP(chunk); if (opcode != null) { - buffer.buffer.asByteData().setUint8(offset, opcode); + buffer!.buffer.asByteData().setUint8(offset, opcode); offset += 1; return null; } pushData.EncodedPushData epd = pushData.encode(buffer, chunk.length, offset); - offset += epd.size; + offset += epd.size!; buffer = epd.buffer; - buffer.setRange(offset, offset + chunk.length, chunk); + buffer!.setRange(offset, offset + chunk.length, chunk); offset += chunk.length; // opcode } else { - buffer.buffer.asByteData().setUint8(offset, chunk); + buffer!.buffer.asByteData().setUint8(offset, chunk); offset += 1; } }); - if (offset != buffer.length) + if (offset != buffer!.length) throw new ArgumentError("Could not decode chunks"); return buffer; } -List decompile(dynamic buffer) { +List? decompile(dynamic buffer) { List chunks = []; if (buffer == null) return chunks; @@ -65,13 +65,13 @@ List decompile(dynamic buffer) { // did reading a pushDataInt fail? if (d == null) return null; - i += d.size; + i += d.size!; // attempt to read too much data? - if (i + d.number > buffer.length) return null; + if (i + d.number! > buffer.length) return null; - final data = buffer.sublist(i, i + d.number); - i += d.number; + final data = buffer.sublist(i, i + d.number!); + i += d.number!; // decompile minimally final op = asMinimalOP(data); @@ -90,22 +90,22 @@ List decompile(dynamic buffer) { return chunks; } -Uint8List fromASM(String asm) { +Uint8List? fromASM(String? asm) { if (asm == '') return Uint8List.fromList([]); - return compile(asm.split(' ').map((chunkStr) { + return compile(asm!.split(' ').map((chunkStr) { if (OPS[chunkStr] != null) return OPS[chunkStr]; return HEX.decode(chunkStr); }).toList()); } -String toASM(List c) { - List chunks; +String toASM(List? c) { + List? chunks; if (c is Uint8List) { chunks = decompile(c); } else { chunks = c; } - return chunks.map((chunk) { + return chunks!.map((chunk) { // data? if (chunk is Uint8List) { final op = asMinimalOP(chunk); @@ -117,10 +117,10 @@ String toASM(List c) { }).join(' '); } -int asMinimalOP(Uint8List buffer) { +int? asMinimalOP(Uint8List buffer) { if (buffer.length == 0) return OPS['OP_0']; if (buffer.length != 1) return null; - if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; + if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE! + buffer[0]; if (buffer[0] == 0x81) return OPS['OP_1NEGATE']; return null; } @@ -179,17 +179,17 @@ Uint8List bip66encode(r, s) { if (lenS > 1 && (s[0] == 0x00) && s[1] & 0x80 == 0) throw new ArgumentError('S value excessively padded'); - var signature = new Uint8List(6 + lenR + lenS); + var signature = new Uint8List(6 + lenR + lenS as int); // 0x30 [total-length] 0x02 [R-length] [R] 0x02 [S-length] [S] signature[0] = 0x30; signature[1] = signature.length - 2; signature[2] = 0x02; signature[3] = r.length; - signature.setRange(4, 4 + lenR, r); - signature[4 + lenR] = 0x02; - signature[5 + lenR] = s.length; - signature.setRange(6 + lenR, 6 + lenR + lenS, s); + signature.setRange(4, 4 + lenR as int, r); + signature[4 + lenR as int] = 0x02; + signature[5 + lenR as int] = s.length; + signature.setRange(6 + lenR as int, 6 + lenR + lenS as int, s); return signature; } diff --git a/lib/src/utils/varuint.dart b/lib/src/utils/varuint.dart index 04aff4c..1694249 100644 --- a/lib/src/utils/varuint.dart +++ b/lib/src/utils/varuint.dart @@ -1,7 +1,7 @@ import 'check_types.dart'; import 'dart:typed_data'; -Uint8List encode(int number, [Uint8List buffer, int offset]) { +Uint8List encode(int number, [Uint8List? buffer, int? offset]) { if (!isUint(number, 53)) ; buffer = buffer ?? new Uint8List(encodingLength(number)); @@ -30,7 +30,7 @@ Uint8List encode(int number, [Uint8List buffer, int offset]) { return buffer; } -int decode(Uint8List buffer, [int offset]) { +int decode(Uint8List buffer, [int? offset]) { offset = offset ?? 0; ByteData bytes = buffer.buffer.asByteData(); final first = bytes.getUint8(offset); diff --git a/pubspec.yaml b/pubspec.yaml index ee9b2ca..b26c922 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -4,7 +4,7 @@ version: 2.0.2 homepage: https://github.com/anicdh environment: - sdk: ">=2.3.0 <3.0.0" + sdk: '>=2.12.0 <3.0.0' dependencies: bip39: ^1.0.6 @@ -13,6 +13,7 @@ dependencies: hex: ^0.2.0 bs58check: ^1.0.2 bech32: 0.2.1 + collection: ^1.15.0-nullsafety.4 dev_dependencies: test: ^1.21.1 diff --git a/test/ecpair_test.dart b/test/ecpair_test.dart index ec70e54..2bfc45c 100644 --- a/test/ecpair_test.dart +++ b/test/ecpair_test.dart @@ -15,30 +15,30 @@ main() { group('ECPair', () { group('fromPrivateKey', () { test('defaults to compressed', () { - final keyPair = ECPair.fromPrivateKey(ONE); + final keyPair = ECPair.fromPrivateKey(ONE as Uint8List); expect(keyPair.compressed, true); }); test('supports the uncompressed option', () { - final keyPair = ECPair.fromPrivateKey(ONE, compressed: false); + final keyPair = ECPair.fromPrivateKey(ONE as Uint8List, compressed: false); expect(keyPair.compressed, false); }); test('supports the network option', () { - final keyPair = ECPair.fromPrivateKey(ONE, + final keyPair = ECPair.fromPrivateKey(ONE as Uint8List, 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']); - expect(HEX.encode(keyPair.publicKey), f['Q']); + final keyPair = ECPair.fromPrivateKey(d as Uint8List, compressed: f['compressed']); + expect(HEX.encode(keyPair.publicKey!), f['Q']); }); }); (fixtures['invalid']['fromPrivateKey'] as List).forEach((f) { test('throws ' + f['exception'], () { final d = HEX.decode(f['d']); try { - expect(ECPair.fromPrivateKey(d), isArgumentError); + expect(ECPair.fromPrivateKey(d as Uint8List), isArgumentError); } catch (err) { expect((err as ArgumentError).message, f['exception']); } @@ -50,7 +50,7 @@ main() { test('throws ' + f['exception'], () { final Q = HEX.decode(f['Q']); try { - expect(ECPair.fromPublicKey(Q), isArgumentError); + expect(ECPair.fromPublicKey(Q as Uint8List), isArgumentError); } catch (err) { expect((err as ArgumentError).message, f['exception']); } @@ -62,7 +62,7 @@ main() { test('imports ${f['WIF']}', () { final keyPair = ECPair.fromWIF(f['WIF']); var network = _getNetwork(f); - expect(HEX.encode(keyPair.privateKey), f['d']); + expect(HEX.encode(keyPair.privateKey!), f['d']); expect(keyPair.compressed, f['compressed']); expect(keyPair.network, network); }); @@ -121,7 +121,7 @@ main() { group('.network', () { (fixtures['valid'] as List).forEach((f) { test('return ${f['network']} for ${f['WIF']}', () { - NETWORKS.NetworkType network = _getNetwork(f); + NETWORKS.NetworkType? network = _getNetwork(f); final keyPair = ECPair.fromWIF(f['WIF']); expect(keyPair.network, network); }); @@ -130,7 +130,7 @@ main() { }); } -NETWORKS.NetworkType _getNetwork(f) { +NETWORKS.NetworkType? _getNetwork(f) { var network; if (f['network'] != null) { if (f['network'] == 'bitcoin') { diff --git a/test/integration/addresses_test.dart b/test/integration/addresses_test.dart index ebe72db..ed9ff2c 100644 --- a/test/integration/addresses_test.dart +++ b/test/integration/addresses_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import '../../lib/src/models/networks.dart' as NETWORKS; import '../../lib/src/ecpair.dart' show ECPair; import '../../lib/src/payments/index.dart' show PaymentData; @@ -30,7 +32,7 @@ main() { }); test('can generate an address from a SHA256 hash', () { final hash = new SHA256Digest() - .process(utf8.encode('correct horse battery staple')); + .process(utf8.encode('correct horse battery staple') as Uint8List); final keyPair = ECPair.fromPrivateKey(hash); final address = new P2PKH(data: new PaymentData(pubkey: keyPair.publicKey)) diff --git a/test/integration/bip32_test.dart b/test/integration/bip32_test.dart index 22119f0..75ecf4e 100644 --- a/test/integration/bip32_test.dart +++ b/test/integration/bip32_test.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:bitcoin_flutter/src/models/networks.dart'; import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData; import 'package:bitcoin_flutter/src/payments/p2pkh.dart'; @@ -43,7 +45,7 @@ void main() { test('can create a BIP32, bitcoin, account 0, external address', () { const path = "m/0'/0/0"; final root = bip32.BIP32.fromSeed(HEX.decode( - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd')); + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd') as Uint8List); final child1 = root.derivePath(path); // option 2, manually final child1b = root.deriveHardened(0).derive(0).derive(0); @@ -52,7 +54,7 @@ void main() { }); test('can create a BIP44, bitcoin, account 0, external address', () { final root = bip32.BIP32.fromSeed(HEX.decode( - 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd')); + 'dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd') as Uint8List); final child1 = root.derivePath("m/44'/0'/0'/0/0"); // option 2, manually final child1b = root @@ -87,7 +89,7 @@ void main() { }); } -String getAddress(node, [network]) { +String? getAddress(node, [network]) { return P2PKH(data: new PaymentData(pubkey: node.publicKey), network: network) .data .address; diff --git a/test/payments/p2pkh_test.dart b/test/payments/p2pkh_test.dart index 63721e7..1cadcc8 100644 --- a/test/payments/p2pkh_test.dart +++ b/test/payments/p2pkh_test.dart @@ -57,10 +57,10 @@ PaymentData _preformPaymentData(dynamic x) { final output = x['output'] != null ? bscript.fromASM(x['output']) : x['outputHex'] != null ? HEX.decode(x['outputHex']) : null; final pubkey = x['pubkey'] != null ? HEX.decode(x['pubkey']) : null; final signature = x['signature'] != null ? HEX.decode(x['signature']) : null; - return new PaymentData(address: address, hash: hash, input: input, output: output, pubkey: pubkey, signature: signature); + return new PaymentData(address: address, hash: hash as Uint8List?, input: input, output: output as Uint8List?, pubkey: pubkey as Uint8List?, signature: signature as Uint8List?); } -String _toString(dynamic x) { +String? _toString(dynamic x) { if (x == null) { return null; } diff --git a/test/payments/p2wpkh_test.dart b/test/payments/p2wpkh_test.dart index 4adb17a..250951f 100644 --- a/test/payments/p2wpkh_test.dart +++ b/test/payments/p2wpkh_test.dart @@ -64,10 +64,10 @@ PaymentData _preformPaymentData(dynamic x) { final output = x['output'] != null ? bscript.fromASM(x['output']) : x['outputHex'] != null ? HEX.decode(x['outputHex']) : null; final pubkey = x['pubkey'] != null ? HEX.decode(x['pubkey']) : null; final signature = x['signature'] != null ? HEX.decode(x['signature']) : null; - return new PaymentData(address: address, hash: hash, input: input, output: output, pubkey: pubkey, signature: signature, witness: witness); + return new PaymentData(address: address, hash: hash as Uint8List?, input: input, output: output as Uint8List?, pubkey: pubkey as Uint8List?, signature: signature as Uint8List?, witness: witness); } -String _toString(dynamic x) { +String? _toString(dynamic x) { if (x == null) { return null; } diff --git a/test/transaction_builder_test.dart b/test/transaction_builder_test.dart index 749ac45..f4e9736 100644 --- a/test/transaction_builder_test.dart +++ b/test/transaction_builder_test.dart @@ -31,7 +31,7 @@ constructSign(f, TransactionBuilder txb) { return txb; } -TransactionBuilder construct(f, [bool dontSign]) { +TransactionBuilder construct(f, [bool? dontSign]) { final network = NETWORKS[f['network']]; final txb = new TransactionBuilder(network: network); if (f['version'] != null) txb.setVersion(f['version']); @@ -71,7 +71,7 @@ main() { .readAsStringSync(encoding: utf8)); group('TransactionBuilder', () { final keyPair = ECPair.fromPrivateKey(HEX.decode( - '0000000000000000000000000000000000000000000000000000000000000001')); + '0000000000000000000000000000000000000000000000000000000000000001') as Uint8List); final scripts = [ '1BgGZ9tcN4rm9KBzDn7KprQz87SZ26SAMH', '1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP' @@ -131,27 +131,27 @@ main() { }); }); group('addInput', () { - TransactionBuilder txb; + late 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]; + final txIn = txb.tx!.ins[0]; expect(txIn.hash, txHash); expect(txIn.index, 1); expect(txIn.sequence, 54); - expect(txb.inputs[0].prevOutScript, null); + 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]; + 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)); + expect(txb.inputs![0].prevOutScript, scripts.elementAt(1)); }); test('accepts a prevTx, index [and sequence number]', () { final prevTx = new Transaction(); @@ -161,11 +161,11 @@ main() { final vin = txb.addInput(prevTx, 1, 54); expect(vin, 0); - final txIn = txb.tx.ins[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)); + expect(txb.inputs![0].prevOutScript, scripts.elementAt(1)); }); test('returns the input index', () { expect(txb.addInput(txHash, 0), 0); @@ -186,7 +186,7 @@ main() { }); }); group('addOutput', () { - TransactionBuilder txb; + late TransactionBuilder txb; setUp(() { txb = new TransactionBuilder(); }); @@ -197,14 +197,14 @@ main() { .address; final vout = txb.addOutput(address, 1000); expect(vout, 0); - final txout = txb.tx.outs[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]; + final txout = txb.tx!.outs[0]; expect(txout.script, scripts.elementAt(0)); expect(txout.value, 1000); }); @@ -260,9 +260,9 @@ main() { }); }); group('addOutputData', () { - TransactionBuilder txb; - String data; - String data2; + late TransactionBuilder txb; + late String data; + late String data2; setUp(() { txb = new TransactionBuilder(); data = 'Hey this is a random string without Bitcoins.'; @@ -271,7 +271,7 @@ main() { test('accepts a ScriptPubKey', () { final vout = txb.addOutputData(scripts.elementAt(0)); expect(vout, 0); - final txout = txb.tx.outs[0]; + final txout = txb.tx!.outs[0]; expect(txout.script, scripts.elementAt(0)); expect(txout.value, 0); }); diff --git a/test/transaction_test.dart b/test/transaction_test.dart index f817c57..b83dd13 100644 --- a/test/transaction_test.dart +++ b/test/transaction_test.dart @@ -9,11 +9,11 @@ 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); + final valids = (fixtures['valid'] as List?); group('Transaction', () { group('fromBuffer/fromHex', () { - valids.forEach(importExport); + valids!.forEach(importExport); (fixtures['hashForSignature'] as List).forEach(importExport); (fixtures['invalid']['fromBuffer'] as List).forEach((f) { test('throws on ${f['exception']}', () { @@ -33,7 +33,7 @@ main() { }); group('toBuffer/toHex', () { - valids.forEach((f) { + valids!.forEach((f) { test('exports ${f['description']} (${f['id']})', () { Transaction actual = fromRaw(f['raw'], false); expect(actual.toHex(), f['hex']); @@ -49,7 +49,7 @@ main() { group('weight/virtualSize', () { test('computes virtual size', () { - valids.forEach((f) { + valids!.forEach((f) { final txHex = (f['whex'] != null && f['whex'] != '') ? f['whex'] : f['hex']; final transaction = Transaction.fromHex(txHex); @@ -59,7 +59,7 @@ main() { }); group('addInput', () { - var prevTxHash; + late var prevTxHash; setUp(() { prevTxHash = HEX.decode( 'ffffffff00ffff000000000000000000000000000000000000000000101010ff'); @@ -72,7 +72,7 @@ main() { test('defaults to empty script, and 0xffffffff SEQUENCE number', () { final tx = new Transaction(); tx.addInput(prevTxHash, 0); - expect(tx.ins[0].script.length, 0); + expect(tx.ins[0].script!.length, 0); expect(tx.ins[0].sequence, 0xffffffff); }); (fixtures['invalid']['addInput'] as List).forEach((f) { @@ -80,7 +80,7 @@ main() { final tx = new Transaction(); final hash = HEX.decode(f['hash']); try { - expect(tx.addInput(hash, f['index']), isArgumentError); + expect(tx.addInput(hash as Uint8List, f['index']), isArgumentError); } catch (err) { expect((err as ArgumentError).message, f['exception']); } @@ -105,7 +105,7 @@ main() { }); } - valids.forEach(verify); + valids!.forEach(verify); }); group('isCoinbase', () { @@ -118,7 +118,7 @@ main() { }); } - valids.forEach(verify); + valids!.forEach(verify); }); group('hashForSignature', () { @@ -160,7 +160,7 @@ Transaction fromRaw(raw, [isWitness]) { } else if (txIn['script'] != null && txIn['script'] != '') { scriptSig = bscript.fromASM(txIn['script']); } - tx.addInput(txHash, txIn['index'], txIn['sequence'], scriptSig); + tx.addInput(txHash as Uint8List, txIn['index'], txIn['sequence'], scriptSig); if (isWitness) { var witness = (txIn['witness'] as List) From 375b7c70849e907cc22dc46902013604a932e119 Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Sat, 18 Mar 2023 16:32:58 +0530 Subject: [PATCH 02/12] Bech32 updated --- lib/src/address.dart | 51 ++++++---- lib/src/payments/index.dart | 71 +++++++++++++- lib/src/payments/p2sh.dart | 185 ++++++++++++++++++++++++++++++++++++ lib/src/utils/script.dart | 15 +++ pubspec.yaml | 1 + 5 files changed, 301 insertions(+), 22 deletions(-) create mode 100644 lib/src/payments/p2sh.dart diff --git a/lib/src/address.dart b/lib/src/address.dart index 66d78a8..b4af55f 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -1,9 +1,12 @@ import 'dart:typed_data'; +import 'package:defichain_bech32/defichain_bech32.dart'; + import 'models/networks.dart'; import 'package:bs58check/bs58check.dart' as bs58check; -import 'package:bech32/bech32.dart'; +// import 'package:bech32/bech32.dart'; import 'payments/index.dart' show PaymentData; import 'payments/p2pkh.dart'; +import 'payments/p2sh.dart'; import 'payments/p2wpkh.dart'; class Address { @@ -17,32 +20,44 @@ class Address { } static Uint8List? addressToOutputScript(String address, [NetworkType? nw]) { - NetworkType network = nw ?? bitcoin; + var network = nw ?? bitcoin; var decodeBase58; var decodeBech32; try { decodeBase58 = bs58check.decode(address); - } catch (err) {} + } catch (err) { + // Base58check decode fail + } 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) { + return P2PKH(data: PaymentData(address: address), network: network) + .data! + .output; + } + if (decodeBase58[0] == network.scriptHash) { + return P2SH(data: PaymentData(address: address), network: network) + .data! + .output; + } + throw ArgumentError('Invalid version or Network mismatch'); } else { try { - decodeBech32 = segwit.decode(address); - } catch (err) {} + decodeBech32 = segwit.decode(SegwitInput(network.bech32!, address)); + } catch (err) { + // Bech32 decode fail + } 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'); + } + var p2wpkh = + P2WPKH(data: PaymentData(address: address), network: network); + return p2wpkh.data!.output; } } - throw new ArgumentError(address + ' has no matching Script'); + throw ArgumentError(address + ' has no matching Script'); } } diff --git a/lib/src/payments/index.dart b/lib/src/payments/index.dart index b7f41cb..a729c50 100644 --- a/lib/src/payments/index.dart +++ b/lib/src/payments/index.dart @@ -1,6 +1,7 @@ import 'dart:typed_data'; class PaymentData { + String? name; String? address; Uint8List? hash; Uint8List? output; @@ -8,18 +9,80 @@ class PaymentData { Uint8List? pubkey; Uint8List? input; List? witness; + PaymentData? redeem; PaymentData( - {this.address, + {this.name, + this.address, this.hash, this.output, this.pubkey, this.input, this.signature, - this.witness}); + this.witness, + this.redeem}); + + dynamic operator [](String key) { + switch (key) { + case 'name': + return name; + case 'address': + return address; + case 'hash': + return hash; + case 'output': + return output; + case 'pubkey': + return pubkey; + case 'input': + return input; + case 'signature': + return signature; + case 'witness': + return witness; + case 'redeem': + return redeem; + default: + throw ArgumentError('Invalid PaymentData key'); + } + } + + operator []=(String key, dynamic value) { + switch (key) { + case 'name': + name = value; + break; + case 'address': + address = value; + break; + case 'hash': + hash = value; + break; + case 'output': + output = value; + break; + case 'pubkey': + pubkey = value; + break; + case 'input': + input = value; + break; + case 'signature': + signature = value; + break; + case 'witness': + witness = value; + break; + case 'redeem': + redeem = value; + break; + default: + throw ArgumentError('Invalid PaymentData key'); + } + } @override String toString() { - return 'PaymentData{address: $address, hash: $hash, output: $output, signature: $signature, pubkey: $pubkey, input: $input, witness: $witness}'; + return 'PaymentData{name: $name, address: $address, hash: $hash, output: $output, signature: $signature, pubkey: $pubkey, input: $input, witness: $witness, redeem: ${redeem.toString()}}'; } -} +} \ No newline at end of file diff --git a/lib/src/payments/p2sh.dart b/lib/src/payments/p2sh.dart new file mode 100644 index 0000000..0dae6be --- /dev/null +++ b/lib/src/payments/p2sh.dart @@ -0,0 +1,185 @@ +import 'dart:typed_data'; +// import 'package:bip32_defichain/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 P2SH { + PaymentData? data; + late NetworkType network; + P2SH({required data, network}) { + this.network = network ?? bitcoin; + this.data = data; + _init(); + } + void _init() { + data!.name = 'p2sh'; + + if (data!.address == null && data!.hash == null && data!.output == null && data!.redeem == null && data!.input == null) throw ArgumentError('Not enough data'); + + if (data!.address != null) { + _getDataFromAddress(data!.address!); + _getDataFromHash(); + } + + if (data!.hash != null) { + _getDataFromHash(); + } + + if (data!.output != null) { + if (data!.output!.length != 23 || data!.output![0] != OPS['OP_HASH160'] || data!.output![1] != 0x14 || data!.output![22] != OPS['OP_EQUAL']) { + throw ArgumentError('Output is invalid'); + } + final hash = data!.output!.sublist(2, 22); + + if (data!.hash != null && data!.hash.toString() != hash.toString()) { + throw ArgumentError('Hash mismatch'); + } + data!.hash = hash; + _getDataFromHash(); + } + + if (data!.input != null) { + _getDataFromInput(); + _checkRedeem(data!.redeem!); + _getDataFromRedeem(); + } + + if (data!.redeem != null) { + _checkRedeem(data!.redeem!); + _getDataFromRedeem(); + } + + if (data!.witness != null) { + if (data!.redeem != null && data!.redeem!.witness != null && !_stacksEqual(data!.redeem!.witness!, data!.witness!)) { + throw ArgumentError('Witness and redeem.witness mismatch'); + } + } + } + + void _getDataFromAddress(String address) { + var payload = bs58check.decode(address); + final version = payload.buffer.asByteData().getUint8(0); + if (version != network.scriptHash) { + throw ArgumentError('Invalid version or Network mismatch'); + } + + final hash = payload.sublist(1); + + if (data!.hash != null && data!.hash.toString() != hash.toString()) { + throw ArgumentError('Hash mismatch'); + } + + data!.hash = hash; + + if (data!.hash!.length != 20) throw ArgumentError('Invalid address'); + } + + void _getDataFromHash() { + if (data!.address == null) { + final payload = Uint8List(21); + payload.buffer.asByteData().setUint8(0, network.scriptHash); + payload.setRange(1, payload.length, data!.hash!); + data!.address = bs58check.encode(payload); + } + + data!.output ??= bscript.compile([ + OPS['OP_HASH160'], + data!.hash, + OPS['OP_EQUAL'], + ]); + } + + void _checkRedeem(PaymentData redeem) { + // is the redeem output empty/invalid? + if (redeem.output != null) { + final decompile = bscript.decompile(redeem.output)!; + if (decompile.isEmpty) { + throw ArgumentError('Redeem.output too short'); + } + + // match hash against other sources + final hash2 = hash160(redeem.output!); + if (data!.hash != null && data!.hash!.isNotEmpty && (data!.hash.toString() != hash2.toString())) { + throw ArgumentError('Hash mismatch'); + } + } + + if (redeem.input != null) { + final hasInput = redeem.input!.isNotEmpty; + final hasWitness = redeem.witness != null && redeem.witness!.isNotEmpty; + if (!hasInput && !hasWitness) { + throw ArgumentError('Empty input'); + } + if (hasInput && hasWitness) { + throw ArgumentError('Input and witness provided'); + } + if (hasInput) { + final richunks = bscript.decompile(redeem.input); + if (!bscript.isPushOnly(richunks)) { + throw ArgumentError('Non push-only scriptSig'); + } + } + } + } + + void _getDataFromRedeem() { + if (data!.redeem!.output != null) { + data!.hash = hash160(data!.redeem!.output!); + _getDataFromHash(); + + if (data!.redeem!.input != null) { + var _chunks = bscript.decompile(data!.redeem!.input)!; + _chunks.add(data!.redeem!.output); + _getDataFromChunk(_chunks); + } + } + + data!.witness ??= data!.redeem!.witness ?? []; + } + + void _getDataFromChunk([List? _chunks]) { + if (data!.input == null && _chunks != null) { + data!.input = bscript.compile(_chunks); + } + } + + void _getDataFromInput() { + final chunks = _chunks(); + if (chunks == null || chunks.isEmpty) { + throw ArgumentError('Input too short'); + } + + if (_redeem().output == null) throw ArgumentError('Input is invalid'); + + data!.redeem ??= _redeem(); + } + + List? _chunks() { + return bscript.decompile(data!.input); + } + + PaymentData _redeem() { + final chunks = bscript.decompile(data!.input)!; + final output = chunks[chunks.length - 1] is Uint8List ? chunks[chunks.length - 1] : null; + return PaymentData( + output: output, + input: bscript.compile(chunks.sublist(0, chunks.length - 1)), + witness: data!.witness ?? [], + ); + } + + bool _stacksEqual(List a, List b) { + if (a.length != b.length) return false; + var i = 0; + return a.every((x) { + final res = x.toString() == b[i].toString(); + i += 1; + return res; + }); + } +} \ No newline at end of file diff --git a/lib/src/utils/script.dart b/lib/src/utils/script.dart index 96e6de5..c5d8365 100644 --- a/lib/src/utils/script.dart +++ b/lib/src/utils/script.dart @@ -10,6 +10,21 @@ Map REVERSE_OPS = final OP_INT_BASE = OPS['OP_RESERVED']; final ZERO = Uint8List.fromList([0]); +bool isOPInt(dynamic value) { + return (value is num && + (value == OPS['OP_0'] || + (value >= OPS['OP_1']! && value <= OPS['OP_16']!) || + value == OPS['OP_1NEGATE'])); +} + +bool isPushOnlyChunk(dynamic value) { + return (value is Uint8List) || isOPInt(value); +} + +bool isPushOnly(dynamic value) { + return (value is List) && value.every(isPushOnlyChunk); +} + Uint8List? compile(List chunks) { final bufferSize = chunks.fold(0, (dynamic acc, chunk) { if (chunk is int) return acc + 1; diff --git a/pubspec.yaml b/pubspec.yaml index b26c922..2be6b80 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: bs58check: ^1.0.2 bech32: 0.2.1 collection: ^1.15.0-nullsafety.4 + defichain_bech32: ^0.1.0 dev_dependencies: test: ^1.21.1 From cde6a997b8e73f8c2adc39df28abe4ca4386306a Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Sat, 18 Mar 2023 17:10:38 +0530 Subject: [PATCH 03/12] p2wpkh update --- lib/src/payments/p2wpkh.dart | 106 +++++++++++------------- lib/src/transaction.dart | 8 +- lib/src/transaction_builder.dart | 8 +- pubspec.yaml | 1 + test/integration/addresses_test.dart | 4 +- test/integration/transactions_test.dart | 2 +- test/payments/p2wpkh_test.dart | 14 ++-- 7 files changed, 68 insertions(+), 75 deletions(-) diff --git a/lib/src/payments/p2wpkh.dart b/lib/src/payments/p2wpkh.dart index 8653d3f..775b4a1 100644 --- a/lib/src/payments/p2wpkh.dart +++ b/lib/src/payments/p2wpkh.dart @@ -1,7 +1,6 @@ import 'dart:typed_data'; -import 'package:meta/meta.dart'; -import 'package:bip32/src/utils/ecurve.dart' show isPoint; -import 'package:bech32/bech32.dart'; +import 'package:bip32_defichain/src/utils/ecurve.dart' show isPoint; +import 'package:defichain_bech32/defichain_bech32.dart'; import '../crypto.dart'; import '../models/networks.dart'; @@ -12,7 +11,7 @@ import '../utils/constants/op.dart'; class P2WPKH { final EMPTY_SCRIPT = Uint8List.fromList([]); - late PaymentData data; + PaymentData? data; late NetworkType network; P2WPKH({required data, network}) { this.network = network ?? bitcoin; @@ -20,88 +19,79 @@ class P2WPKH { _init(); } - _init() { - if (data.address == null && - data.hash == null && - data.output == null && - data.pubkey == null && - data.witness == null) throw new ArgumentError('Not enough data'); + void _init() { + 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!); + data!.name = 'p2wpkh'; + + if (data!.address != null) { + _getDataFromAddress(data!.address!); } - if (data.hash != null) { + if (data!.hash != null) { _getDataFromHash(); } - if (data.output != null) { - if (data.output!.length != 22 || - data.output![0] != OPS['OP_0'] || - data.output![1] != 20) // 0x14 - throw new ArgumentError('Output is invalid'); - if (data.hash == null) { - data.hash = data.output!.sublist(2); + if (data!.output != null) { + if (data!.output!.length != 22 || data!.output![0] != OPS['OP_0'] || data!.output![1] != 20) { + throw ArgumentError('Output is invalid'); } + data!.hash ??= data!.output!.sublist(2); _getDataFromHash(); } - if (data.pubkey != null) { - data.hash = hash160(data.pubkey!); + if (data!.pubkey != null) { + 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); - } else if (data.pubkey != null && data.signature != null) { - data.witness = [data.signature, data.pubkey]; - if (data.input == null) data.input = EMPTY_SCRIPT; + if (data!.witness != null) { + 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!.input ??= EMPTY_SCRIPT; } } 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!.input ??= EMPTY_SCRIPT; + if (data!.pubkey == null) { + data!.pubkey = witness![1]; + data!.hash ??= hash160(data!.pubkey!); _getDataFromHash(); } - if (data.signature == null) data.signature = witness![0]; + data!.signature ??= witness![0]; } void _getDataFromHash() { - if (data.address == null) { - data.address = segwit.encode(Segwit(network.bech32!, 0, data.hash!)); - } - if (data.output == null) { - data.output = bscript.compile([OPS['OP_0'], data.hash]); - } + data!.address ??= segwit.encode(Segwit(network.bech32!, 0, data!.hash!)).address; + data!.output ??= bscript.compile([OPS['OP_0'], data!.hash]); } void _getDataFromAddress(String address) { try { - Segwit _address = segwit.decode(address); - if (network.bech32 != _address.hrp) - throw new ArgumentError('Invalid prefix or Network mismatch'); - if (_address.version != 0) // Only support version 0 now; - throw new ArgumentError('Invalid address version'); - data.hash = Uint8List.fromList(_address.program); + var _address = segwit.decode(SegwitInput(network.bech32!, address)); + if (network.bech32 != _address.hrp) { + throw ArgumentError('Invalid prefix or Network mismatch'); + } + // Only support version 0 now; + if (_address.version != 0) { + 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'); } } -} +} \ No newline at end of file diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 8867543..0251e7e 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -585,10 +585,10 @@ class Input { if (type == SCRIPT_TYPES['P2WPKH']) { P2WPKH p2wpkh = new P2WPKH(data: new PaymentData(witness: witness)); return new Input( - prevOutScript: p2wpkh.data.output, + prevOutScript: p2wpkh.data!.output, prevOutType: SCRIPT_TYPES['P2WPKH'], - pubkeys: [p2wpkh.data.pubkey], - signatures: [p2wpkh.data.signature]); + pubkeys: [p2wpkh.data!.pubkey], + signatures: [p2wpkh.data!.signature]); } else if (type == SCRIPT_TYPES['P2PKH']) { P2PKH p2pkh = new P2PKH(data: new PaymentData(input: scriptSig)); return new Input( @@ -657,7 +657,7 @@ class Output { var type = classifyOutput(script!); if (type == SCRIPT_TYPES['P2WPKH']) { Uint8List? wpkh1 = - new P2WPKH(data: new PaymentData(output: script)).data.hash; + 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]); diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart index 768a76e..260152e 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -107,7 +107,9 @@ class TransactionBuilder { if (data.length <= MAX_OP_RETURN_SIZE) { scriptPubKey = bscript.compile([OPS['OP_RETURN'], utf8.encode(data)]); } else { - throw new ArgumentError('Too much data embedded, max OP_RETURN size is '+MAX_OP_RETURN_SIZE.toString()); + throw new ArgumentError( + 'Too much data embedded, max OP_RETURN size is ' + + MAX_OP_RETURN_SIZE.toString()); } } else if (data is Uint8List) { scriptPubKey = data; @@ -265,8 +267,8 @@ class TransactionBuilder { pubkey: _inputs![i].pubkeys![0], signature: _inputs![i].signatures![0]), network: network); - tx.setInputScript(i, payment.data.input); - tx.setWitness(i, payment.data.witness); + tx.setInputScript(i, payment.data!.input); + tx.setWitness(i, payment.data!.witness); } } else if (!allowIncomplete) { throw new ArgumentError('Transaction is not complete'); diff --git a/pubspec.yaml b/pubspec.yaml index 2be6b80..a24f57e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,7 @@ dependencies: bech32: 0.2.1 collection: ^1.15.0-nullsafety.4 defichain_bech32: ^0.1.0 + bip32_defichain: ^3.0.1+1 dev_dependencies: test: ^1.21.1 diff --git a/test/integration/addresses_test.dart b/test/integration/addresses_test.dart index ed9ff2c..ab6834c 100644 --- a/test/integration/addresses_test.dart +++ b/test/integration/addresses_test.dart @@ -78,7 +78,7 @@ main() { final address = new P2WPKH(data: new PaymentData(pubkey: keyPair.publicKey)) .data - .address; + !.address; expect(address, 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'); }); test('can generate a SegWit testnet address', () { @@ -89,7 +89,7 @@ main() { data: new PaymentData(pubkey: keyPair.publicKey), network: testnet) .data - .address; + !.address; expect(address, 'tb1qgmp0h7lvexdxx9y05pmdukx09xcteu9sx2h4ya'); }); }); diff --git a/test/integration/transactions_test.dart b/test/integration/transactions_test.dart index 574b0f8..17edaf3 100644 --- a/test/integration/transactions_test.dart +++ b/test/integration/transactions_test.dart @@ -92,7 +92,7 @@ main() { 0, null, p2wpkh - .output); // Alice's previous transaction output, has 200000 satoshis + !.output); // Alice's previous transaction output, has 200000 satoshis txb.addOutput('tb1qchsmnkk5c8wsjg8vxecmsntynpmkxme0yvh2yt', 1000000); txb.addOutput('tb1qn40fftdp6z2lvzmsz4s0gyks3gq86y2e8svgap', 8995000); diff --git a/test/payments/p2wpkh_test.dart b/test/payments/p2wpkh_test.dart index 250951f..7279007 100644 --- a/test/payments/p2wpkh_test.dart +++ b/test/payments/p2wpkh_test.dart @@ -17,25 +17,25 @@ main() { final arguments = _preformPaymentData(f['arguments']); final p2wpkh = new P2WPKH(data: arguments); if (arguments.address == null) { - expect(p2wpkh.data.address, f['expected']['address']); + expect(p2wpkh.data!.address, f['expected']['address']); } if (arguments.hash == null) { - expect(_toString(p2wpkh.data.hash), f['expected']['hash']); + expect(_toString(p2wpkh.data!.hash), f['expected']['hash']); } if (arguments.pubkey == null) { - expect(_toString(p2wpkh.data.pubkey), f['expected']['pubkey']); + expect(_toString(p2wpkh.data!.pubkey), f['expected']['pubkey']); } if (arguments.input == null) { - expect(_toString(p2wpkh.data.input), f['expected']['input']); + expect(_toString(p2wpkh.data!.input), f['expected']['input']); } if (arguments.output == null) { - expect(_toString(p2wpkh.data.output), f['expected']['output']); + expect(_toString(p2wpkh.data!.output), f['expected']['output']); } if (arguments.signature == null) { - expect(_toString(p2wpkh.data.signature), f['expected']['signature']); + expect(_toString(p2wpkh.data!.signature), f['expected']['signature']); } if (arguments.witness == null) { - expect(_toString(p2wpkh.data.witness), f['expected']['witness']); + expect(_toString(p2wpkh.data!.witness), f['expected']['witness']); } }); }); From 082e57a77c949d5fb3c808bfed64e99b7265fd3d Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Sat, 18 Mar 2023 18:00:31 +0530 Subject: [PATCH 04/12] p2pkh update --- lib/src/address.dart | 2 +- lib/src/payments/p2pk.dart | 7 +------ lib/src/payments/p2pkh.dart | 3 +-- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/src/address.dart b/lib/src/address.dart index b4af55f..259cbee 100644 --- a/lib/src/address.dart +++ b/lib/src/address.dart @@ -31,7 +31,7 @@ class Address { if (decodeBase58 != null) { if (decodeBase58[0] == network.pubKeyHash) { return P2PKH(data: PaymentData(address: address), network: network) - .data! + .data .output; } if (decodeBase58[0] == network.scriptHash) { diff --git a/lib/src/payments/p2pk.dart b/lib/src/payments/p2pk.dart index 7f6ead1..30c1357 100644 --- a/lib/src/payments/p2pk.dart +++ b/lib/src/payments/p2pk.dart @@ -1,12 +1,7 @@ -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 'package:bip32_defichain/src/utils/ecurve.dart' show isPoint; -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 { diff --git a/lib/src/payments/p2pkh.dart b/lib/src/payments/p2pkh.dart index 8f9f96f..5fa23a1 100644 --- a/lib/src/payments/p2pkh.dart +++ b/lib/src/payments/p2pkh.dart @@ -1,6 +1,5 @@ import 'dart:typed_data'; -import 'package:meta/meta.dart'; -import 'package:bip32/src/utils/ecurve.dart' show isPoint; +import 'package:bip32_defichain/src/utils/ecurve.dart' show isPoint; import 'package:bs58check/bs58check.dart' as bs58check; import '../crypto.dart'; From 3e75c6e5324b0abbb867c875b586ad0bb1466b84 Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Tue, 28 Mar 2023 20:28:26 +0530 Subject: [PATCH 05/12] Transaction segwit changes --- lib/src/classify.dart | 7 +- lib/src/templates/scriptHash.dart | 43 +++++ lib/src/transaction.dart | 293 ++++++++++++++++++++++-------- lib/src/transaction_builder.dart | 179 ++++++++++++++---- 4 files changed, 404 insertions(+), 118 deletions(-) create mode 100644 lib/src/templates/scriptHash.dart diff --git a/lib/src/classify.dart b/lib/src/classify.dart index 771e06c..c0540c0 100644 --- a/lib/src/classify.dart +++ b/lib/src/classify.dart @@ -2,6 +2,7 @@ import 'dart:typed_data'; import '../src/utils/script.dart' as bscript; import 'templates/pubkeyhash.dart' as pubkeyhash; import 'templates/pubkey.dart' as pubkey; +import 'templates/scriptHash.dart' as scripthash; import 'templates/witnesspubkeyhash.dart' as witnessPubKeyHash; const SCRIPT_TYPES = { @@ -24,11 +25,15 @@ String? classifyOutput(Uint8List script) { return SCRIPT_TYPES['NONSTANDARD']; } -String? classifyInput(Uint8List? script) { +String? classifyInput(Uint8List? script, bool allowIncomplete) { 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']; + if (scripthash.inputCheck(chunks, allowIncomplete)) { + return SCRIPT_TYPES['P2SH']; + } + if (pubkey.inputCheck(chunks)) return SCRIPT_TYPES['P2PK']; return SCRIPT_TYPES['NONSTANDARD']; } diff --git a/lib/src/templates/scriptHash.dart b/lib/src/templates/scriptHash.dart new file mode 100644 index 0000000..c1398b4 --- /dev/null +++ b/lib/src/templates/scriptHash.dart @@ -0,0 +1,43 @@ +import 'dart:typed_data'; +import '../utils/script.dart' as bscript; +import './pubkeyhash.dart' as p2pkh; +import './witnesspubkeyhash.dart' as p2wpkh; +import '../utils/constants/op.dart'; + +bool inputCheck(List chunks, bool allowIncomplete) { + if (chunks.isEmpty) return false; + + final lastChunk = chunks.last; + + if (!(lastChunk is Uint8List)) return false; + + final scriptSigChunks = bscript.decompile( + bscript.compile(chunks.sublist(0, chunks.length - 1)), + ); + + final redeemScriptChunks = bscript.decompile(lastChunk); + // is redeemScript a valid script? + if (redeemScriptChunks == null) return false; + // is redeemScriptSig push only? + if (!bscript.isPushOnly(scriptSigChunks)) return false; + // is witness? + if (chunks.length == 1) { + // TODO p2wsh + return p2wpkh.outputCheck(bscript.compile(redeemScriptChunks)!); + } + + if (p2pkh.inputCheck(scriptSigChunks!) && + p2pkh.outputCheck(bscript.compile(redeemScriptChunks)!)) { + return true; + } + + return false; +} + +bool outputCheck(Uint8List script) { + final buffer = bscript.compile(script)!; + return buffer.length == 23 && + buffer[0] == OPS['OP_HASH160'] && + buffer[1] == 0x14 && + buffer[22] == OPS['OP_EQUAL']; +} diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 0251e7e..59e58d3 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; +import 'package:bitcoin_flutter/src/payments/p2sh.dart'; import 'package:collection/collection.dart' show IterableExtension; import 'package:hex/hex.dart'; import 'payments/index.dart' show PaymentData; @@ -26,14 +27,18 @@ final ZERO = HEX final ONE = HEX .decode('0000000000000000000000000000000000000000000000000000000000000001'); final VALUE_UINT64_MAX = HEX.decode('ffffffffffffffff'); -final BLANK_OUTPUT = new Output( - script: EMPTY_SCRIPT, valueBuffer: VALUE_UINT64_MAX as Uint8List?); +final BLANK_OUTPUT = Output( + script: EMPTY_SCRIPT, valueBuffer: Uint8List.fromList(VALUE_UINT64_MAX)); + +const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000; +const int SERIALIZE_TRANSACTION_NO_TOKENS = 0x20000000; +const MIN_VERSION_NO_TOKENS = 3; class Transaction { int? version = 1; int? locktime = 0; List ins = []; - List outs = []; + List outs = []; Transaction(); int addInput(Uint8List hash, int? index, @@ -77,42 +82,44 @@ class Transaction { var hashPrevouts = ZERO; var hashSequence = ZERO; - writeSlice(slice) { - tbuffer.setRange(toffset, toffset + slice.length as int, slice); - toffset += slice.length as int; + void writeSlice(List? slice) { + tbuffer.setRange(toffset, toffset + slice!.length, slice); + toffset += slice.length; } - writeUInt8(i) { + void writeUInt8(i) { bytes.setUint8(toffset, i); toffset++; } - writeUInt32(i) { + void writeUInt32(i) { bytes.setUint32(toffset, i, Endian.little); toffset += 4; } - writeInt32(i) { + // ignore: unused_element + void writeInt32(i) { bytes.setInt32(toffset, i, Endian.little); toffset += 4; } - writeUInt64(i) { + void writeUInt64(i) { bytes.setUint64(toffset, i, Endian.little); toffset += 8; } - writeVarInt(i) { + void writeVarInt(i) { varuint.encode(i, tbuffer, toffset); toffset += varuint.encodingLength(i); } - writeVarSlice(slice) { + void writeVarSlice(slice) { writeVarInt(slice.length); writeSlice(slice); } - writeVector(vector) { + // ignore: unused_element + void writeVector(vector) { writeVarInt(vector.length); vector.forEach((buf) { writeVarSlice(buf); @@ -134,7 +141,7 @@ class Transaction { if ((hashType & SIGHASH_ANYONECANPAY) == 0 && (hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { - tbuffer = new Uint8List(4 * this.ins.length); + tbuffer = Uint8List(4 * this.ins.length); bytes = tbuffer.buffer.asByteData(); toffset = 0; ins.forEach((txIn) { @@ -244,18 +251,32 @@ class Transaction { return bcrypto.hash256(buffer); } - _byteLength(_ALLOW_WITNESS) { + num _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!)) + + outs.fold( + 0, + (sum, output) => + sum + + (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + + 8 + + varSliceSize(output.script!)) + (hasWitness - ? ins.fold(0, (sum, input) => sum + vectorSize(input.witness!)) + ? ins.fold(0, (sum, input) => sum + vectorSizeNew(input)) : 0); } + int vectorSizeNew(Input input) { + if (input.witness != null && input.witness!.isNotEmpty) { + return vectorSize(input.witness!); + } + + return varuint.encodingLength(0); + } + int vectorSize(List someVector) { var length = someVector.length; return varuint.encodingLength(length) + @@ -268,11 +289,11 @@ class Transaction { int? weight() { var base = _byteLength(false); var total = _byteLength(true); - return base * 3 + total; + return base * 3 + total as int; } int byteLength() { - return _byteLength(true); + return _byteLength(true) as int; } int virtualSize() { @@ -310,7 +331,8 @@ class Transaction { _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)); + if (buffer == null) + buffer = new Uint8List(_byteLength(_ALLOW_WITNESS) as int); // 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 @@ -385,11 +407,18 @@ class Transaction { writeSlice(txOut.valueBuffer); } writeVarSlice(txOut.script); + if (this.version! > MIN_VERSION_NO_TOKENS) { + writeVarInt(txOut.tokenId); + } }); if (_ALLOW_WITNESS && hasWitnesses()) { ins.forEach((txInt) { - writeVector(txInt.witness); + if (txInt.witness == null) { + writeVarInt(0); + } else { + writeVector(txInt.witness); + } }); } @@ -410,7 +439,7 @@ class Transaction { return Input.clone(input); }).toList(); tx.outs = _tx.outs.map((output) { - return Output.clone(output); + return OutputBase.clone(output); }).toList(); return tx; } @@ -450,7 +479,7 @@ class Transaction { Uint8List readSlice(n) { offset += n as int; - return buffer.sublist(offset - n as int, offset); + return buffer.sublist(offset - n, offset); } int readVarInt() { @@ -526,15 +555,19 @@ class Transaction { ); } - // @override - // toString() { - // this.ins.forEach((txInput) { - // print(txInput.toString()); - // }); - // this.outs.forEach((txOutput) { - // print(txOutput.toString()); - // }); - // } + @override + String toString() { + final s = []; + ins.forEach((txInput) { + s.add(txInput.toString()); + print(txInput.toString()); + }); + outs.forEach((txOutput) { + s.add(txOutput.toString()); + print(txOutput.toString()); + }); + return s.join('\n'); + } } class Input { @@ -545,11 +578,17 @@ class Input { Uint8List? script; Uint8List? signScript; Uint8List? prevOutScript; + Uint8List? redeemScript; + Uint8List? witnessScript; + String? signType; String? prevOutType; - late bool hasWitness; + String? redeemScriptType; + String? witnessScriptType; + bool? hasWitness; List? pubkeys; List? signatures; List? witness; + int? maxSignatures; Input( {this.hash, @@ -558,25 +597,35 @@ class Input { this.sequence, this.value, this.prevOutScript, + this.redeemScript, + this.witnessScript, 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!)) + this.signType, + this.prevOutType, + this.redeemScriptType, + this.witnessScriptType, + this.maxSignatures}) { + hasWitness = false; // Default value + if (hash != null && !isHash256bit(hash!)) { + throw ArgumentError('Invalid input hash'); + } + if (index != null && !isUint(index!, 32)) { + throw ArgumentError('Invalid input index'); + } + if (sequence != null && !isUint(sequence!, 32)) { + throw ArgumentError('Invalid input sequence'); + } + if (value != null && !isShatoshi(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 ssType = classifyInput(scriptSig, true); var wsType = classifyWitness(witness); if (ssType == SCRIPT_TYPES['NONSTANDARD']) ssType = null; if (wsType == SCRIPT_TYPES['NONSTANDARD']) wsType = null; @@ -603,7 +652,32 @@ class Input { pubkeys: [], signatures: [p2pk.data.signature]); } - throw Exception("conditions failed!"); + if (type == SCRIPT_TYPES['P2SH']) { + var p2sh = P2SH(data: PaymentData(input: scriptSig, witness: witness)); + final output = p2sh.data!.output; + final redeem = p2sh.data!.redeem!; + final outputType = classifyOutput(redeem.output!); + final expanded = Input.expandInput( + redeem.input!, + redeem.witness!, + outputType, + redeem.output, + ); + if (expanded.prevOutType == null) return Input(); + return Input( + prevOutScript: output, + prevOutType: SCRIPT_TYPES['P2SH'], + redeemScript: redeem.output, + redeemScriptType: expanded.prevOutType, + witnessScript: expanded.witnessScript, + witnessScriptType: expanded.witnessScriptType, + pubkeys: expanded.pubkeys, + signatures: expanded.signatures); + } + return Input( + prevOutType: SCRIPT_TYPES['NONSTANDARD'], + prevOutScript: scriptSig, + ); } factory Input.clone(Input input) { @@ -631,48 +705,75 @@ class Input { @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}'; + return ''' + Input{ + hash: $hash, + index: $index, + sequence: $sequence, + value: $value, + script: $script, + signScript: $signScript, + prevOutScript: $prevOutScript, + redeemScript: $redeemScript, + witnessScript: $witnessScript, + pubkeys: $pubkeys, + signatures: $signatures, + witness: $witness, + signType: $signType, + prevOutType: $prevOutType, + redeemScriptType: $redeemScriptType, + witnessScriptType: $witnessScriptType, + } + '''; } } -class Output { +class OutputBase { + String? type; Uint8List? script; int? value; Uint8List? valueBuffer; List? pubkeys; List? signatures; + int? maxSignatures; - Output( - {this.script, + final int tokenId; + + OutputBase( + {this.type, + this.script, this.value, this.pubkeys, this.signatures, - this.valueBuffer}) { - if (value != null && !isShatoshi(value!)) - throw ArgumentError('Invalid ouput value'); - } + this.valueBuffer, + this.maxSignatures, + this.tokenId = 0}) {} - factory Output.expandOutput(Uint8List? script, [Uint8List? ourPubKey]) { - if (ourPubKey == null) return new Output(); + factory OutputBase.expandOutput(Uint8List? script, [Uint8List? ourPubKey]) { + if (ourPubKey == null) return OutputBase(); var type = classifyOutput(script!); if (type == SCRIPT_TYPES['P2WPKH']) { Uint8List? wpkh1 = - new P2WPKH(data: new PaymentData(output: script)).data!.hash; + 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]); + if (wpkh1.toString() != wpkh2.toString()) + throw ArgumentError('Hash mismatch!'); + return OutputBase(type: type, 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]); + if (pkh1.toString() != pkh2.toString()) { + throw ArgumentError('Hash mismatch!'); + } + return OutputBase(type: type, pubkeys: [ourPubKey], signatures: [null]); } - throw Exception("conditions failed!"); + return OutputBase(); } - factory Output.clone(Output output) { - return new Output( + factory OutputBase.clone(OutputBase output) { + return new OutputBase( + type: output.type, script: output.script != null ? Uint8List.fromList(output.script!) : null, value: output.value, valueBuffer: output.valueBuffer != null @@ -693,7 +794,39 @@ class Output { @override String toString() { - return 'Output{script: $script, value: $value, valueBuffer: $valueBuffer, pubkeys: $pubkeys, signatures: $signatures}'; + return ''' + Output{ + type: $type, + script: $script, + value: $value, + valueBuffer: $valueBuffer, + pubkeys: $pubkeys, + signatures: $signatures + } + '''; + } +} + +class Output extends OutputBase { + Output( + {String? type, + Uint8List? script, + int? value, + Uint8List? valueBuffer, + List? pubkeys, + List? signartures, + int? maxSignatures}) + : super( + type: type, + script: script, + value: value, + valueBuffer: valueBuffer, + pubkeys: pubkeys, + signatures: signartures, + maxSignatures: maxSignatures) { + if (value != null && !isShatoshi(value)) { + throw ArgumentError('Invalid ouput value'); + } } } @@ -705,23 +838,23 @@ bool isCoinbaseHash(Uint8List buffer) { 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']; -} +// 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; diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart index 260152e..7b39380 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -13,6 +13,7 @@ import 'address.dart'; import 'payments/index.dart' show PaymentData; import 'payments/p2pkh.dart'; import 'payments/p2wpkh.dart'; +import 'payments/p2sh.dart'; import 'classify.dart'; const int MAX_OP_RETURN_SIZE = 100; @@ -22,7 +23,7 @@ class TransactionBuilder { late int maximumFeeRate; List? _inputs; Transaction? _tx; - Map _prevTxSet = {}; + final Map _prevTxSet = {}; TransactionBuilder({NetworkType? network, int? maximumFeeRate}) { this.network = network ?? bitcoin; @@ -75,7 +76,7 @@ class TransactionBuilder { if (locktime < 0 || locktime > 0xFFFFFFFF) throw ArgumentError('Expected Uint32'); // if any signatures exist, throw - if (this._inputs!.map((input) { + if (_inputs!.map((input) { if (input.signatures == null) return false; return input.signatures!.map((s) { return s != null; @@ -84,6 +85,7 @@ class TransactionBuilder { throw new ArgumentError('No, this would invalidate signatures'); } _tx!.locktime = locktime; + return true; } int addOutput(dynamic data, int? value) { @@ -156,24 +158,72 @@ class TransactionBuilder { Uint8List? witnessScript, int? hashType}) { if (keyPair.network != null && - keyPair.network.toString().compareTo(network.toString()) != 0) + keyPair.network.toString().compareTo(network.toString()) != 0) { throw new ArgumentError('Inconsistent network'); - if (vin >= _inputs!.length) + } + if (vin >= _inputs!.length) { throw new ArgumentError('No input at index: $vin'); + } hashType = hashType ?? SIGHASH_ALL; - if (this._needsOutputs(hashType)) + if (_needsOutputs(hashType)) { throw new ArgumentError('Transaction needs outputs'); + } final input = _inputs![vin]; final ourPubKey = keyPair.publicKey; + if (input.redeemScript != null && + redeemScript != null && + input.redeemScript.toString() != redeemScript.toString()) { + throw ArgumentError('Inconsistent redeemScript'); + } if (!_canSign(input)) { if (witnessValue != null) { + if (input.value != null && input.value != witnessValue) { + throw ArgumentError('Input did not match witnessValue'); + } input.value = witnessValue; } if (redeemScript != null && witnessScript != null) { // TODO p2wsh } if (redeemScript != null) { - // TODO + final p2sh = P2SH( + data: PaymentData(redeem: PaymentData(output: redeemScript)), + network: network); + if (input.prevOutScript != null) { + // TODO check + } + final expanded = + OutputBase.expandOutput(p2sh.data!.redeem!.output, ourPubKey); + + if (expanded.pubkeys == null) { + throw ArgumentError( + '${expanded.type} not supported as redeemScript (${bscript.toASM(redeemScript)})', + ); + } + + if (input.signatures != null && + input.signatures!.any((x) => x != null)) { + expanded.signatures = input.signatures; + } + + Uint8List? signScript = redeemScript; + if (expanded.type == SCRIPT_TYPES['P2WPKH']) { + signScript = P2PKH( + data: PaymentData(pubkey: expanded.pubkeys![0]), + network: network) + .data! + .output; + } + input.redeemScript = redeemScript; + input.redeemScriptType = expanded.type; + input.prevOutType = SCRIPT_TYPES['P2SH']; + input.prevOutScript = p2sh.data!.output; + input.hasWitness = (expanded.type == SCRIPT_TYPES['P2WPKH']); + input.signScript = signScript; + input.signType = expanded.type; + input.pubkeys = expanded.pubkeys; + input.signatures = expanded.signatures; + input.maxSignatures = expanded.maxSignatures; } if (witnessScript != null) { // TODO @@ -207,13 +257,11 @@ class TransactionBuilder { } } var signatureHash; - if (input.hasWitness) { - signatureHash = this - ._tx! - .hashForWitnessV0(vin, input.signScript!, input.value!, hashType); - } else { + if (input.hasWitness!) { signatureHash = - this._tx!.hashForSignature(vin, input.signScript, hashType); + _tx!.hashForWitnessV0(vin, input.signScript!, input.value!, hashType); + } else { + signatureHash = _tx!.hashForSignature(vin, input.signScript, hashType); } // enforce in order signing of public keys @@ -240,39 +288,62 @@ class TransactionBuilder { Transaction _build(bool allowIncomplete) { if (!allowIncomplete) { - if (_tx!.ins.length == 0) + if (_tx!.ins.isEmpty) throw new ArgumentError('Transaction has no inputs'); - if (_tx!.outs.length == 0) + if (_tx!.outs.isEmpty) 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); + final input = _inputs![i]; + if (input.pubkeys != null && + input.signatures != null && + input.pubkeys!.isNotEmpty && + input.signatures!.isNotEmpty) { + final result = + buildByType(input.prevOutType, input, allowIncomplete, network); + if (result == null) { + if (!allowIncomplete && + input.prevOutType == SCRIPT_TYPES['NONSTANDARD']) { + throw ArgumentError('Unknown input type'); + } + if (!allowIncomplete) { + throw ArgumentError('Not enough information'); + } + continue; } + + tx.setInputScript(i, result.input); + tx.setWitness(i, result.witness); } else if (!allowIncomplete) { - throw new ArgumentError('Transaction is not complete'); + throw ArgumentError('Transaction is not complete'); } + // 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) { @@ -352,7 +423,7 @@ class TransactionBuilder { input.pubkeys!.length > 0; } - _addInputUnsafe(Uint8List hash, int? vout, Input options) { + int _addInputUnsafe(Uint8List hash, int? vout, Input options) { String txHash = HEX.encode(hash); Input input; if (isCoinbaseHash(hash)) { @@ -370,7 +441,7 @@ class TransactionBuilder { 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); + var expanded = OutputBase.expandOutput(options.prevOutScript); if (expanded.pubkeys != null && !expanded.pubkeys!.isEmpty) { input.pubkeys = expanded.pubkeys; input.signatures = expanded.signatures; @@ -394,6 +465,40 @@ class TransactionBuilder { Map get prevTxSet => _prevTxSet; } +PaymentData? buildByType( + String? type, Input input, bool allowIncomplete, NetworkType? network) { + if (type == SCRIPT_TYPES['P2PKH']) { + return P2PKH( + data: PaymentData( + pubkey: input.pubkeys![0], signature: input.signatures![0]), + network: network) + .data; + } else if (type == SCRIPT_TYPES['P2WPKH']) { + return P2WPKH( + data: PaymentData( + pubkey: input.pubkeys![0], signature: input.signatures![0]), + network: network) + .data; + } else if (type == SCRIPT_TYPES['P2SH']) { + final redeem = + buildByType(input.redeemScriptType, input, allowIncomplete, network); + + if (redeem == null) { + return null; + } + return P2SH( + data: PaymentData( + redeem: PaymentData( + output: redeem.output ?? input.redeemScript, + input: redeem.input, + witness: redeem.witness, + )), + network: network) + .data; + } + return null; +} + Uint8List? pubkeyToOutputScript(Uint8List? pubkey, [NetworkType? nw]) { NetworkType network = nw ?? bitcoin; P2PKH p2pkh = From 42fd8dbbd46699ca9982ae01dd5b02aaebc4e9e0 Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Tue, 28 Mar 2023 21:03:43 +0530 Subject: [PATCH 06/12] update -24 --- lib/src/transaction.dart | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/lib/src/transaction.dart b/lib/src/transaction.dart index 59e58d3..80016d2 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -21,7 +21,7 @@ const SIGHASH_ANYONECANPAY = 0x80; const ADVANCED_TRANSACTION_MARKER = 0x00; const ADVANCED_TRANSACTION_FLAG = 0x01; final EMPTY_SCRIPT = Uint8List.fromList([]); -final List? EMPTY_WITNESS = []; +final EMPTY_WITNESS = []; final ZERO = HEX .decode('0000000000000000000000000000000000000000000000000000000000000000'); final ONE = HEX @@ -59,7 +59,7 @@ class Transaction { bool hasWitnesses() { var witness = ins.firstWhereOrNull( - (input) => input.witness != null && input.witness!.length != 0); + (input) => input.witness != null && input.witness!.isNotEmpty); return witness != null; } @@ -153,23 +153,36 @@ class Transaction { if ((hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { var txOutsSize = outs.fold( - 0, (dynamic sum, output) => sum + 8 + varSliceSize(output.script!)); + 0, + (dynamic sum, output) => + sum + + (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + + 8 + + varSliceSize(output.script!)); tbuffer = new Uint8List(txOutsSize); bytes = tbuffer.buffer.asByteData(); toffset = 0; outs.forEach((txOut) { writeUInt64(txOut.value); writeVarSlice(txOut.script); + if (version! > MIN_VERSION_NO_TOKENS) { + writeVarInt(txOut.tokenId); + } }); 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!)); + tbuffer = new Uint8List(8 + + (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + + varSliceSize(output.script!)); bytes = tbuffer.buffer.asByteData(); toffset = 0; writeUInt64(output.value); writeVarSlice(output.script); + if (version! > MIN_VERSION_NO_TOKENS) { + writeVarInt(output.tokenId); + } hashOutputs = bcrypto.hash256(tbuffer); } From 7253174bb904280450450d9ee42e3b91cc8fcf72 Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Wed, 29 Mar 2023 08:22:56 +0530 Subject: [PATCH 07/12] More update on classify.dart --- lib/src/classify.dart | 13 +- lib/src/ecpair.dart | 59 +++--- lib/src/transaction.dart | 314 +++++++++++-------------------- lib/src/transaction_builder.dart | 48 ++--- 4 files changed, 165 insertions(+), 269 deletions(-) diff --git a/lib/src/classify.dart b/lib/src/classify.dart index c0540c0..9870b64 100644 --- a/lib/src/classify.dart +++ b/lib/src/classify.dart @@ -3,7 +3,7 @@ import '../src/utils/script.dart' as bscript; import 'templates/pubkeyhash.dart' as pubkeyhash; import 'templates/pubkey.dart' as pubkey; import 'templates/scriptHash.dart' as scripthash; -import 'templates/witnesspubkeyhash.dart' as witnessPubKeyHash; +import 'templates/witnesspubkeyhash.dart' as witness_pubkey_hash; const SCRIPT_TYPES = { 'P2SM': 'multisig', @@ -18,10 +18,11 @@ const SCRIPT_TYPES = { }; String? classifyOutput(Uint8List script) { - if (witnessPubKeyHash.outputCheck(script)) return SCRIPT_TYPES['P2WPKH']; + if (witness_pubkey_hash.outputCheck(script)) return SCRIPT_TYPES['P2WPKH']; if (pubkeyhash.outputCheck(script)) return SCRIPT_TYPES['P2PKH']; + if (scripthash.outputCheck(script)) return SCRIPT_TYPES['P2SH']; final chunks = bscript.decompile(script); - if (chunks == null) throw new ArgumentError('Invalid script'); + if (chunks == null) throw ArgumentError('Invalid script'); return SCRIPT_TYPES['NONSTANDARD']; } @@ -37,9 +38,9 @@ String? classifyInput(Uint8List? script, bool allowIncomplete) { return SCRIPT_TYPES['NONSTANDARD']; } -String? classifyWitness(List script) { +String? classifyWitness(List? script) { final chunks = bscript.decompile(script); - if (chunks == null) throw new ArgumentError('Invalid script'); - if (witnessPubKeyHash.inputCheck(chunks)) return SCRIPT_TYPES['P2WPKH']; + if (chunks == null) throw ArgumentError('Invalid script'); + if (witness_pubkey_hash.inputCheck(chunks)) return SCRIPT_TYPES['P2WPKH']; return SCRIPT_TYPES['NONSTANDARD']; } diff --git a/lib/src/ecpair.dart b/lib/src/ecpair.dart index afc23d7..02b5fdc 100644 --- a/lib/src/ecpair.dart +++ b/lib/src/ecpair.dart @@ -1,7 +1,12 @@ +import 'dart:convert'; import 'dart:typed_data'; import 'dart:math'; -import 'package:bip32/src/utils/ecurve.dart' as ecc; -import 'package:bip32/src/utils/wif.dart' as wif; +import 'package:bip32_defichain/bip32.dart' as seg; +import 'package:bip32_defichain/src/utils/ecurve.dart' as ecc; +import 'package:bip32_defichain/src/utils/wif.dart' as wif; +import 'package:bitcoin_flutter/src/utils/magic_hash.dart'; +import 'package:hex/hex.dart'; +import 'package:pointycastle/ecc/api.dart'; import 'models/networks.dart'; class ECPair { @@ -16,67 +21,67 @@ class ECPair { this.compressed = compressed ?? true; } Uint8List? get publicKey { - if (_Q == null) _Q = ecc.pointFromScalar(_d!, compressed!); + _Q ??= ecc.pointFromScalar(_d!, compressed!); return _Q; } Uint8List? get privateKey => _d; String toWIF() { if (privateKey == null) { - throw new ArgumentError('Missing private key'); + throw ArgumentError('Missing private key'); } - return wif.encode(new wif.WIF( - version: network!.wif, privateKey: privateKey!, compressed: compressed!)); + return wif.encode(wif.WIF(version: network!.wif, privateKey: privateKey!, compressed: compressed!)); } Uint8List sign(Uint8List hash) { return ecc.sign(hash, privateKey!); } + String signMessage(String message, [NetworkType? network, seg.SegwitType segwitType = seg.SegwitType.None, bool compressed = true]) { + var hash = magicHash(message, network); + + return base64Encode(ecc.signMessage(hash, privateKey!, compressed, segwitType)); + } + bool verify(Uint8List hash, Uint8List signature) { return ecc.verify(hash, publicKey!, signature); } factory ECPair.fromWIF(String w, {NetworkType? network}) { - wif.WIF decoded = wif.decode(w); + var decoded = wif.decode(w); final version = decoded.version; // TODO support multi networks NetworkType nw; if (network != null) { nw = network; - if (nw.wif != version) throw new ArgumentError('Invalid network version'); + if (nw.wif != version) throw ArgumentError('Invalid network version'); } else { if (version == bitcoin.wif) { nw = bitcoin; } else if (version == testnet.wif) { nw = testnet; } else { - throw new ArgumentError('Unknown network version'); + throw 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 ArgumentError('Expected property privateKey of type Buffer(Length: 32)'); + } + if (!ecc.isPrivate(privateKey)) { + throw ArgumentError('Private key not in range [1, n)'); + } + return ECPair(privateKey, null, network: network, compressed: compressed); } - 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/transaction.dart b/lib/src/transaction.dart index 80016d2..0ca3e54 100644 --- a/lib/src/transaction.dart +++ b/lib/src/transaction.dart @@ -1,11 +1,11 @@ import 'dart:typed_data'; -import 'package:bitcoin_flutter/src/payments/p2sh.dart'; import 'package:collection/collection.dart' show IterableExtension; 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 'payments/p2sh.dart' show P2SH; import 'crypto.dart' as bcrypto; import 'classify.dart'; import 'utils/check_types.dart'; @@ -22,13 +22,10 @@ const ADVANCED_TRANSACTION_MARKER = 0x00; const ADVANCED_TRANSACTION_FLAG = 0x01; final EMPTY_SCRIPT = Uint8List.fromList([]); final EMPTY_WITNESS = []; -final ZERO = HEX - .decode('0000000000000000000000000000000000000000000000000000000000000000'); -final ONE = HEX - .decode('0000000000000000000000000000000000000000000000000000000000000001'); +final ZERO = HEX.decode('0000000000000000000000000000000000000000000000000000000000000000'); +final ONE = HEX.decode('0000000000000000000000000000000000000000000000000000000000000001'); final VALUE_UINT64_MAX = HEX.decode('ffffffffffffffff'); -final BLANK_OUTPUT = Output( - script: EMPTY_SCRIPT, valueBuffer: Uint8List.fromList(VALUE_UINT64_MAX)); +final BLANK_OUTPUT = Output(script: EMPTY_SCRIPT, valueBuffer: Uint8List.fromList(VALUE_UINT64_MAX)); const int SERIALIZE_TRANSACTION_NO_WITNESS = 0x40000000; const int SERIALIZE_TRANSACTION_NO_TOKENS = 0x20000000; @@ -41,43 +38,50 @@ class Transaction { 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)); + int addInput(Uint8List hash, int? index, [int? sequence, Uint8List? scriptSig]) { + ins.add(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)); + outs.add(Output(script: scriptPubKey, value: value)); return outs.length - 1; } + int addOutputAt(Uint8List? scriptPubKey, int value, int at) { + final output = Output(script: scriptPubKey, value: value); + return addBaseOutputAt(output, at); + } + + int addBaseOutput(OutputBase output) { + outs.add(output); + return outs.length - 1; + } + + int addBaseOutputAt(OutputBase output, int index) { + outs.insert(index, output); + return index; + } + bool hasWitnesses() { - var witness = ins.firstWhereOrNull( - (input) => input.witness != null && input.witness!.isNotEmpty); + var witness = ins.firstWhereOrNull((input) => input.witness != null && input.witness!.isNotEmpty); return witness != null; } - setInputScript(int index, Uint8List? scriptSig) { + void setInputScript(int index, Uint8List? scriptSig) { ins[index].script = scriptSig; } - setWitness(int index, List? witness) { + void setWitness(int index, List? witness) { ins[index].witness = witness; } - hashForWitnessV0( - int inIndex, Uint8List prevOutScript, int value, int hashType) { + Uint8List 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 bytes = tbuffer.buffer.asByteData(); var hashOutputs = ZERO; var hashPrevouts = ZERO; var hashSequence = ZERO; @@ -87,6 +91,7 @@ class Transaction { toffset += slice.length; } + // ignore: unused_element void writeUInt8(i) { bytes.setUint8(toffset, i); toffset++; @@ -127,7 +132,7 @@ class Transaction { } if ((hashType & SIGHASH_ANYONECANPAY) == 0) { - tbuffer = new Uint8List(36 * this.ins.length); + tbuffer = Uint8List(36 * ins.length); bytes = tbuffer.buffer.asByteData(); toffset = 0; @@ -138,10 +143,8 @@ class Transaction { hashPrevouts = bcrypto.hash256(tbuffer); } - if ((hashType & SIGHASH_ANYONECANPAY) == 0 && - (hashType & 0x1f) != SIGHASH_SINGLE && - (hashType & 0x1f) != SIGHASH_NONE) { - tbuffer = Uint8List(4 * this.ins.length); + if ((hashType & SIGHASH_ANYONECANPAY) == 0 && (hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { + tbuffer = Uint8List(4 * ins.length); bytes = tbuffer.buffer.asByteData(); toffset = 0; ins.forEach((txIn) { @@ -150,16 +153,9 @@ class Transaction { hashSequence = bcrypto.hash256(tbuffer); } - if ((hashType & 0x1f) != SIGHASH_SINGLE && - (hashType & 0x1f) != SIGHASH_NONE) { - var txOutsSize = outs.fold( - 0, - (dynamic sum, output) => - sum + - (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + - 8 + - varSliceSize(output.script!)); - tbuffer = new Uint8List(txOutsSize); + if ((hashType & 0x1f) != SIGHASH_SINGLE && (hashType & 0x1f) != SIGHASH_NONE) { + var txOutsSize = outs.fold(0, (dynamic sum, output) => sum + (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + 8 + varSliceSize(output.script!)); + tbuffer = Uint8List(txOutsSize); bytes = tbuffer.buffer.asByteData(); toffset = 0; outs.forEach((txOut) { @@ -173,9 +169,7 @@ class Transaction { } else if ((hashType & 0x1f) == SIGHASH_SINGLE && inIndex < outs.length) { // SIGHASH_SINGLE only hash that according output var output = outs[inIndex]; - tbuffer = new Uint8List(8 + - (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + - varSliceSize(output.script!)); + tbuffer = Uint8List(8 + (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + varSliceSize(output.script!)); bytes = tbuffer.buffer.asByteData(); toffset = 0; writeUInt64(output.value); @@ -186,7 +180,7 @@ class Transaction { hashOutputs = bcrypto.hash256(tbuffer); } - tbuffer = new Uint8List(156 + varSliceSize(prevOutScript)); + tbuffer = Uint8List(156 + varSliceSize(prevOutScript)); bytes = tbuffer.buffer.asByteData(); toffset = 0; var input = ins[inIndex]; @@ -199,17 +193,16 @@ class Transaction { writeUInt64(value); writeUInt32(input.sequence); writeSlice(hashOutputs); - writeUInt32(this.locktime); + writeUInt32(locktime); writeUInt32(hashType); return bcrypto.hash256(tbuffer); } - hashForSignature(int inIndex, Uint8List? prevOutScript, int? hashType) { + List 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) { + final ourScript = bscript.compile(bscript.decompile(prevOutScript)!.where((x) { return x != OPS['OP_CODESEPARATOR']; }).toList()); final txTmp = Transaction.clone(this); @@ -257,9 +250,7 @@ class Transaction { } // serialize and hash final buffer = Uint8List(txTmp.virtualSize() + 4); - buffer.buffer - .asByteData() - .setUint32(buffer.length - 4, hashType, Endian.little); + buffer.buffer.asByteData().setUint32(buffer.length - 4, hashType, Endian.little); txTmp._toBuffer(buffer, 0); return bcrypto.hash256(buffer); } @@ -270,16 +261,8 @@ class Transaction { varuint.encodingLength(ins.length) + varuint.encodingLength(outs.length) + ins.fold(0, (sum, input) => sum + 40 + varSliceSize(input.script!)) + - outs.fold( - 0, - (sum, output) => - sum + - (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + - 8 + - varSliceSize(output.script!)) + - (hasWitness - ? ins.fold(0, (sum, input) => sum + vectorSizeNew(input)) - : 0); + outs.fold(0, (sum, output) => sum + (this.version! > MIN_VERSION_NO_TOKENS ? 1 : 0) + 8 + varSliceSize(output.script!)) + + (hasWitness ? ins.fold(0, (sum, input) => sum + vectorSizeNew(input)) : 0); } int vectorSizeNew(Input input) { @@ -292,14 +275,10 @@ class Transaction { int vectorSize(List someVector) { var length = someVector.length; - return varuint.encodingLength(length) + - someVector.fold( - 0, - ((sum, witness) => sum + varSliceSize(witness!) as int) as int - Function(int, Uint8List?)); + return varuint.encodingLength(length) + someVector.fold(0, ((sum, witness) => sum + varSliceSize(witness!) as int) as int Function(int, Uint8List?)); } - int? weight() { + int weight() { var base = _byteLength(false); var total = _byteLength(true); return base * 3 + total as int; @@ -310,15 +289,15 @@ class Transaction { } int virtualSize() { - return (weight()! / 4).ceil(); + return (weight() / 4).ceil(); } Uint8List toBuffer([Uint8List? buffer, int? initialOffset]) { - return this._toBuffer(buffer, initialOffset, true); + return _toBuffer(buffer, initialOffset, true); } String toHex() { - return HEX.encode(this.toBuffer()); + return HEX.encode(toBuffer()); } bool isCoinbaseHash(buffer) { @@ -342,52 +321,51 @@ class Transaction { return HEX.encode(getHash().reversed.toList()); } - _toBuffer([Uint8List? buffer, initialOffset, bool _ALLOW_WITNESS = false]) { + Uint8List _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) as int); + buffer ??= Uint8List(_byteLength(_ALLOW_WITNESS) as int); // 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) { + void writeSlice(slice) { buffer!.setRange(offset, offset + slice.length, slice); offset += slice.length; } - writeUInt8(i) { + void writeUInt8(i) { bytes.setUint8(offset, i); offset++; } - writeUInt32(i) { + void writeUInt32(i) { bytes.setUint32(offset, i, Endian.little); offset += 4; } - writeInt32(i) { + void writeInt32(i) { bytes.setInt32(offset, i, Endian.little); offset += 4; } - writeUInt64(i) { + void writeUInt64(i) { bytes.setUint64(offset, i, Endian.little); offset += 8; } - writeVarInt(i) { + void writeVarInt(i) { varuint.encode(i, buffer, offset); offset += varuint.encodingLength(i); } - writeVarSlice(slice) { + void writeVarSlice(slice) { writeVarInt(slice.length); writeSlice(slice); } - writeVector(vector) { + void writeVector(vector) { writeVarInt(vector.length); vector.forEach((buf) { writeVarSlice(buf); @@ -402,7 +380,7 @@ class Transaction { writeUInt8(ADVANCED_TRANSACTION_FLAG); } - writeVarInt(this.ins.length); + writeVarInt(ins.length); ins.forEach((txIn) { writeSlice(txIn.hash); @@ -411,7 +389,7 @@ class Transaction { writeUInt32(txIn.sequence); }); - writeVarInt(this.outs.length); + writeVarInt(outs.length); outs.forEach((txOut) { if (txOut.valueBuffer == null) { @@ -435,7 +413,7 @@ class Transaction { }); } - writeUInt32(this.locktime); + writeUInt32(locktime); // End writeBuffer // avoid slicing unless necessary @@ -445,7 +423,7 @@ class Transaction { } factory Transaction.clone(Transaction _tx) { - Transaction tx = new Transaction(); + var tx = Transaction(); tx.version = _tx.version; tx.locktime = _tx.locktime; tx.ins = _tx.ins.map((input) { @@ -464,7 +442,7 @@ class Transaction { 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(); + var bytes = buffer.buffer.asByteData(); int readUInt8() { final i = bytes.getUint8(offset); @@ -490,8 +468,8 @@ class Transaction { return i; } - Uint8List readSlice(n) { - offset += n as int; + Uint8List readSlice(int n) { + offset += n; return buffer.sublist(offset - n, offset); } @@ -507,22 +485,21 @@ class Transaction { List readVector() { var count = readVarInt(); - List vector = []; + var vector = []; for (var i = 0; i < count; ++i) { vector.add(readVarSlice()); } return vector; } - final tx = new Transaction(); + final tx = Transaction(); tx.version = readInt32(); final marker = readUInt8(); final flag = readUInt8(); var hasWitnesses = false; - if (marker == ADVANCED_TRANSACTION_MARKER && - flag == ADVANCED_TRANSACTION_FLAG) { + if (marker == ADVANCED_TRANSACTION_MARKER && flag == ADVANCED_TRANSACTION_FLAG) { hasWitnesses = true; } else { offset -= 2; // Reset offset if not segwit tx @@ -530,16 +507,12 @@ class Transaction { final vinLen = readVarInt(); for (var i = 0; i < vinLen; ++i) { - tx.ins.add(new Input( - hash: readSlice(32), - index: readUInt32(), - script: readVarSlice(), - sequence: readUInt32())); + tx.ins.add(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())); + tx.outs.add(Output(value: readUInt64(), script: readVarSlice())); } if (hasWitnesses) { @@ -552,8 +525,9 @@ class Transaction { if (noStrict) return tx; - if (offset != buffer.length) - throw new ArgumentError('Transaction has unexpected data'); + if (offset != buffer.length) { + throw ArgumentError('Transaction has unexpected data'); + } return tx; } @@ -563,7 +537,7 @@ class Transaction { bool noStrict = false, }) { return Transaction.fromBuffer( - HEX.decode(hex) as Uint8List, + Uint8List.fromList(HEX.decode(hex)), noStrict: noStrict, ); } @@ -635,8 +609,10 @@ class Input { } } - factory Input.expandInput(Uint8List? scriptSig, List witness, - [String? type, Uint8List? scriptPubKey]) { + factory Input.expandInput(Uint8List scriptSig, List? witness, [String? type, Uint8List? scriptPubKey]) { + if (scriptSig.isEmpty && witness!.isEmpty) { + return Input(); + } if (type == null || type == '') { var ssType = classifyInput(scriptSig, true); var wsType = classifyWitness(witness); @@ -645,25 +621,19 @@ class Input { 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]); + var p2wpkh = P2WPKH(data: PaymentData(witness: witness)); + return Input(prevOutScript: p2wpkh.data!.output, prevOutType: SCRIPT_TYPES['P2WPKH'], pubkeys: [p2wpkh.data!.pubkey], signatures: [p2wpkh.data!.signature]); + } + if (type == SCRIPT_TYPES['P2PKH']) { + var p2pkh = P2PKH(data: PaymentData(input: scriptSig)); + return Input(prevOutScript: p2pkh.data!.output, prevOutType: SCRIPT_TYPES['P2PKH'], pubkeys: [p2pkh.data!.pubkey], signatures: [p2pkh.data!.signature]); + } + if (type == SCRIPT_TYPES['P2PK']) { + var p2pk = P2PK(data: PaymentData(input: scriptSig)); + return Input(prevOutType: SCRIPT_TYPES['P2PK'], pubkeys: [], signatures: [p2pk.data.signature]); + } + if (type == SCRIPT_TYPES['P2MS']) { + // TODO } if (type == SCRIPT_TYPES['P2SH']) { var p2sh = P2SH(data: PaymentData(input: scriptSig, witness: witness)); @@ -672,7 +642,7 @@ class Input { final outputType = classifyOutput(redeem.output!); final expanded = Input.expandInput( redeem.input!, - redeem.witness!, + redeem.witness, outputType, redeem.output, ); @@ -694,25 +664,15 @@ class Input { } factory Input.clone(Input input) { - return new Input( + return 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) - as List? - : null, - signatures: input.signatures != null - ? input.signatures!.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) - as List? - : null, + prevOutScript: input.prevOutScript != null ? Uint8List.fromList(input.prevOutScript!) : null, + pubkeys: input.pubkeys != null ? input.pubkeys!.map((pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) as List? : null, + signatures: input.signatures != null ? input.signatures!.map((signature) => signature != null ? Uint8List.fromList(signature) : null) as List? : null, ); } @@ -752,56 +712,40 @@ class OutputBase { final int tokenId; - OutputBase( - {this.type, - this.script, - this.value, - this.pubkeys, - this.signatures, - this.valueBuffer, - this.maxSignatures, - this.tokenId = 0}) {} + OutputBase({this.type, this.script, this.value, this.pubkeys, this.signatures, this.valueBuffer, this.maxSignatures, this.tokenId = 0}) {} factory OutputBase.expandOutput(Uint8List? script, [Uint8List? ourPubKey]) { if (ourPubKey == null) return OutputBase(); var type = classifyOutput(script!); if (type == SCRIPT_TYPES['P2WPKH']) { - Uint8List? wpkh1 = - P2WPKH(data: new PaymentData(output: script)).data!.hash; - Uint8List wpkh2 = bcrypto.hash160(ourPubKey); - if (wpkh1.toString() != wpkh2.toString()) + var wpkh1 = P2WPKH(data: PaymentData(output: script)).data!.hash; + var wpkh2 = bcrypto.hash160(ourPubKey); + if (wpkh1.toString() != wpkh2.toString()) { throw ArgumentError('Hash mismatch!'); + } return OutputBase(type: type, 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 (type == SCRIPT_TYPES['P2PKH']) { + var pkh1 = P2PKH(data: PaymentData(output: script)).data!.hash; + var pkh2 = bcrypto.hash160(ourPubKey); if (pkh1.toString() != pkh2.toString()) { throw ArgumentError('Hash mismatch!'); } return OutputBase(type: type, pubkeys: [ourPubKey], signatures: [null]); } + return OutputBase(); } factory OutputBase.clone(OutputBase output) { - return new OutputBase( + return OutputBase( type: output.type, 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) - as List? - : null, - signatures: output.signatures != null - ? output.signatures!.map((signature) => - signature != null ? Uint8List.fromList(signature) : null) - as List? - : null, + valueBuffer: output.valueBuffer != null ? Uint8List.fromList(output.valueBuffer!) : null, + pubkeys: output.pubkeys != null ? output.pubkeys!.map((pubkey) => pubkey != null ? Uint8List.fromList(pubkey) : null) as List? : null, + signatures: output.signatures != null ? output.signatures!.map((signature) => signature != null ? Uint8List.fromList(signature) : null) as List? : null, ); } @@ -821,22 +765,8 @@ class OutputBase { } class Output extends OutputBase { - Output( - {String? type, - Uint8List? script, - int? value, - Uint8List? valueBuffer, - List? pubkeys, - List? signartures, - int? maxSignatures}) - : super( - type: type, - script: script, - value: value, - valueBuffer: valueBuffer, - pubkeys: pubkeys, - signatures: signartures, - maxSignatures: maxSignatures) { + Output({String? type, Uint8List? script, int? value, Uint8List? valueBuffer, List? pubkeys, List? signartures, int? maxSignatures}) + : super(type: type, script: script, value: value, valueBuffer: valueBuffer, pubkeys: pubkeys, signatures: signartures, maxSignatures: maxSignatures) { if (value != null && !isShatoshi(value)) { throw ArgumentError('Invalid ouput value'); } @@ -844,31 +774,13 @@ class Output extends OutputBase { } bool isCoinbaseHash(Uint8List buffer) { - if (!isHash256bit(buffer)) throw new ArgumentError('Invalid hash'); + if (!isHash256bit(buffer)) throw 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 index 7b39380..9383d86 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -170,6 +170,7 @@ class TransactionBuilder { } final input = _inputs![vin]; final ourPubKey = keyPair.publicKey; + // if redeemScript was previously provided, enforce consistency if (input.redeemScript != null && redeemScript != null && input.redeemScript.toString() != redeemScript.toString()) { @@ -235,21 +236,21 @@ class TransactionBuilder { 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.signScript = + P2PKH(data: PaymentData(pubkey: ourPubKey), network: network) + .data! + .output; + } else if (type == SCRIPT_TYPES['P2PKH']) { + var prevOutScript = pubkeyToOutputScript(ourPubKey); input.prevOutType = SCRIPT_TYPES['P2PKH']; input.signatures = [null]; input.pubkeys = [ourPubKey]; input.signScript = prevOutScript; + } else { + // TODO other type } } else { - Uint8List? prevOutScript = pubkeyToOutputScript(ourPubKey); + var prevOutScript = pubkeyToOutputScript(ourPubKey); input.prevOutType = SCRIPT_TYPES['P2PKH']; input.signatures = [null]; input.pubkeys = [ourPubKey]; @@ -269,8 +270,9 @@ class TransactionBuilder { for (var i = 0; i < input.pubkeys!.length; i++) { if (HEX.encode(ourPubKey!).compareTo(HEX.encode(input.pubkeys![i]!)) != 0) continue; - if (input.signatures![i] != null) + if (input.signatures![i] != null) { throw new ArgumentError('Signature already exists'); + } final signature = keyPair.sign(signatureHash); input.signatures![i] = bscript.encodeSignature(signature, hashType); signed = true; @@ -320,30 +322,6 @@ class TransactionBuilder { } else if (!allowIncomplete) { throw ArgumentError('Transaction is not complete'); } - // 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) { @@ -434,7 +412,7 @@ class TransactionBuilder { throw new ArgumentError('Duplicate TxOut: ' + prevTxOut); if (options.script != null) { input = - Input.expandInput(options.script, options.witness ?? EMPTY_WITNESS!); + Input.expandInput(options.script!, options.witness ?? EMPTY_WITNESS!); } else { input = new Input(); } From b86ecb234e3664eb9c9a96ed681a22939189b401 Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Tue, 18 Jul 2023 23:34:23 +0530 Subject: [PATCH 08/12] p2sh exported --- lib/bitcoin_flutter.dart | 7 +++---- lib/src/models/networks.dart | 2 -- lib/src/transaction_builder.dart | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/bitcoin_flutter.dart b/lib/bitcoin_flutter.dart index a77de46..1b7f5a9 100644 --- a/lib/bitcoin_flutter.dart +++ b/lib/bitcoin_flutter.dart @@ -1,6 +1,3 @@ -/// Support for doing something awesome. -/// -/// More dartdocs go here. library bitcoin_flutter; export 'src/bitcoin_flutter_base.dart'; @@ -11,5 +8,7 @@ export 'src/transaction_builder.dart'; export 'src/ecpair.dart'; export 'src/payments/p2pkh.dart'; export 'src/payments/p2wpkh.dart'; +export 'src/payments/p2sh.dart'; export 'src/payments/index.dart'; -// TODO: Export any libraries intended for clients of this package. +export 'src/utils/magic_hash.dart'; + diff --git a/lib/src/models/networks.dart b/lib/src/models/networks.dart index a70ac72..a9a633f 100644 --- a/lib/src/models/networks.dart +++ b/lib/src/models/networks.dart @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; - class NetworkType { String messagePrefix; String? bech32; diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart index 9383d86..171888d 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -1,7 +1,6 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:bitcoin_flutter/src/utils/constants/op.dart'; -import 'package:meta/meta.dart'; import 'package:hex/hex.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:bech32/bech32.dart'; From 05a9d7288f9ca1023061b4034ad1f1e236354848 Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Thu, 12 Oct 2023 23:20:36 +0530 Subject: [PATCH 09/12] dart3 --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index a24f57e..bc38f1d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,6 +22,6 @@ dev_dependencies: dependency_overrides: analyzer: 4.1.0 - convert: ^3.0.0 + convert: ^3.1.1 crypto: ^3.0.0 hex: ^0.2.0 From 8ef4233b74ddd64feac0872c1ff131402a74ac3c Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Thu, 12 Oct 2023 23:32:11 +0530 Subject: [PATCH 10/12] dart3-update --- pubspec.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index bc38f1d..4d68977 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,6 +2,7 @@ name: bitcoin_flutter description: A dart Bitcoin library for Flutter. BIP32, BIP39, P2PKH integration. version: 2.0.2 homepage: https://github.com/anicdh +publish_to: none environment: sdk: '>=2.12.0 <3.0.0' @@ -14,7 +15,10 @@ dependencies: bs58check: ^1.0.2 bech32: 0.2.1 collection: ^1.15.0-nullsafety.4 - defichain_bech32: ^0.1.0 + defichain_bech32: + git: + url: https://github.com/ArunBabu98/defichain_bech32 + ref: dart3 bip32_defichain: ^3.0.1+1 dev_dependencies: From 46f54a6701f16df0c060c8f78ed81065f8d212a8 Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Thu, 12 Oct 2023 23:34:37 +0530 Subject: [PATCH 11/12] dart3-update-2 --- lib/src/transaction_builder.dart | 4 ++-- pubspec.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart index 171888d..893b22e 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -2,8 +2,8 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:bitcoin_flutter/src/utils/constants/op.dart'; import 'package:hex/hex.dart'; -import 'package:bs58check/bs58check.dart' as bs58check; -import 'package:bech32/bech32.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'; diff --git a/pubspec.yaml b/pubspec.yaml index 4d68977..b6b5008 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,8 +12,8 @@ dependencies: bip32: ^2.0.0 pointycastle: ^3.6.0 hex: ^0.2.0 - bs58check: ^1.0.2 - bech32: 0.2.1 + # bs58check: ^1.0.2 + # bech32: 0.2.1 collection: ^1.15.0-nullsafety.4 defichain_bech32: git: From b4d840d0c88b466a26cf6fa8fde7f967e733f55a Mon Sep 17 00:00:00 2001 From: ArunBabu98 Date: Mon, 20 Nov 2023 17:57:53 +0530 Subject: [PATCH 12/12] _overmaximum fees disabled --- lib/src/transaction_builder.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/transaction_builder.dart b/lib/src/transaction_builder.dart index 893b22e..b82adea 100644 --- a/lib/src/transaction_builder.dart +++ b/lib/src/transaction_builder.dart @@ -325,9 +325,9 @@ class TransactionBuilder { if (!allowIncomplete) { // do not rely on this, its merely a last resort - if (_overMaximumFees(tx.virtualSize())) { - throw new ArgumentError('Transaction has absurd fees'); - } + // if (_overMaximumFees(tx.virtualSize())) { + // throw new ArgumentError('Transaction has absurd fees'); + // } } return tx;