Skip to content

Commit 699e45b

Browse files
committed
finish up null-safety impl
1 parent ed4b05e commit 699e45b

File tree

5 files changed

+51
-137
lines changed

5 files changed

+51
-137
lines changed

lib/src/bip32_base.dart

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import 'package:hex/hex.dart';
33

44
import 'utils/crypto.dart';
55
import 'utils/ecurve.dart' as ecc;
6-
import 'package:bs58check_dart/bs58check.dart' as bs58check;
6+
import 'package:bs58check/bs58check.dart' as bs58check;
77
import 'utils/wif.dart' as wif;
88
import 'dart:convert';
99

@@ -20,8 +20,7 @@ class NetworkType {
2020
NetworkType({required this.wif, required this.bip32});
2121
}
2222

23-
final _BITCOIN = new NetworkType(
24-
wif: 0x80, bip32: new Bip32Type(public: 0x0488b21e, private: 0x0488ade4));
23+
final _BITCOIN = new NetworkType(wif: 0x80, bip32: new Bip32Type(public: 0x0488b21e, private: 0x0488ade4));
2524
const HIGHEST_BIT = 0x80000000;
2625
const UINT31_MAX = 2147483647; // 2^31 - 1
2726
const UINT32_MAX = 4294967295; // 2^32 - 1
@@ -51,17 +50,15 @@ class BIP32 {
5150
}
5251

5352
BIP32 neutered() {
54-
final neutered =
55-
BIP32.fromPublicKey(this.publicKey, this.chainCode, this.network);
53+
final neutered = BIP32.fromPublicKey(this.publicKey, this.chainCode, this.network);
5654
neutered.depth = this.depth;
5755
neutered.index = this.index;
5856
neutered.parentFingerprint = this.parentFingerprint;
5957
return neutered;
6058
}
6159

6260
String toBase58() {
63-
final version =
64-
(!isNeutered()) ? network.bip32.private : network.bip32.public;
61+
final version = (!isNeutered()) ? network.bip32.private : network.bip32.public;
6562
Uint8List buffer = new Uint8List(78);
6663
ByteData bytes = buffer.buffer.asByteData();
6764
bytes.setUint32(0, version);
@@ -82,13 +79,11 @@ class BIP32 {
8279
if (privateKey == null) {
8380
throw new ArgumentError("Missing private key");
8481
}
85-
return wif.encode(new wif.WIF(
86-
version: network.wif, privateKey: privateKey!, compressed: true));
82+
return wif.encode(new wif.WIF(version: network.wif, privateKey: privateKey!, compressed: true));
8783
}
8884

8985
BIP32 derive(int index) {
90-
if (index > UINT32_MAX || index < 0)
91-
throw new ArgumentError("Expected UInt32");
86+
if (index > UINT32_MAX || index < 0) throw new ArgumentError("Expected UInt32");
9287
final isHardened = index >= HIGHEST_BIT;
9388
Uint8List data = new Uint8List(37);
9489
if (isHardened) {
@@ -125,8 +120,7 @@ class BIP32 {
125120
}
126121

127122
BIP32 deriveHardened(int index) {
128-
if (index > UINT31_MAX || index < 0)
129-
throw new ArgumentError("Expected UInt31");
123+
if (index > UINT31_MAX || index < 0) throw new ArgumentError("Expected UInt31");
130124
return this.derive(index + HIGHEST_BIT);
131125
}
132126

@@ -135,8 +129,7 @@ class BIP32 {
135129
if (!regex.hasMatch(path)) throw new ArgumentError("Expected BIP32 Path");
136130
List<String> splitPath = path.split("/");
137131
if (splitPath[0] == "m") {
138-
if (parentFingerprint != 0)
139-
throw new ArgumentError("Expected master, got child");
132+
if (parentFingerprint != 0) throw new ArgumentError("Expected master, got child");
140133
splitPath = splitPath.sublist(1);
141134
}
142135
return splitPath.fold(this, (BIP32 prevHd, String indexStr) {
@@ -175,8 +168,7 @@ class BIP32 {
175168
// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
176169
var parentFingerprint = bytes.getUint32(5);
177170
if (depth == 0) {
178-
if (parentFingerprint != 0x00000000)
179-
throw new ArgumentError("Invalid parent fingerprint");
171+
if (parentFingerprint != 0x00000000) throw new ArgumentError("Invalid parent fingerprint");
180172
}
181173

182174
// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
@@ -190,8 +182,7 @@ class BIP32 {
190182

191183
// 33 bytes: private key data (0x00 + k)
192184
if (version == network.bip32.private) {
193-
if (bytes.getUint8(45) != 0x00)
194-
throw new ArgumentError("Invalid private key");
185+
if (bytes.getUint8(45) != 0x00) throw new ArgumentError("Invalid private key");
195186
Uint8List k = buffer.sublist(46, 78);
196187
hd = BIP32.fromPrivateKey(k, chainCode, network);
197188
} else {
@@ -205,23 +196,18 @@ class BIP32 {
205196
return hd;
206197
}
207198

208-
factory BIP32.fromPublicKey(Uint8List publicKey, Uint8List chainCode,
209-
[NetworkType? nw]) {
199+
factory BIP32.fromPublicKey(Uint8List publicKey, Uint8List chainCode, [NetworkType? nw]) {
210200
NetworkType network = nw ?? _BITCOIN;
211201
if (!ecc.isPoint(publicKey)) {
212202
throw new ArgumentError("Point is not on the curve");
213203
}
214204
return new BIP32(null, publicKey, chainCode, network);
215205
}
216206

217-
factory BIP32.fromPrivateKey(Uint8List privateKey, Uint8List chainCode,
218-
[NetworkType? nw]) {
207+
factory BIP32.fromPrivateKey(Uint8List privateKey, Uint8List chainCode, [NetworkType? nw]) {
219208
NetworkType network = nw ?? _BITCOIN;
220-
if (privateKey.length != 32)
221-
throw new ArgumentError(
222-
"Expected property privateKey of type Buffer(Length: 32)");
223-
if (!ecc.isPrivate(privateKey))
224-
throw new ArgumentError("Private key not in range [1, n]");
209+
if (privateKey.length != 32) throw new ArgumentError("Expected property privateKey of type Buffer(Length: 32)");
210+
if (!ecc.isPrivate(privateKey)) throw new ArgumentError("Private key not in range [1, n]");
225211
return new BIP32(privateKey, null, chainCode, network);
226212
}
227213

lib/src/utils/ecurve.dart

Lines changed: 6 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -99,11 +99,11 @@ Uint8List? pointAddScalar(Uint8List p, Uint8List tweak, bool _compressed) {
9999
if (!isPoint(p)) throw new ArgumentError(THROW_BAD_POINT);
100100
if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK);
101101
bool compressed = assumeCompression(_compressed, p);
102-
ECPoint pp = decodeFrom(p);
102+
ECPoint? pp = decodeFrom(p);
103103
if (_compare(tweak, ZERO32) == 0) return getEncoded(pp, compressed);
104104
BigInt tt = fromBuffer(tweak);
105105
ECPoint qq = (G * tt) as ECPoint;
106-
ECPoint uu = (pp + qq) as ECPoint;
106+
ECPoint uu = (pp! + qq) as ECPoint;
107107
if (uu.isInfinity) return null;
108108
return getEncoded(uu, compressed);
109109
}
@@ -146,7 +146,7 @@ bool verify(Uint8List hash, Uint8List q, Uint8List signature) {
146146
// 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (1, isSignature enforces '< n - 1')
147147
if (!isSignature(signature)) throw new ArgumentError(THROW_BAD_SIGNATURE);
148148

149-
ECPoint Q = decodeFrom(q);
149+
ECPoint? Q = decodeFrom(q);
150150
BigInt r = fromBuffer(signature.sublist(0, 32));
151151
BigInt s = fromBuffer(signature.sublist(32, 64));
152152

@@ -229,12 +229,12 @@ Uint8List toBuffer(BigInt d) {
229229
return _encodeBigInt(d);
230230
}
231231

232-
ECPoint decodeFrom(Uint8List P) {
232+
ECPoint? decodeFrom(Uint8List P) {
233233
return secp256k1.curve.decodePoint(P);
234234
}
235235

236-
Uint8List getEncoded(ECPoint P, compressed) {
237-
return P.getEncoded(compressed);
236+
Uint8List getEncoded(ECPoint? P, compressed) {
237+
return P!.getEncoded(compressed);
238238
}
239239

240240
ECSignature deterministicGenerateK(Uint8List hash, Uint8List x) {
@@ -252,40 +252,3 @@ int _compare(Uint8List a, Uint8List b) {
252252
if (aa > bb) return 1;
253253
return -1;
254254
}
255-
256-
/// Decode a BigInt from bytes in big-endian encoding.
257-
BigInt _decodeBigInt(List<int> bytes) {
258-
BigInt result = new BigInt.from(0);
259-
for (int i = 0; i < bytes.length; i++) {
260-
result += new BigInt.from(bytes[bytes.length - i - 1]) << (8 * i);
261-
}
262-
return result;
263-
}
264-
265-
var _byteMask = new BigInt.from(0xff);
266-
267-
/// Encode a BigInt into bytes using big-endian encoding.
268-
Uint8List _encodeBigInt(BigInt number) {
269-
int needsPaddingByte;
270-
int rawSize;
271-
272-
if (number > BigInt.zero) {
273-
rawSize = (number.bitLength + 7) >> 3;
274-
needsPaddingByte = ((number >> (rawSize - 1) * 8) & negativeFlag) == negativeFlag ? 1 : 0;
275-
276-
if (rawSize < 32) {
277-
needsPaddingByte = 1;
278-
}
279-
} else {
280-
needsPaddingByte = 0;
281-
rawSize = (number.bitLength + 8) >> 3;
282-
}
283-
284-
final size = rawSize < 32 ? rawSize + needsPaddingByte : rawSize;
285-
var result = new Uint8List(size);
286-
for (int i = 0; i < size; i++) {
287-
result[size - i - 1] = (number & _byteMask).toInt();
288-
number = number >> 8;
289-
}
290-
return result;
291-
}

lib/src/utils/wif.dart

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,27 @@
1-
import "package:bs58check_dart/bs58check.dart" as bs58check;
1+
import "package:bs58check/bs58check.dart" as bs58check;
22
import "dart:typed_data";
33

44
class WIF {
55
int version;
66
Uint8List privateKey;
77
bool compressed;
8-
WIF(
9-
{required this.version,
10-
required this.privateKey,
11-
required this.compressed});
8+
WIF({required this.version, required this.privateKey, required this.compressed});
129
}
1310

1411
WIF decodeRaw(Uint8List buffer, [int? version]) {
1512
if (version != null && buffer[0] != version) {
1613
throw new ArgumentError("Invalid network version");
1714
}
1815
if (buffer.length == 33) {
19-
return new WIF(
20-
version: buffer[0],
21-
privateKey: buffer.sublist(1, 33),
22-
compressed: false);
16+
return new WIF(version: buffer[0], privateKey: buffer.sublist(1, 33), compressed: false);
2317
}
2418
if (buffer.length != 34) {
2519
throw new ArgumentError("Invalid WIF length");
2620
}
2721
if (buffer[33] != 0x01) {
2822
throw new ArgumentError("Invalid compression flag");
2923
}
30-
return new WIF(
31-
version: buffer[0], privateKey: buffer.sublist(1, 33), compressed: true);
24+
return new WIF(version: buffer[0], privateKey: buffer.sublist(1, 33), compressed: true);
3225
}
3326

3427
Uint8List encodeRaw(int version, Uint8List privateKey, bool compressed) {
@@ -50,6 +43,5 @@ WIF decode(String string, [int? version]) {
5043
}
5144

5245
String encode(WIF wif) {
53-
return bs58check
54-
.encode(encodeRaw(wif.version, wif.privateKey, wif.compressed));
46+
return bs58check.encode(encodeRaw(wif.version, wif.privateKey, wif.compressed));
5547
}

test/bip32_test.dart

Lines changed: 14 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
import 'dart:typed_data';
2+
import 'package:bip32_defichain/bip32.dart';
23
import 'package:hex/hex.dart';
34
import 'package:test/test.dart';
45
import 'dart:io';
56
import 'dart:convert';
67

7-
final LITECOIN = new NetworkType(
8-
bip32: new Bip32Type(private: 0x019d9cfe, public: 0x019da462), wif: 0xb0);
8+
final LITECOIN = new NetworkType(bip32: new Bip32Type(private: 0x019d9cfe, public: 0x019da462), wif: 0xb0);
99
List<dynamic> validAll = [];
1010

1111
void main() {
12-
Map<String, dynamic> fixtures = json
13-
.decode(File('./test/fixtures.json').readAsStringSync(encoding: utf8));
12+
Map<String, dynamic> fixtures = json.decode(File('./test/fixtures.json').readAsStringSync(encoding: utf8));
1413
(fixtures['valid'] as List<dynamic>).forEach((f) {
1514
f['master']['network'] = f['network'];
1615
f['master']['children'] = f['children'];
@@ -51,8 +50,7 @@ void main() {
5150
test('fromBase58 throws', () {
5251
(fixtures['invalid']['fromBase58'] as List<dynamic>).forEach((f) {
5352
var network;
54-
if (f['network'] != null && f['network'] == 'litecoin')
55-
network = LITECOIN;
53+
if (f['network'] != null && f['network'] == 'litecoin') network = LITECOIN;
5654
BIP32? hd;
5755
try {
5856
hd = BIP32.fromBase58(f['string'], network);
@@ -96,8 +94,7 @@ void main() {
9694
try {
9795
hd = master.deriveHardened(c['m']);
9896
} catch (err) {
99-
expect((err as ArgumentError).message,
100-
"Missing private key for hardened child key");
97+
expect((err as ArgumentError).message, "Missing private key for hardened child key");
10198
} finally {
10299
expect(hd, null);
103100
}
@@ -142,8 +139,7 @@ void main() {
142139
try {
143140
hdFPrv1 = BIP32.fromPrivateKey(new Uint8List(2), ONE32);
144141
} catch (err) {
145-
expect((err as ArgumentError).message,
146-
"Expected property privateKey of type Buffer(Length: 32)");
142+
expect((err as ArgumentError).message, "Expected property privateKey of type Buffer(Length: 32)");
147143
} finally {
148144
expect(hdFPrv1, null);
149145
}
@@ -157,22 +153,17 @@ void main() {
157153
});
158154

159155
test("works when private key has leading zeros", () {
160-
const key =
161-
"xprv9s21ZrQH143K3ckY9DgU79uMTJkQRLdbCCVDh81SnxTgPzLLGax6uHeBULTtaEtcAvKjXfT7ZWtHzKjTpujMkUd9dDb8msDeAfnJxrgAYhr";
156+
const key = "xprv9s21ZrQH143K3ckY9DgU79uMTJkQRLdbCCVDh81SnxTgPzLLGax6uHeBULTtaEtcAvKjXfT7ZWtHzKjTpujMkUd9dDb8msDeAfnJxrgAYhr";
162157
BIP32 hdkey = BIP32.fromBase58(key);
163-
expect(HEX.encode(hdkey.privateKey!),
164-
"00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd");
158+
expect(HEX.encode(hdkey.privateKey!), "00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd");
165159
BIP32 child = hdkey.derivePath("m/44'/0'/0'/0/0'");
166-
expect(HEX.encode(child.privateKey!),
167-
"3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb");
160+
expect(HEX.encode(child.privateKey!), "3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb");
168161
});
169162

170163
test('derive', () {
171-
final hd = BIP32.fromBase58(
172-
'xprv9s21ZrQH143K3Jpuz63XbuGs9CH9xG4sniVBBRVm6AJR57D9arxWz6FkXF3JSxSK7jUmVA11AdWa6ZsUtwGztE4QT5i8Y457RRPvMCc39rY');
164+
final hd = BIP32.fromBase58('xprv9s21ZrQH143K3Jpuz63XbuGs9CH9xG4sniVBBRVm6AJR57D9arxWz6FkXF3JSxSK7jUmVA11AdWa6ZsUtwGztE4QT5i8Y457RRPvMCc39rY');
173165
final d = hd.derivePath("m/1'/199007533'/627785449'/1521366139'/1'");
174-
expect(d.toBase58(),
175-
'xprvA39a1i4ieYqGUQ7G1KGnaGzGwm7v3emjms3QN4jZ3HPeubXjshA3XjD5XFaiNgWFvoyC2NV5jN4eFcsVhkrWkvwR4qjdPbue3kpt6Ur3JRf');
166+
expect(d.toBase58(), 'xprvA39a1i4ieYqGUQ7G1KGnaGzGwm7v3emjms3QN4jZ3HPeubXjshA3XjD5XFaiNgWFvoyC2NV5jN4eFcsVhkrWkvwR4qjdPbue3kpt6Ur3JRf');
176167
});
177168

178169
test("fromSeed", () {
@@ -191,8 +182,7 @@ void main() {
191182
test("ecdsa", () {
192183
Uint8List seed = Uint8List.fromList(List.generate(32, (index) => 1));
193184
Uint8List hash = Uint8List.fromList(List.generate(32, (index) => 2));
194-
String sigStr =
195-
"9636ee2fac31b795a308856b821ebe297dda7b28220fb46ea1fbbd7285977cc04c82b734956246a0f15a9698f03f546d8d96fe006c8e7bd2256ca7c8229e6f5c";
185+
String sigStr = "9636ee2fac31b795a308856b821ebe297dda7b28220fb46ea1fbbd7285977cc04c82b734956246a0f15a9698f03f546d8d96fe006c8e7bd2256ca7c8229e6f5c";
196186
Uint8List signature = HEX.decode(sigStr) as Uint8List;
197187
BIP32 node = BIP32.fromSeed(seed);
198188
expect(HEX.encode(node.sign(hash)), sigStr);
@@ -219,10 +209,7 @@ void verify(BIP32 hd, prv, f, network) {
219209
expect(hd.isNeutered(), !prv);
220210

221211
if (f['children'] == null) return;
222-
if (!prv &&
223-
(f['children'] as List<dynamic>)
224-
.map((fc) => fc['hardened'])
225-
.contains(true)) return;
212+
if (!prv && (f['children'] as List<dynamic>).map((fc) => fc['hardened']).contains(true)) return;
226213

227214
(f['children'] as List<dynamic>).forEach((cf) {
228215
var chd = hd.derivePath(cf['path']);
@@ -239,8 +226,7 @@ void verify(BIP32 hd, prv, f, network) {
239226
shd = shd.deriveHardened(cf['m']);
240227
} else {
241228
// verify any publicly derived children
242-
if (cf['base58'] != null)
243-
verify(shd.neutered().derive(cf['m']), false, cf, network);
229+
if (cf['base58'] != null) verify(shd.neutered().derive(cf['m']), false, cf, network);
244230
shd = shd.derive(cf['m']);
245231
verify(shd, prv, cf, network);
246232
}

0 commit comments

Comments
 (0)