├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example └── bip32_example.dart ├── lib ├── bip32.dart └── src │ ├── bip32_base.dart │ └── utils │ ├── crypto.dart │ ├── ecurve.dart │ └── wif.dart ├── pubspec.yaml └── test ├── bip32_test.dart ├── ecc_test.dart └── fixtures.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | doc/api/ 12 | 13 | .idea/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.0 2 | 3 | - Initial version, created by anicdh 4 | 5 | ## 1.0.1 6 | 7 | - Format code & add description 8 | 9 | ## 1.0.2 10 | 11 | - Change version bs58check 12 | 13 | ## 1.0.3 14 | 15 | - Change generate BIP32 from function to factory 16 | 17 | ## 1.0.4 18 | 19 | - Fix error message when generate BIP32 from private key 20 | - Fix argument of wif decode function 21 | 22 | ## 1.0.5 23 | 24 | - Fix wif decodeRaw function 25 | 26 | ## 1.0.6 27 | 28 | - Fix derive bug 29 | 30 | ## 1.0.8 31 | 32 | - Update pointycastle version to 2.0.0 33 | 34 | ## 1.0.10 35 | 36 | - Fix sign 37 | 38 | ## 2.0.0 39 | - Add null-safety -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 anicdh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bip32 2 | 3 | A [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) compatible library for Flutter writing in Dart. 4 | 5 | Inspired by [bitcoinjs](https://github.com/bitcoinjs/bip32) 6 | 7 | ## Example 8 | ```dart 9 | 10 | import 'package:bip32/bip32.dart' as bip32; 11 | import 'package:hex/hex.dart'; 12 | 13 | main() { 14 | bip32.BIP32 node = bip32.BIP32.fromBase58('xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'); 15 | 16 | print(HEX.encode(node.privateKey)); 17 | // => e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35 18 | 19 | bip32.BIP32 nodeNeutered = node.neutered(); 20 | print(nodeNeutered.isNeutered()); 21 | // => true 22 | 23 | print(HEX.encode(nodeNeutered.publicKey)); 24 | // => 0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2 25 | 26 | print(nodeNeutered.toBase58()); 27 | // => xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8 28 | 29 | bip32.BIP32 child = node.derivePath('m/0/0'); 30 | print(child.toBase58()); 31 | // => xprv9ww7sMFLzJMzur2oEQDB642fbsMS4q6JRraMVTrM9bTWBq7NDS8ZpmsKVB4YF3mZecqax1fjnsPF19xnsJNfRp4RSyexacULXMKowSACTRc 32 | 33 | print(HEX.encode(child.privateKey)); 34 | // => f26cf12f89ab91aeeb8d7324a22e8ba080829db15c9245414b073a8c342322aa 35 | 36 | bip32.BIP32 childNeutered = child.neutered(); 37 | print(childNeutered.isNeutered()); 38 | // => true 39 | 40 | print(HEX.encode(childNeutered.publicKey)); 41 | // => 02756de182c5dd4b717ea87e693006da62dbb3cddaa4a5cad2ed1f5bbab755f0f5 42 | 43 | print(childNeutered.toBase58()); 44 | // => xpub6AvUGrnEpfvJ8L7GLRkBTByQ9uBvUHp9o5VxHrFxhvzV4dSWkySpNaBoLR9FpbnwRmTa69yLHF3QfcaxbWT7gWdwws5k4dpmJvqpEuMWwnj 45 | 46 | bip32.BIP32 nodeFromSeed = bip32.BIP32.fromSeed(HEX.decode("000102030405060708090a0b0c0d0e0f")); 47 | print(nodeFromSeed.toBase58()); 48 | // => xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi 49 | 50 | bip32.BIP32 nodeFromPub = bip32.BIP32.fromBase58("xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"); 51 | print(nodeFromPub.toBase58()); 52 | // => xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8 53 | 54 | var message = HEX.decode("0202020202020202020202020202020202020202020202020202020202020202"); 55 | var signature = nodeFromSeed.sign(message); 56 | print(signature); 57 | // => [63, 219, 20, 114, 95, 184, 192, 55, 216, 206, 126, 121, 17, 71, 64, 70, 163, 82, 247, 73, 243, 95, 30, 137, 177, 155, 100, 225, 177, 203, 217, 147, 122, 64, 208, 129, 54, 133, 113, 41, 216, 160, 191, 15, 136, 98, 235, 25, 219, 178, 70, 222, 127, 151, 135, 242, 25, 192, 161, 187, 187, 84, 81, 215] 58 | 59 | print(HEX.encode(signature)); 60 | // => 3fdb14725fb8c037d8ce7e7911474046a352f749f35f1e89b19b64e1b1cbd9937a40d08136857129d8a0bf0f8862eb19dbb246de7f9787f219c0a1bbbb5451d7 61 | 62 | print(nodeFromSeed.verify(message, signature)); 63 | // => true 64 | } 65 | 66 | ``` 67 | 68 | ## Contributor 69 | * anicdh 70 | * p3root 71 | * jcramer (null-safety) 72 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | # exclude: 3 | # - path/to/excluded/files/** 4 | 5 | # Lint rules and documentation, see http://dart-lang.github.io/linter/lints 6 | linter: 7 | rules: 8 | - cancel_subscriptions 9 | - hash_and_equals 10 | - iterable_contains_unrelated_type 11 | - list_remove_unrelated_type 12 | - test_types_in_equals 13 | - unrelated_type_equality_checks 14 | - valid_regexps 15 | -------------------------------------------------------------------------------- /example/bip32_example.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import '../lib/bip32.dart' as bip32; 3 | import 'package:hex/hex.dart'; 4 | 5 | main() { 6 | bip32.BIP32 node = bip32.BIP32.fromBase58( 7 | 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'); 8 | 9 | print(HEX.encode(node.privateKey!)); 10 | // => e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35 11 | 12 | bip32.BIP32 nodeNeutered = node.neutered(); 13 | print(nodeNeutered.isNeutered()); 14 | // => true 15 | 16 | print(HEX.encode(nodeNeutered.publicKey)); 17 | // => 0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2 18 | 19 | print(nodeNeutered.toBase58()); 20 | // => xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8 21 | 22 | bip32.BIP32 child = node.derivePath('m/0/0'); 23 | print(child.toBase58()); 24 | // => xprv9ww7sMFLzJMzur2oEQDB642fbsMS4q6JRraMVTrM9bTWBq7NDS8ZpmsKVB4YF3mZecqax1fjnsPF19xnsJNfRp4RSyexacULXMKowSACTRc 25 | 26 | print(HEX.encode(child.privateKey!)); 27 | // => f26cf12f89ab91aeeb8d7324a22e8ba080829db15c9245414b073a8c342322aa 28 | 29 | bip32.BIP32 childNeutered = child.neutered(); 30 | print(childNeutered.isNeutered()); 31 | // => true 32 | 33 | print(HEX.encode(childNeutered.publicKey)); 34 | // => 02756de182c5dd4b717ea87e693006da62dbb3cddaa4a5cad2ed1f5bbab755f0f5 35 | 36 | print(childNeutered.toBase58()); 37 | // => xpub6AvUGrnEpfvJ8L7GLRkBTByQ9uBvUHp9o5VxHrFxhvzV4dSWkySpNaBoLR9FpbnwRmTa69yLHF3QfcaxbWT7gWdwws5k4dpmJvqpEuMWwnj 38 | 39 | bip32.BIP32 nodeFromSeed = bip32.BIP32 40 | .fromSeed(HEX.decode("000102030405060708090a0b0c0d0e0f") as Uint8List); 41 | print(nodeFromSeed.toBase58()); 42 | // => xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi 43 | 44 | bip32.BIP32 nodeFromPub = bip32.BIP32.fromBase58( 45 | "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"); 46 | print(nodeFromPub.toBase58()); 47 | // => xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8 48 | 49 | var message = HEX.decode( 50 | "0202020202020202020202020202020202020202020202020202020202020202") 51 | as Uint8List; 52 | var signature = nodeFromSeed.sign(message); 53 | print(signature); 54 | // => [63, 219, 20, 114, 95, 184, 192, 55, 216, 206, 126, 121, 17, 71, 64, 70, 163, 82, 247, 73, 243, 95, 30, 137, 177, 155, 100, 225, 177, 203, 217, 147, 122, 64, 208, 129, 54, 133, 113, 41, 216, 160, 191, 15, 136, 98, 235, 25, 219, 178, 70, 222, 127, 151, 135, 242, 25, 192, 161, 187, 187, 84, 81, 215] 55 | 56 | print(HEX.encode(signature)); 57 | // => 3fdb14725fb8c037d8ce7e7911474046a352f749f35f1e89b19b64e1b1cbd9937a40d08136857129d8a0bf0f8862eb19dbb246de7f9787f219c0a1bbbb5451d7 58 | 59 | print(nodeFromSeed.verify(message, signature)); 60 | // => true 61 | } 62 | -------------------------------------------------------------------------------- /lib/bip32.dart: -------------------------------------------------------------------------------- 1 | /// Support for doing something awesome. 2 | /// 3 | /// More dartdocs go here. 4 | library bip32; 5 | 6 | export 'src/bip32_base.dart'; 7 | 8 | // TODO: Export any libraries intended for clients of this package. 9 | -------------------------------------------------------------------------------- /lib/src/bip32_base.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:hex/hex.dart'; 3 | 4 | import 'utils/crypto.dart'; 5 | import 'utils/ecurve.dart' as ecc; 6 | import 'package:bs58check/bs58check.dart' as bs58check; 7 | import 'utils/wif.dart' as wif; 8 | import 'dart:convert'; 9 | 10 | // TODO: Put public facing types in this file. 11 | class Bip32Type { 12 | int public; 13 | int private; 14 | Bip32Type({required this.public, required this.private}); 15 | } 16 | 17 | class NetworkType { 18 | int wif; 19 | Bip32Type bip32; 20 | NetworkType({required this.wif, required this.bip32}); 21 | } 22 | 23 | final _BITCOIN = new NetworkType(wif: 0x80, bip32: new Bip32Type(public: 0x0488b21e, private: 0x0488ade4)); 24 | const HIGHEST_BIT = 0x80000000; 25 | const UINT31_MAX = 2147483647; // 2^31 - 1 26 | const UINT32_MAX = 4294967295; // 2^32 - 1 27 | 28 | /// Checks if you are awesome. Spoiler: you are. 29 | class BIP32 { 30 | Uint8List? _d; 31 | Uint8List? _Q; 32 | Uint8List chainCode; 33 | int depth = 0; 34 | int index = 0; 35 | NetworkType network; 36 | int parentFingerprint = 0x00000000; 37 | BIP32(this._d, this._Q, this.chainCode, this.network); 38 | 39 | Uint8List get publicKey { 40 | if (_Q == null) _Q = ecc.pointFromScalar(_d!, true)!; 41 | return _Q!; 42 | } 43 | 44 | Uint8List? get privateKey => _d; 45 | Uint8List get identifier => hash160(publicKey); 46 | Uint8List get fingerprint => identifier.sublist(0, 4); 47 | 48 | bool isNeutered() { 49 | return this._d == null; 50 | } 51 | 52 | BIP32 neutered() { 53 | final neutered = BIP32.fromPublicKey(this.publicKey, this.chainCode, this.network); 54 | neutered.depth = this.depth; 55 | neutered.index = this.index; 56 | neutered.parentFingerprint = this.parentFingerprint; 57 | return neutered; 58 | } 59 | 60 | String toBase58() { 61 | final version = (!isNeutered()) ? network.bip32.private : network.bip32.public; 62 | Uint8List buffer = new Uint8List(78); 63 | ByteData bytes = buffer.buffer.asByteData(); 64 | bytes.setUint32(0, version); 65 | bytes.setUint8(4, depth); 66 | bytes.setUint32(5, parentFingerprint); 67 | bytes.setUint32(9, index); 68 | buffer.setRange(13, 45, chainCode); 69 | if (!isNeutered()) { 70 | bytes.setUint8(45, 0); 71 | buffer.setRange(46, 78, privateKey!); 72 | } else { 73 | buffer.setRange(45, 78, publicKey); 74 | } 75 | return bs58check.encode(buffer); 76 | } 77 | 78 | String toWIF() { 79 | if (privateKey == null) { 80 | throw new ArgumentError("Missing private key"); 81 | } 82 | return wif.encode(new wif.WIF(version: network.wif, privateKey: privateKey!, compressed: true)); 83 | } 84 | 85 | BIP32 derive(int index) { 86 | if (index > UINT32_MAX || index < 0) throw new ArgumentError("Expected UInt32"); 87 | final isHardened = index >= HIGHEST_BIT; 88 | Uint8List data = new Uint8List(37); 89 | if (isHardened) { 90 | if (isNeutered()) { 91 | throw new ArgumentError("Missing private key for hardened child key"); 92 | } 93 | data[0] = 0x00; 94 | data.setRange(1, 33, privateKey!); 95 | data.buffer.asByteData().setUint32(33, index); 96 | } else { 97 | data.setRange(0, 33, publicKey); 98 | data.buffer.asByteData().setUint32(33, index); 99 | } 100 | final I = hmacSHA512(chainCode, data); 101 | final IL = I.sublist(0, 32); 102 | final IR = I.sublist(32); 103 | if (!ecc.isPrivate(IL)) { 104 | return derive(index + 1); 105 | } 106 | BIP32 hd; 107 | if (!isNeutered()) { 108 | final ki = ecc.privateAdd(privateKey!, IL); 109 | if (ki == null) return derive(index + 1); 110 | hd = BIP32.fromPrivateKey(ki, IR, network); 111 | } else { 112 | final ki = ecc.pointAddScalar(publicKey, IL, true); 113 | if (ki == null) return derive(index + 1); 114 | hd = BIP32.fromPublicKey(ki, IR, network); 115 | } 116 | hd.depth = depth + 1; 117 | hd.index = index; 118 | hd.parentFingerprint = fingerprint.buffer.asByteData().getUint32(0); 119 | return hd; 120 | } 121 | 122 | BIP32 deriveHardened(int index) { 123 | if (index > UINT31_MAX || index < 0) throw new ArgumentError("Expected UInt31"); 124 | return this.derive(index + HIGHEST_BIT); 125 | } 126 | 127 | BIP32 derivePath(String path) { 128 | final regex = new RegExp(r"^(m\/)?(\d+'?\/)*\d+'?$"); 129 | if (!regex.hasMatch(path)) throw new ArgumentError("Expected BIP32 Path"); 130 | List splitPath = path.split("/"); 131 | if (splitPath[0] == "m") { 132 | if (parentFingerprint != 0) throw new ArgumentError("Expected master, got child"); 133 | splitPath = splitPath.sublist(1); 134 | } 135 | return splitPath.fold(this, (BIP32 prevHd, String indexStr) { 136 | int index; 137 | if (indexStr.substring(indexStr.length - 1) == "'") { 138 | index = int.parse(indexStr.substring(0, indexStr.length - 1)); 139 | return prevHd.deriveHardened(index); 140 | } else { 141 | index = int.parse(indexStr); 142 | return prevHd.derive(index); 143 | } 144 | }); 145 | } 146 | 147 | sign(Uint8List hash) { 148 | return ecc.sign(hash, privateKey!); 149 | } 150 | 151 | verify(Uint8List hash, Uint8List signature) { 152 | return ecc.verify(hash, publicKey, signature); 153 | } 154 | 155 | factory BIP32.fromBase58(String string, [NetworkType? nw]) { 156 | Uint8List buffer = bs58check.decode(string); 157 | if (buffer.length != 78) throw new ArgumentError("Invalid buffer length"); 158 | NetworkType network = nw ?? _BITCOIN; 159 | ByteData bytes = buffer.buffer.asByteData(); 160 | // 4 bytes: version bytes 161 | var version = bytes.getUint32(0); 162 | if (version != network.bip32.private && version != network.bip32.public) { 163 | throw new ArgumentError("Invalid network version"); 164 | } 165 | // 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 descendants, ... 166 | var depth = buffer[4]; 167 | 168 | // 4 bytes: the fingerprint of the parent's key (0x00000000 if master key) 169 | var parentFingerprint = bytes.getUint32(5); 170 | if (depth == 0) { 171 | if (parentFingerprint != 0x00000000) throw new ArgumentError("Invalid parent fingerprint"); 172 | } 173 | 174 | // 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized. 175 | // This is encoded in MSB order. (0x00000000 if master key) 176 | var index = bytes.getUint32(9); 177 | if (depth == 0 && index != 0) throw new ArgumentError("Invalid index"); 178 | 179 | // 32 bytes: the chain code 180 | Uint8List chainCode = buffer.sublist(13, 45); 181 | BIP32 hd; 182 | 183 | // 33 bytes: private key data (0x00 + k) 184 | if (version == network.bip32.private) { 185 | if (bytes.getUint8(45) != 0x00) throw new ArgumentError("Invalid private key"); 186 | Uint8List k = buffer.sublist(46, 78); 187 | hd = BIP32.fromPrivateKey(k, chainCode, network); 188 | } else { 189 | // 33 bytes: public key data (0x02 + X or 0x03 + X) 190 | Uint8List X = buffer.sublist(45, 78); 191 | hd = BIP32.fromPublicKey(X, chainCode, network); 192 | } 193 | hd.depth = depth; 194 | hd.index = index; 195 | hd.parentFingerprint = parentFingerprint; 196 | return hd; 197 | } 198 | 199 | factory BIP32.fromPublicKey(Uint8List publicKey, Uint8List chainCode, [NetworkType? nw]) { 200 | NetworkType network = nw ?? _BITCOIN; 201 | if (!ecc.isPoint(publicKey)) { 202 | throw new ArgumentError("Point is not on the curve"); 203 | } 204 | return new BIP32(null, publicKey, chainCode, network); 205 | } 206 | 207 | factory BIP32.fromPrivateKey(Uint8List privateKey, Uint8List chainCode, [NetworkType? nw]) { 208 | NetworkType network = nw ?? _BITCOIN; 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]"); 211 | return new BIP32(privateKey, null, chainCode, network); 212 | } 213 | 214 | factory BIP32.fromSeed(Uint8List seed, [NetworkType? nw]) { 215 | if (seed.length < 16) { 216 | throw new ArgumentError("Seed should be at least 128 bits"); 217 | } 218 | if (seed.length > 64) { 219 | throw new ArgumentError("Seed should be at most 512 bits"); 220 | } 221 | NetworkType network = nw ?? _BITCOIN; 222 | final I = hmacSHA512(utf8.encode("Bitcoin seed") as Uint8List, seed); 223 | final IL = I.sublist(0, 32); 224 | final IR = I.sublist(32); 225 | return BIP32.fromPrivateKey(IL, IR, network); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /lib/src/utils/crypto.dart: -------------------------------------------------------------------------------- 1 | import "dart:typed_data"; 2 | import "package:pointycastle/digests/sha512.dart"; 3 | import "package:pointycastle/api.dart" show KeyParameter; 4 | import "package:pointycastle/macs/hmac.dart"; 5 | import "package:pointycastle/digests/ripemd160.dart"; 6 | import "package:pointycastle/digests/sha256.dart"; 7 | 8 | final ONE1 = Uint8List.fromList([1]); 9 | final ZERO1 = Uint8List.fromList([0]); 10 | 11 | Uint8List hash160(Uint8List buffer) { 12 | Uint8List _tmp = new SHA256Digest().process(buffer); 13 | return new RIPEMD160Digest().process(_tmp); 14 | } 15 | 16 | Uint8List hmacSHA512(Uint8List key,Uint8List data) { 17 | final _tmp = new HMac(new SHA512Digest(), 128)..init(new KeyParameter(key)); 18 | return _tmp.process(data); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /lib/src/utils/ecurve.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:hex/hex.dart'; 3 | import "package:pointycastle/ecc/curves/secp256k1.dart"; 4 | import "package:pointycastle/api.dart" show PrivateKeyParameter, PublicKeyParameter; 5 | import 'package:pointycastle/ecc/api.dart' show ECPrivateKey, ECPublicKey, ECSignature, ECPoint; 6 | import "package:pointycastle/signers/ecdsa_signer.dart"; 7 | import 'package:pointycastle/macs/hmac.dart'; 8 | import "package:pointycastle/digests/sha256.dart"; 9 | import 'package:pointycastle/src/utils.dart'; 10 | 11 | final ZERO32 = Uint8List.fromList(List.generate(32, (index) => 0)); 12 | final EC_GROUP_ORDER = HEX.decode("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); 13 | final EC_P = HEX.decode("fffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); 14 | final secp256k1 = new ECCurve_secp256k1(); 15 | final n = secp256k1.n; 16 | final G = secp256k1.G; 17 | BigInt nDiv2 = n >> 1; 18 | const THROW_BAD_PRIVATE = 'Expected Private'; 19 | const THROW_BAD_POINT = 'Expected Point'; 20 | const THROW_BAD_TWEAK = 'Expected Tweak'; 21 | const THROW_BAD_HASH = 'Expected Hash'; 22 | const THROW_BAD_SIGNATURE = 'Expected Signature'; 23 | 24 | bool isPrivate(Uint8List x) { 25 | if (!isScalar(x)) return false; 26 | return _compare(x, ZERO32) > 0 && // > 0 27 | _compare(x, EC_GROUP_ORDER as Uint8List) < 0; // < G 28 | } 29 | 30 | bool isPoint(Uint8List p) { 31 | if (p.length < 33) { 32 | return false; 33 | } 34 | var t = p[0]; 35 | var x = p.sublist(1, 33); 36 | 37 | if (_compare(x, ZERO32) == 0) { 38 | return false; 39 | } 40 | if (_compare(x, EC_P as Uint8List) == 1) { 41 | return false; 42 | } 43 | try { 44 | decodeFrom(p); 45 | } catch (err) { 46 | return false; 47 | } 48 | if ((t == 0x02 || t == 0x03) && p.length == 33) { 49 | return true; 50 | } 51 | var y = p.sublist(33); 52 | if (_compare(y, ZERO32) == 0) { 53 | return false; 54 | } 55 | if (_compare(y, EC_P as Uint8List) == 1) { 56 | return false; 57 | } 58 | if (t == 0x04 && p.length == 65) { 59 | return true; 60 | } 61 | return false; 62 | } 63 | 64 | bool isScalar(Uint8List x) { 65 | return x.length == 32; 66 | } 67 | 68 | bool isOrderScalar(x) { 69 | if (!isScalar(x)) return false; 70 | return _compare(x, EC_GROUP_ORDER as Uint8List) < 0; // < G 71 | } 72 | 73 | bool isSignature(Uint8List value) { 74 | Uint8List r = value.sublist(0, 32); 75 | Uint8List s = value.sublist(32, 64); 76 | 77 | return value.length == 64 && _compare(r, EC_GROUP_ORDER as Uint8List) < 0 && _compare(s, EC_GROUP_ORDER as Uint8List) < 0; 78 | } 79 | 80 | bool _isPointCompressed(Uint8List p) { 81 | return p[0] != 0x04; 82 | } 83 | 84 | bool assumeCompression(bool? value, Uint8List? pubkey) { 85 | if (value == null && pubkey != null) return _isPointCompressed(pubkey); 86 | if (value == null) return true; 87 | return value; 88 | } 89 | 90 | Uint8List? pointFromScalar(Uint8List d, bool _compressed) { 91 | if (!isPrivate(d)) throw new ArgumentError(THROW_BAD_PRIVATE); 92 | BigInt dd = fromBuffer(d); 93 | ECPoint pp = (G * dd) as ECPoint; 94 | if (pp.isInfinity) return null; 95 | return getEncoded(pp, _compressed); 96 | } 97 | 98 | Uint8List? pointAddScalar(Uint8List p, Uint8List tweak, bool _compressed) { 99 | if (!isPoint(p)) throw new ArgumentError(THROW_BAD_POINT); 100 | if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK); 101 | bool compressed = assumeCompression(_compressed, p); 102 | ECPoint? pp = decodeFrom(p); 103 | if (_compare(tweak, ZERO32) == 0) return getEncoded(pp, compressed); 104 | BigInt tt = fromBuffer(tweak); 105 | ECPoint qq = (G * tt) as ECPoint; 106 | ECPoint uu = (pp! + qq) as ECPoint; 107 | if (uu.isInfinity) return null; 108 | return getEncoded(uu, compressed); 109 | } 110 | 111 | Uint8List? privateAdd(Uint8List d, Uint8List tweak) { 112 | if (!isPrivate(d)) throw new ArgumentError(THROW_BAD_PRIVATE); 113 | if (!isOrderScalar(tweak)) throw new ArgumentError(THROW_BAD_TWEAK); 114 | BigInt dd = fromBuffer(d); 115 | BigInt tt = fromBuffer(tweak); 116 | Uint8List dt = toBuffer((dd + tt) % n); 117 | 118 | if (dt.length < 32) { 119 | Uint8List padLeadingZero = Uint8List(32 - dt.length); 120 | dt = Uint8List.fromList(padLeadingZero + dt); 121 | } 122 | 123 | if (!isPrivate(dt)) return null; 124 | return dt; 125 | } 126 | 127 | Uint8List sign(Uint8List hash, Uint8List x) { 128 | if (!isScalar(hash)) throw new ArgumentError(THROW_BAD_HASH); 129 | if (!isPrivate(x)) throw new ArgumentError(THROW_BAD_PRIVATE); 130 | ECSignature sig = deterministicGenerateK(hash, x); 131 | Uint8List buffer = new Uint8List(64); 132 | buffer.setRange(0, 32, _encodeBigInt(sig.r)); 133 | var s; 134 | if (sig.s.compareTo(nDiv2) > 0) { 135 | s = n - sig.s; 136 | } else { 137 | s = sig.s; 138 | } 139 | buffer.setRange(32, 64, _encodeBigInt(s)); 140 | return buffer; 141 | } 142 | 143 | bool verify(Uint8List hash, Uint8List q, Uint8List signature) { 144 | if (!isScalar(hash)) throw new ArgumentError(THROW_BAD_HASH); 145 | if (!isPoint(q)) throw new ArgumentError(THROW_BAD_POINT); 146 | // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (1, isSignature enforces '< n - 1') 147 | if (!isSignature(signature)) throw new ArgumentError(THROW_BAD_SIGNATURE); 148 | 149 | ECPoint? Q = decodeFrom(q); 150 | BigInt r = fromBuffer(signature.sublist(0, 32)); 151 | BigInt s = fromBuffer(signature.sublist(32, 64)); 152 | 153 | final signer = new ECDSASigner(null, new HMac(new SHA256Digest(), 64)); 154 | signer.init(false, new PublicKeyParameter(new ECPublicKey(Q, secp256k1))); 155 | return signer.verifySignature(hash, new ECSignature(r, s)); 156 | /* STEP BY STEP 157 | // 1.4.1 Enforce r and s are both integers in the interval [1, n − 1] (2, enforces '> 0') 158 | if (r.compareTo(n) >= 0) return false; 159 | if (s.compareTo(n) >= 0) return false; 160 | 161 | // 1.4.2 H = Hash(M), already done by the user 162 | // 1.4.3 e = H 163 | BigInt e = fromBuffer(hash); 164 | 165 | BigInt sInv = s.modInverse(n); 166 | BigInt u1 = (e * sInv) % n; 167 | BigInt u2 = (r * sInv) % n; 168 | 169 | // 1.4.5 Compute R = (xR, yR) 170 | // R = u1G + u2Q 171 | ECPoint R = G * u1 + Q * u2; 172 | 173 | // 1.4.5 (cont.) Enforce R is not at infinity 174 | if (R.isInfinity) return false; 175 | 176 | // 1.4.6 Convert the field element R.x to an integer 177 | BigInt xR = R.x.toBigInteger(); 178 | 179 | // 1.4.7 Set v = xR mod n 180 | BigInt v = xR % n; 181 | 182 | // 1.4.8 If v = r, output "valid", and if v != r, output "invalid" 183 | return v.compareTo(r) == 0; 184 | */ 185 | } 186 | 187 | /// Decode a BigInt from bytes in big-endian encoding. 188 | BigInt _decodeBigInt(List bytes) { 189 | BigInt result = new BigInt.from(0); 190 | for (int i = 0; i < bytes.length; i++) { 191 | result += new BigInt.from(bytes[bytes.length - i - 1]) << (8 * i); 192 | } 193 | return result; 194 | } 195 | 196 | var _byteMask = new BigInt.from(0xff); 197 | 198 | /// Encode a BigInt into bytes using big-endian encoding. 199 | Uint8List _encodeBigInt(BigInt number) { 200 | int needsPaddingByte; 201 | int rawSize; 202 | 203 | if (number > BigInt.zero) { 204 | rawSize = (number.bitLength + 7) >> 3; 205 | needsPaddingByte = ((number >> (rawSize - 1) * 8) & negativeFlag) == negativeFlag ? 1 : 0; 206 | 207 | if (rawSize < 32) { 208 | needsPaddingByte = 1; 209 | } 210 | } else { 211 | needsPaddingByte = 0; 212 | rawSize = (number.bitLength + 8) >> 3; 213 | } 214 | 215 | final size = rawSize < 32 ? rawSize + needsPaddingByte : rawSize; 216 | var result = new Uint8List(size); 217 | for (int i = 0; i < size; i++) { 218 | result[size - i - 1] = (number & _byteMask).toInt(); 219 | number = number >> 8; 220 | } 221 | return result; 222 | } 223 | 224 | BigInt fromBuffer(Uint8List d) { 225 | return _decodeBigInt(d); 226 | } 227 | 228 | Uint8List toBuffer(BigInt d) { 229 | return _encodeBigInt(d); 230 | } 231 | 232 | ECPoint? decodeFrom(Uint8List P) { 233 | return secp256k1.curve.decodePoint(P); 234 | } 235 | 236 | Uint8List getEncoded(ECPoint? P, compressed) { 237 | return P!.getEncoded(compressed); 238 | } 239 | 240 | ECSignature deterministicGenerateK(Uint8List hash, Uint8List x) { 241 | final signer = new ECDSASigner(null, new HMac(new SHA256Digest(), 64)); 242 | var pkp = new PrivateKeyParameter(new ECPrivateKey(_decodeBigInt(x), secp256k1)); 243 | signer.init(true, pkp); 244 | // signer.init(false, new PublicKeyParameter(new ECPublicKey(secp256k1.curve.decodePoint(x), secp256k1))); 245 | return signer.generateSignature(hash) as ECSignature; 246 | } 247 | 248 | int _compare(Uint8List a, Uint8List b) { 249 | BigInt aa = fromBuffer(a); 250 | BigInt bb = fromBuffer(b); 251 | if (aa == bb) return 0; 252 | if (aa > bb) return 1; 253 | return -1; 254 | } 255 | -------------------------------------------------------------------------------- /lib/src/utils/wif.dart: -------------------------------------------------------------------------------- 1 | import "package:bs58check/bs58check.dart" as bs58check; 2 | import "dart:typed_data"; 3 | 4 | class WIF { 5 | int version; 6 | Uint8List privateKey; 7 | bool compressed; 8 | WIF({required this.version, required this.privateKey, required this.compressed}); 9 | } 10 | 11 | WIF decodeRaw(Uint8List buffer, [int? version]) { 12 | if (version != null && buffer[0] != version) { 13 | throw new ArgumentError("Invalid network version"); 14 | } 15 | if (buffer.length == 33) { 16 | return new WIF(version: buffer[0], privateKey: buffer.sublist(1, 33), compressed: false); 17 | } 18 | if (buffer.length != 34) { 19 | throw new ArgumentError("Invalid WIF length"); 20 | } 21 | if (buffer[33] != 0x01) { 22 | throw new ArgumentError("Invalid compression flag"); 23 | } 24 | return new WIF(version: buffer[0], privateKey: buffer.sublist(1, 33), compressed: true); 25 | } 26 | 27 | Uint8List encodeRaw(int version, Uint8List privateKey, bool compressed) { 28 | if (privateKey.length != 32) { 29 | throw new ArgumentError("Invalid privateKey length"); 30 | } 31 | Uint8List result = new Uint8List(compressed ? 34 : 33); 32 | ByteData bytes = result.buffer.asByteData(); 33 | bytes.setUint8(0, version); 34 | result.setRange(1, 33, privateKey); 35 | if (compressed) { 36 | result[33] = 0x01; 37 | } 38 | return result; 39 | } 40 | 41 | WIF decode(String string, [int? version]) { 42 | return decodeRaw(bs58check.decode(string), version); 43 | } 44 | 45 | String encode(WIF wif) { 46 | return bs58check.encode(encodeRaw(wif.version, wif.privateKey, wif.compressed)); 47 | } 48 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: bip32 2 | description: A BIP32 (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) compatible library for Flutter writing by Dart. 3 | version: 2.0.0 4 | homepage: https://github.com/dart-bitcoin/bip32-dart 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | pointycastle: ^3.0.1 11 | hex: ^0.2.0 12 | bs58check: ^1.0.2 13 | 14 | dev_dependencies: 15 | test: ^1.17.1 -------------------------------------------------------------------------------- /test/bip32_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:bip32/bip32.dart'; 3 | import 'package:hex/hex.dart'; 4 | import 'package:test/test.dart'; 5 | import 'dart:io'; 6 | import 'dart:convert'; 7 | 8 | final LITECOIN = new NetworkType(bip32: new Bip32Type(private: 0x019d9cfe, public: 0x019da462), wif: 0xb0); 9 | List validAll = []; 10 | 11 | void main() { 12 | Map fixtures = json.decode(File('./test/fixtures.json').readAsStringSync(encoding: utf8)); 13 | (fixtures['valid'] as List).forEach((f) { 14 | f['master']['network'] = f['network']; 15 | f['master']['children'] = f['children']; 16 | f['master']['comment'] = f['comment']; 17 | (f['children'] as List).forEach((fc) { 18 | fc['network'] = f['network']; 19 | validAll.add(fc); 20 | }); 21 | validAll.add(f['master']); 22 | validAll.forEach((ff) { 23 | group(ff['comment'] ?? ff['base58Priv'], () { 24 | setUp(() {}); 25 | var network; 26 | if (ff['network'] == 'litecoin') { 27 | network = LITECOIN; 28 | } 29 | var hdPrv = BIP32.fromBase58(ff['base58Priv'], network); 30 | test('works for private key -> HD wallet', () { 31 | verify(hdPrv, true, ff, network); 32 | }); 33 | 34 | var hdPub = BIP32.fromBase58(ff['base58'], network); 35 | test('works for public key -> HD wallet', () { 36 | verify(hdPub, false, ff, network); 37 | }); 38 | 39 | if (ff['seed'] != null) { 40 | var seed = HEX.decode(ff['seed']); 41 | var hdSeed = BIP32.fromSeed(seed as Uint8List, network); 42 | test('works for seed -> HD wallet', () { 43 | verify(hdSeed, true, ff, network); 44 | }); 45 | } 46 | }); 47 | }); 48 | }); 49 | 50 | test('fromBase58 throws', () { 51 | (fixtures['invalid']['fromBase58'] as List).forEach((f) { 52 | var network; 53 | if (f['network'] != null && f['network'] == 'litecoin') network = LITECOIN; 54 | BIP32? hd; 55 | try { 56 | hd = BIP32.fromBase58(f['string'], network); 57 | } catch (err) { 58 | expect((err as ArgumentError).message, f['exception']); 59 | } finally { 60 | expect(hd, null); 61 | } 62 | }); 63 | }); 64 | 65 | test('works for Private -> public (neutered)', () { 66 | final f = fixtures['valid'][1]; 67 | final c = f['master']['children'][0]; 68 | final master = BIP32.fromBase58(f['master']['base58Priv'] as String); 69 | final child = master.derive(c['m']).neutered(); 70 | expect(child.toBase58(), c['base58']); 71 | }); 72 | 73 | test('works for Private -> public (neutered, hardened)', () { 74 | final f = fixtures['valid'][0]; 75 | final c = f['master']['children'][0]; 76 | final master = BIP32.fromBase58(f['master']['base58Priv'] as String); 77 | final child = master.deriveHardened(c['m']).neutered(); 78 | expect(child.toBase58(), c['base58']); 79 | }); 80 | 81 | test('works for Public -> public', () { 82 | final f = fixtures['valid'][1]; 83 | final c = f['master']['children'][0]; 84 | final master = BIP32.fromBase58(f['master']['base58'] as String); 85 | final child = master.derive(c['m']); 86 | expect(child.toBase58(), c['base58']); 87 | }); 88 | 89 | test('throws on Public -> public (hardened)', () { 90 | final f = fixtures['valid'][0]; 91 | final c = f['master']['children'][0]; 92 | final master = BIP32.fromBase58(f['master']['base58'] as String); 93 | BIP32? hd; 94 | try { 95 | hd = master.deriveHardened(c['m']); 96 | } catch (err) { 97 | expect((err as ArgumentError).message, "Missing private key for hardened child key"); 98 | } finally { 99 | expect(hd, null); 100 | } 101 | }); 102 | 103 | test('throws on wrong types', () { 104 | final f = fixtures['valid'][0]; 105 | final master = BIP32.fromBase58(f['master']['base58'] as String); 106 | (fixtures['invalid']['derive'] as List).forEach((fx) { 107 | var hd; 108 | try { 109 | hd = master.derive(fx); 110 | } catch (err) { 111 | expect((err as ArgumentError).message, "Expected UInt32"); 112 | } finally { 113 | expect(hd, null); 114 | } 115 | }); 116 | (fixtures['invalid']['deriveHardened'] as List).forEach((fx) { 117 | var hd; 118 | try { 119 | hd = master.deriveHardened(fx); 120 | } catch (err) { 121 | expect((err as ArgumentError).message, "Expected UInt31"); 122 | } finally { 123 | expect(hd, null); 124 | } 125 | }); 126 | (fixtures['invalid']['derivePath'] as List).forEach((fx) { 127 | var hd; 128 | try { 129 | hd = master.derivePath(fx); 130 | } catch (err) { 131 | expect((err as ArgumentError).message, "Expected BIP32 Path"); 132 | } finally { 133 | expect(hd, null); 134 | } 135 | }); 136 | var hdFPrv1, hdFPrv2; 137 | final ZERO32 = Uint8List.fromList(List.generate(32, (index) => 0)); 138 | final ONE32 = Uint8List.fromList(List.generate(32, (index) => 1)); 139 | try { 140 | hdFPrv1 = BIP32.fromPrivateKey(new Uint8List(2), ONE32); 141 | } catch (err) { 142 | expect((err as ArgumentError).message, "Expected property privateKey of type Buffer(Length: 32)"); 143 | } finally { 144 | expect(hdFPrv1, null); 145 | } 146 | try { 147 | hdFPrv2 = BIP32.fromPrivateKey(ZERO32, ONE32); 148 | } catch (err) { 149 | expect((err as ArgumentError).message, "Private key not in range [1, n]"); 150 | } finally { 151 | expect(hdFPrv2, null); 152 | } 153 | }); 154 | 155 | test("works when private key has leading zeros", () { 156 | const key = "xprv9s21ZrQH143K3ckY9DgU79uMTJkQRLdbCCVDh81SnxTgPzLLGax6uHeBULTtaEtcAvKjXfT7ZWtHzKjTpujMkUd9dDb8msDeAfnJxrgAYhr"; 157 | BIP32 hdkey = BIP32.fromBase58(key); 158 | expect(HEX.encode(hdkey.privateKey!), "00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd"); 159 | BIP32 child = hdkey.derivePath("m/44'/0'/0'/0/0'"); 160 | expect(HEX.encode(child.privateKey!), "3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb"); 161 | }); 162 | 163 | test('derive', () { 164 | final hd = BIP32.fromBase58('xprv9s21ZrQH143K3Jpuz63XbuGs9CH9xG4sniVBBRVm6AJR57D9arxWz6FkXF3JSxSK7jUmVA11AdWa6ZsUtwGztE4QT5i8Y457RRPvMCc39rY'); 165 | final d = hd.derivePath("m/1'/199007533'/627785449'/1521366139'/1'"); 166 | expect(d.toBase58(), 'xprvA39a1i4ieYqGUQ7G1KGnaGzGwm7v3emjms3QN4jZ3HPeubXjshA3XjD5XFaiNgWFvoyC2NV5jN4eFcsVhkrWkvwR4qjdPbue3kpt6Ur3JRf'); 167 | }); 168 | 169 | test("fromSeed", () { 170 | (fixtures['invalid']['fromSeed'] as List).forEach((f) { 171 | var hd; 172 | try { 173 | hd = BIP32.fromSeed(HEX.decode(f['seed']) as Uint8List); 174 | } catch (err) { 175 | expect((err as ArgumentError).message, f['exception']); 176 | } finally { 177 | expect(hd, null); 178 | } 179 | }); 180 | }); 181 | 182 | test("ecdsa", () { 183 | Uint8List seed = Uint8List.fromList(List.generate(32, (index) => 1)); 184 | Uint8List hash = Uint8List.fromList(List.generate(32, (index) => 2)); 185 | String sigStr = "9636ee2fac31b795a308856b821ebe297dda7b28220fb46ea1fbbd7285977cc04c82b734956246a0f15a9698f03f546d8d96fe006c8e7bd2256ca7c8229e6f5c"; 186 | Uint8List signature = HEX.decode(sigStr) as Uint8List; 187 | BIP32 node = BIP32.fromSeed(seed); 188 | expect(HEX.encode(node.sign(hash)), sigStr); 189 | expect(node.verify(hash, signature), true); 190 | expect(node.verify(seed, signature), false); 191 | }); 192 | } 193 | 194 | void verify(BIP32 hd, prv, f, network) { 195 | expect(HEX.encode(hd.chainCode), f['chainCode']); 196 | expect(hd.depth, f['depth'] == null ? 0 : f['depth']); 197 | expect(hd.index, f['index'] == null ? 0 : f['index']); 198 | expect(HEX.encode(hd.fingerprint), f['fingerprint']); 199 | expect(HEX.encode(hd.identifier), f['identifier']); 200 | expect(HEX.encode(hd.publicKey), f['pubKey']); 201 | if (prv) { 202 | expect(hd.toBase58(), f['base58Priv']); 203 | expect(HEX.encode(hd.privateKey!), f['privKey']); 204 | expect(hd.toWIF(), f['wif']); 205 | } else { 206 | expect(hd.privateKey, null); 207 | } 208 | expect(hd.neutered().toBase58(), f['base58']); 209 | expect(hd.isNeutered(), !prv); 210 | 211 | if (f['children'] == null) return; 212 | if (!prv && (f['children'] as List).map((fc) => fc['hardened']).contains(true)) return; 213 | 214 | (f['children'] as List).forEach((cf) { 215 | var chd = hd.derivePath(cf['path']); 216 | verify(chd, prv, cf, network); 217 | var chdNoM = hd.derivePath((cf['path'] as String).substring(2)); // no m/ 218 | verify(chdNoM, prv, cf, network); 219 | }); 220 | 221 | // test deriving path from successive children 222 | var shd = hd; 223 | (f['children'] as List).forEach((cf) { 224 | if (cf['m'] == null) return; 225 | if (cf['hardened'] != null && cf['hardened'] as bool) { 226 | shd = shd.deriveHardened(cf['m']); 227 | } else { 228 | // verify any publicly derived children 229 | if (cf['base58'] != null) verify(shd.neutered().derive(cf['m']), false, cf, network); 230 | shd = shd.derive(cf['m']); 231 | verify(shd, prv, cf, network); 232 | } 233 | }); 234 | } 235 | -------------------------------------------------------------------------------- /test/ecc_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:typed_data'; 2 | import 'package:bip32/bip32.dart'; 3 | import 'package:hex/hex.dart'; 4 | import 'package:test/test.dart'; 5 | import '../lib/src/utils/ecurve.dart' as ecc; 6 | 7 | final defichain_testnet = NetworkType(bip32: Bip32Type(private: 0x04358394, public: 0x043587cf), wif: 0xef); 8 | 9 | void main() { 10 | group("curve test", () { 11 | test("test private key generation", () { 12 | // seed 13 | //55b18e96ce3964ef2c81ad69249eca6d42682c11fbe525df6671fcbf0c2be902 private key 14 | 15 | final hdSeed = BIP32.fromSeed( 16 | HEX.decode("6607599b768ce88470b3b20919f9c63bff663e2f1ec3e3072d22fd9da3847784c361d5accc3b411019f5c81dd3e4ccf9fd1fddb232bfc9bfe23864e2e6ee793f") as Uint8List, 17 | defichain_testnet); 18 | 19 | final xMasterPriv = BIP32.fromSeed(hdSeed.privateKey!, defichain_testnet); 20 | final privateKey = xMasterPriv.derivePath("m/0'/0'/0'"); 21 | 22 | final privateKeyHex = HEX.encode(privateKey.privateKey!); 23 | 24 | expect("55b18e96ce3964ef2c81ad69249eca6d42682c11fbe525df6671fcbf0c2be902", privateKeyHex); 25 | }); 26 | 27 | test("test sign", () { 28 | ecc.sign(HEX.decode("b11d3d5e4ae12b89d5e3872ccc7d1f96d29b0ab888b67dccf1be5164b811cdbe") as Uint8List, 29 | HEX.decode("55b18e96ce3964ef2c81ad69249eca6d42682c11fbe525df6671fcbf0c2be902") as Uint8List); 30 | }); 31 | }); 32 | 33 | group('Failing ecc test', () { 34 | test('Should fail with the third set of key-msg', () { 35 | var key, msg, sig; 36 | 37 | key = 'f92ca9fe5f77afa489214a7ba2bd6b36d30dd4acdb55c70d1378b5c45c831820'; 38 | msg = '045a7448dffff67c08023d16279c57c0bd16af6467580c183cc4672e768b8a77'; 39 | 40 | sig = ecc.sign(HEX.decode(msg) as Uint8List, HEX.decode(key) as Uint8List); 41 | print(HEX.encode(sig)); 42 | 43 | key = '8c39fb3d889b6be22850254dc7ce3247c559d9f968785d88251ee4457633e335'; 44 | msg = '6ad99bc30926fd79cf5a977044088fd298a59728c5b0c1040713d3a8e1c1e69b'; 45 | 46 | sig = ecc.sign(HEX.decode(msg) as Uint8List, HEX.decode(key) as Uint8List); 47 | print(HEX.encode(sig)); 48 | 49 | key = '2de619ea940d31927e0a7bbd5ebf855ab66f488b94a65cf929a1eb70b54ec771'; 50 | msg = 'd82a6db2583353856dd4dadc38cdb6373b092ea1f026fa04f03aa3c4e972454f'; 51 | 52 | sig = ecc.sign(HEX.decode(msg) as Uint8List, HEX.decode(key) as Uint8List); 53 | print(HEX.encode(sig)); 54 | }); 55 | }); 56 | } 57 | -------------------------------------------------------------------------------- /test/fixtures.json: -------------------------------------------------------------------------------- 1 | { 2 | "valid": [ 3 | { 4 | "network": "bitcoin", 5 | "master": { 6 | "seed": "000102030405060708090a0b0c0d0e0f", 7 | "wif": "L52XzL2cMkHxqxBXRyEpnPQZGUs3uKiL3R11XbAdHigRzDozKZeW", 8 | "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", 9 | "privKey": "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", 10 | "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", 11 | "base58": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8", 12 | "base58Priv": "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi", 13 | "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", 14 | "fingerprint": "3442193e" 15 | }, 16 | "children": [ 17 | { 18 | "path": "m/0'", 19 | "m": 0, 20 | "hardened": true, 21 | "wif": "L5BmPijJjrKbiUfG4zbiFKNqkvuJ8usooJmzuD7Z8dkRoTThYnAT", 22 | "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", 23 | "privKey": "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea", 24 | "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", 25 | "base58": "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw", 26 | "base58Priv": "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7", 27 | "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", 28 | "fingerprint": "5c1bd648", 29 | "index": 2147483648, 30 | "depth": 1 31 | }, 32 | { 33 | "path": "m/0'/1", 34 | "m": 1, 35 | "wif": "KyFAjQ5rgrKvhXvNMtFB5PCSKUYD1yyPEe3xr3T34TZSUHycXtMM", 36 | "pubKey": "03501e454bf00751f24b1b489aa925215d66af2234e3891c3b21a52bedb3cd711c", 37 | "privKey": "3c6cb8d0f6a264c91ea8b5030fadaa8e538b020f0a387421a12de9319dc93368", 38 | "chainCode": "2a7857631386ba23dacac34180dd1983734e444fdbf774041578e9b6adb37c19", 39 | "base58": "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ", 40 | "base58Priv": "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs", 41 | "identifier": "bef5a2f9a56a94aab12459f72ad9cf8cf19c7bbe", 42 | "fingerprint": "bef5a2f9", 43 | "index": 1, 44 | "depth": 2 45 | }, 46 | { 47 | "path": "m/0'/1/2'", 48 | "m": 2, 49 | "hardened": true, 50 | "wif": "L43t3od1Gh7Lj55Bzjj1xDAgJDcL7YFo2nEcNaMGiyRZS1CidBVU", 51 | "pubKey": "0357bfe1e341d01c69fe5654309956cbea516822fba8a601743a012a7896ee8dc2", 52 | "privKey": "cbce0d719ecf7431d88e6a89fa1483e02e35092af60c042b1df2ff59fa424dca", 53 | "chainCode": "04466b9cc8e161e966409ca52986c584f07e9dc81f735db683c3ff6ec7b1503f", 54 | "base58": "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5", 55 | "base58Priv": "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM", 56 | "identifier": "ee7ab90cde56a8c0e2bb086ac49748b8db9dce72", 57 | "fingerprint": "ee7ab90c", 58 | "index": 2147483650, 59 | "depth": 3 60 | }, 61 | { 62 | "path": "m/0'/1/2'/2", 63 | "m": 2, 64 | "wif": "KwjQsVuMjbCP2Zmr3VaFaStav7NvevwjvvkqrWd5Qmh1XVnCteBR", 65 | "pubKey": "02e8445082a72f29b75ca48748a914df60622a609cacfce8ed0e35804560741d29", 66 | "privKey": "0f479245fb19a38a1954c5c7c0ebab2f9bdfd96a17563ef28a6a4b1a2a764ef4", 67 | "chainCode": "cfb71883f01676f587d023cc53a35bc7f88f724b1f8c2892ac1275ac822a3edd", 68 | "base58": "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV", 69 | "base58Priv": "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334", 70 | "identifier": "d880d7d893848509a62d8fb74e32148dac68412f", 71 | "fingerprint": "d880d7d8", 72 | "index": 2, 73 | "depth": 4 74 | }, 75 | { 76 | "path": "m/0'/1/2'/2/1000000000", 77 | "m": 1000000000, 78 | "wif": "Kybw8izYevo5xMh1TK7aUr7jHFCxXS1zv8p3oqFz3o2zFbhRXHYs", 79 | "pubKey": "022a471424da5e657499d1ff51cb43c47481a03b1e77f951fe64cec9f5a48f7011", 80 | "privKey": "471b76e389e528d6de6d816857e012c5455051cad6660850e58372a6c3e6e7c8", 81 | "chainCode": "c783e67b921d2beb8f6b389cc646d7263b4145701dadd2161548a8b078e65e9e", 82 | "base58": "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy", 83 | "base58Priv": "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76", 84 | "identifier": "d69aa102255fed74378278c7812701ea641fdf32", 85 | "fingerprint": "d69aa102", 86 | "index": 1000000000, 87 | "depth": 5 88 | } 89 | ] 90 | }, 91 | { 92 | "network": "bitcoin", 93 | "master": { 94 | "seed": "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542", 95 | "wif": "KyjXhyHF9wTphBkfpxjL8hkDXDUSbE3tKANT94kXSyh6vn6nKaoy", 96 | "pubKey": "03cbcaa9c98c877a26977d00825c956a238e8dddfbd322cce4f74b0b5bd6ace4a7", 97 | "privKey": "4b03d6fc340455b363f51020ad3ecca4f0850280cf436c70c727923f6db46c3e", 98 | "chainCode": "60499f801b896d83179a4374aeb7822aaeaceaa0db1f85ee3e904c4defbd9689", 99 | "base58": "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB", 100 | "base58Priv": "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U", 101 | "identifier": "bd16bee53961a47d6ad888e29545434a89bdfe95", 102 | "fingerprint": "bd16bee5" 103 | }, 104 | "children": [ 105 | { 106 | "path": "m/0", 107 | "m": 0, 108 | "wif": "L2ysLrR6KMSAtx7uPqmYpoTeiRzydXBattRXjXz5GDFPrdfPzKbj", 109 | "pubKey": "02fc9e5af0ac8d9b3cecfe2a888e2117ba3d089d8585886c9c826b6b22a98d12ea", 110 | "privKey": "abe74a98f6c7eabee0428f53798f0ab8aa1bd37873999041703c742f15ac7e1e", 111 | "chainCode": "f0909affaa7ee7abe5dd4e100598d4dc53cd709d5a5c2cac40e7412f232f7c9c", 112 | "base58": "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH", 113 | "base58Priv": "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt", 114 | "identifier": "5a61ff8eb7aaca3010db97ebda76121610b78096", 115 | "fingerprint": "5a61ff8e", 116 | "index": 0, 117 | "depth": 1 118 | }, 119 | { 120 | "path": "m/0/2147483647'", 121 | "m": 2147483647, 122 | "hardened": true, 123 | "wif": "L1m5VpbXmMp57P3knskwhoMTLdhAAaXiHvnGLMribbfwzVRpz2Sr", 124 | "pubKey": "03c01e7425647bdefa82b12d9bad5e3e6865bee0502694b94ca58b666abc0a5c3b", 125 | "privKey": "877c779ad9687164e9c2f4f0f4ff0340814392330693ce95a58fe18fd52e6e93", 126 | "chainCode": "be17a268474a6bb9c61e1d720cf6215e2a88c5406c4aee7b38547f585c9a37d9", 127 | "base58": "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a", 128 | "base58Priv": "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9", 129 | "identifier": "d8ab493736da02f11ed682f88339e720fb0379d1", 130 | "fingerprint": "d8ab4937", 131 | "index": 4294967295, 132 | "depth": 2 133 | }, 134 | { 135 | "path": "m/0/2147483647'/1", 136 | "m": 1, 137 | "wif": "KzyzXnznxSv249b4KuNkBwowaN3akiNeEHy5FWoPCJpStZbEKXN2", 138 | "pubKey": "03a7d1d856deb74c508e05031f9895dab54626251b3806e16b4bd12e781a7df5b9", 139 | "privKey": "704addf544a06e5ee4bea37098463c23613da32020d604506da8c0518e1da4b7", 140 | "chainCode": "f366f48f1ea9f2d1d3fe958c95ca84ea18e4c4ddb9366c336c927eb246fb38cb", 141 | "base58": "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon", 142 | "base58Priv": "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef", 143 | "identifier": "78412e3a2296a40de124307b6485bd19833e2e34", 144 | "fingerprint": "78412e3a", 145 | "index": 1, 146 | "depth": 3 147 | }, 148 | { 149 | "path": "m/0/2147483647'/1/2147483646'", 150 | "m": 2147483646, 151 | "hardened": true, 152 | "wif": "L5KhaMvPYRW1ZoFmRjUtxxPypQ94m6BcDrPhqArhggdaTbbAFJEF", 153 | "pubKey": "02d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0", 154 | "privKey": "f1c7c871a54a804afe328b4c83a1c33b8e5ff48f5087273f04efa83b247d6a2d", 155 | "chainCode": "637807030d55d01f9a0cb3a7839515d796bd07706386a6eddf06cc29a65a0e29", 156 | "base58": "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL", 157 | "base58Priv": "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc", 158 | "identifier": "31a507b815593dfc51ffc7245ae7e5aee304246e", 159 | "fingerprint": "31a507b8", 160 | "index": 4294967294, 161 | "depth": 4 162 | }, 163 | { 164 | "path": "m/0/2147483647'/1/2147483646'/2", 165 | "m": 2, 166 | "wif": "L3WAYNAZPxx1fr7KCz7GN9nD5qMBnNiqEJNJMU1z9MMaannAt4aK", 167 | "pubKey": "024d902e1a2fc7a8755ab5b694c575fce742c48d9ff192e63df5193e4c7afe1f9c", 168 | "privKey": "bb7d39bdb83ecf58f2fd82b6d918341cbef428661ef01ab97c28a4842125ac23", 169 | "chainCode": "9452b549be8cea3ecb7a84bec10dcfd94afe4d129ebfd3b3cb58eedf394ed271", 170 | "base58": "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt", 171 | "base58Priv": "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j", 172 | "identifier": "26132fdbe7bf89cbc64cf8dafa3f9f88b8666220", 173 | "fingerprint": "26132fdb", 174 | "index": 2, 175 | "depth": 5 176 | } 177 | ] 178 | }, 179 | { 180 | "comment": "Private key has leading zeros (seed vers.)", 181 | "network": "bitcoin", 182 | "master": { 183 | "seed": "d13de7bd1e54422d1a3b3b699a27fb460de2849e7e66a005c647e8e4a54075cb", 184 | "wif": "KwDiCU5bs8xQwsRgxjhkcJcVuR7NE4Mei8X9uSAVviVTE7JmMoS6", 185 | "pubKey": "0298ccc720d5dea817c7077605263bae52bca083cf8888fee77ff4c1b4797ee180", 186 | "privKey": "0000081d1e4bad6731c84450c9a3dbb70e8ba30118d3419f2c74077b7996a078", 187 | "chainCode": "c23ab32b36ddff49fae350a1bed8ec6b4d9fc252238dd789b7273ba4416054eb", 188 | "base58": "xpub661MyMwAqRbcGUbHLLJ5n2DzFAt8mmaDxbmbdimh68m8EiXGEQPiJya4BJat5yMzy4e68VSUoLGCu5uvzf8dUoGvwuJsLE6F1cibmWsxFNn", 189 | "base58Priv": "xprv9s21ZrQH143K3zWpEJm5QtHFh93eNJrNbNqzqLN5XoE9MvC7gs5TmBFaL2PpaXpDc8FBYVe5EChc73ApjSQ5fWsXS7auHy1MmG6hdpywE1q", 190 | "identifier": "1a87677be6f73cc9655e8b4c5d2fd0aeeb1b23c7", 191 | "fingerprint": "1a87677b" 192 | }, 193 | "children": [ 194 | { 195 | "hardened": true, 196 | "path": "m/44'/0'/0'/0/0'", 197 | "wif": "L3z3MSqZtDQ1FPHKi7oWf1nc9rMEGFtZUDCoFa7n4F695g5qZiSu", 198 | "pubKey": "027c3591221e28939e45f8ea297d62c3640ebb09d7058b01d09c963d984a40ad49", 199 | "privKey": "c9d464f63c78ed923b5fed4917bbbbd6936543baf12227067ddc99b63a54a058", 200 | "chainCode": "ca27553aa89617e982e621637d6478f564b32738f8bbe2e48d0a58a8e0f6da40", 201 | "base58": "xpub6GcBnm7FfDg5ERWACCvtuotN6Tdoc37r3SZ1asBHvCWzPkqWn3MVKPWKzy6GsfmdMUGanR3D12dH1cp5tJauuubwc4FAJDn67SH2uUjwAT1", 202 | "base58Priv": "xprvA3cqPFaMpr7n1wRh6BPtYfwdYRoKCaPzgDdQnUmgMrz1WxWNEW3EmbBr9ieh9BJAsRGKFPLvotb4p4Aq79jddUVKPVJt7exVzLHcv777JVf", 203 | "identifier": "e371d69b5dae6eacee832a130ee9f55545275a09", 204 | "fingerprint": "e371d69b", 205 | "index": 2147483648, 206 | "depth": 5 207 | } 208 | ] 209 | }, 210 | { 211 | "comment": "Private key has leading zeros", 212 | "network": "bitcoin", 213 | "master": { 214 | "wif": "KwDiBh3sys6SzXrv4cSUVpDHsVc8XiD3o3xyMx9wvUcu3nPJJ6PE", 215 | "pubKey": "02b3e3e297165289611a2387e8089fcaf099926e4d31fdddb50c0ae0dfa36c97e6", 216 | "privKey": "00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd", 217 | "chainCode": "9c8a5c863e5941f3d99453e6ba66b328bb17cf0b8dec89ed4fc5ace397a1c089", 218 | "base58": "xpub661MyMwAqRbcG6q1FFDUUHr61LatpoMSZRQpVWR4MHzfGnfUp8GMT5xfKcrt4xB7nJCgXn1NBqgCGGDkJx1ZLRKeM58HZkhV5NjBWK1AyQY", 219 | "base58Priv": "xprv9s21ZrQH143K3ckY9DgU79uMTJkQRLdbCCVDh81SnxTgPzLLGax6uHeBULTtaEtcAvKjXfT7ZWtHzKjTpujMkUd9dDb8msDeAfnJxrgAYhr", 220 | "identifier": "afc10e1e7abbe99731c4c4606a3556d832372680", 221 | "fingerprint": "afc10e1e" 222 | }, 223 | "children": [ 224 | { 225 | "hardened": true, 226 | "path": "m/44'/0'/0'/0/0'", 227 | "wif": "KxwPsTN9AdQ2CjGihVP8MnSadbtyByskCiX1GCEhjovKj4m3d7Xs", 228 | "pubKey": "030e13168e3f9560da5cebca9c91b78280e7ffa221d097c6dac3e98f450355d6f6", 229 | "privKey": "3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb", 230 | "chainCode": "7a16b2f4ad4cc1069338237f373dabf1fe329a5f3a0d95c4b98d061204676293", 231 | "base58": "xpub6FqvhT2Bdh4YaWA3nqsNTaYNmCEXJnSJnBgntrH8GQVX8hf6exHusxyqgyd6kKKNTvtbpmNPGEH74VxJaAuo14NS52WBmWG9krq1ESqdR6T", 232 | "base58Priv": "xprvA2raHwVHoKWFN25agpLN6SbeDAQ2uKiTQxmC6TsWi4xYFuKx7QyfLAfMqgvyXVr6fcDspooYFsas1ngymgmVSPjTCU5JRfwj6w4zukhfucS", 233 | "identifier": "b2d8238bfeb71a89c8244b7e53105cd97021904e", 234 | "fingerprint": "b2d8238b", 235 | "index": 2147483648, 236 | "depth": 5 237 | } 238 | ] 239 | }, 240 | { 241 | "network": "litecoin", 242 | "master": { 243 | "seed": "000102030405060708090a0b0c0d0e0f", 244 | "wif": "TAroS5Knm8GZcnpPycBgzjwwDLWMyQjDrcuGPPoArgrbW7Ln22qp", 245 | "pubKey": "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2", 246 | "privKey": "e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35", 247 | "chainCode": "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508", 248 | "base58": "Ltub2SSUS19CirucWFod2ZsYA2J4v4U76YiCXHdcQttnoiy5aGanFHCPDBX7utfG6f95u1cUbZJNafmvzNCzZZJTw1EmyFoL8u1gJbGM8ipu491", 249 | "base58Priv": "Ltpv71G8qDifUiNetP6nmxPA5STrUVmv2J9YSmXajv8VsYBUyuPhvN9xCaQrfX2wo5xxJNtEazYCFRUu5FmokYMM79pcqz8pcdo4rNXAFPgyB4k", 250 | "identifier": "3442193e1bb70916e914552172cd4e2dbc9df811", 251 | "fingerprint": "3442193e" 252 | }, 253 | "children": [ 254 | { 255 | "path": "m/0'", 256 | "m": 0, 257 | "hardened": true, 258 | "wif": "TB22qU2V9EJCVKJ8cdYaTfvDhnYcCzthcWgFm1k6hbvbKM1NLxoL", 259 | "pubKey": "035a784662a4a20a65bf6aab9ae98a6c068a81c52e4b032c0fb5400c706cfccc56", 260 | "privKey": "edb2e14f9ee77d26dd93b4ecede8d16ed408ce149b6cd80b0715a2d911a0afea", 261 | "chainCode": "47fdacbd0f1097043b78c63c20c34ef4ed9a111d980047ad16282c7ae6236141", 262 | "base58": "Ltub2UhtRiSfp82berwLEKkB34QBEt2TUdCDCu4WNzGumvAMwYsxfWjULKsXhADxqy3cuDu3TnqoKJr1xmB8Wb2qzthWAtbb4CutpXPuSU1YMgG", 263 | "base58Priv": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx", 264 | "identifier": "5c1bd648ed23aa5fd50ba52b2457c11e9e80a6a7", 265 | "fingerprint": "5c1bd648", 266 | "index": 2147483648, 267 | "depth": 1 268 | } 269 | ] 270 | } 271 | ], 272 | "invalid": { 273 | "fromBase58": [ 274 | { 275 | "exception": "Invalid checksum", 276 | "string": "xprvQQQQQQQQQQQQQQQQCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334" 277 | }, 278 | { 279 | "exception": "Invalid buffer length", 280 | "network": "bitcoin", 281 | "string": "HAsbc6CgKmTYEQg2CTz7m5STEPAB" 282 | }, 283 | { 284 | "exception": "Invalid parent fingerprint", 285 | "network": "bitcoin", 286 | "string": "xprv9tnJFvAXAXPfPnMTKfwpwnkty7MzJwELVgp4NTBquaKXy4RndyfJJCJJf7zNaVpBpzrwVRutZNLRCVLEcZHcvuCNG3zGbGBcZn57FbNnmSP" 287 | }, 288 | { 289 | "exception": "Invalid private key", 290 | "network": "bitcoin", 291 | "string": "xprv9s21ZrQH143K3yLysFvsu3n1dMwhNusmNHr7xArzAeCc7MQYqDBBStmqnZq6WLi668siBBNs3SjiyaexduHu9sXT9ixTsqptL67ADqcaBdm" 292 | }, 293 | { 294 | "exception": "Invalid index", 295 | "network": "bitcoin", 296 | "string": "xprv9s21ZrQYdgnodnKW4Drm1Qg7poU6Gf2WUDsjPxvYiK7iLBMrsjbnF1wsZZQgmXNeMSG3s7jmHk1b3JrzhG5w8mwXGxqFxfrweico7k8DtxR" 297 | }, 298 | { 299 | "exception": "Invalid network version", 300 | "network": "litecoin", 301 | "string": "1111111111111adADjFaSNPxwXqLjHLj4mBfYxuewDPbw9hEj1uaXCzMxRPXDFF3cUoezTFYom4sEmEVSQmENPPR315cFk9YUFVek73wE9" 302 | }, 303 | { 304 | "exception": "Invalid network version", 305 | "string": "8FH81Rao5EgGmdScoN66TJAHsQP7phEMeyMTku9NBJd7hXgaj3HTvSNjqJjoqBpxdbuushwPEM5otvxXt2p9dcw33AqNKzZEPMqGHmz7Dpayi6Vb" 306 | }, 307 | { 308 | "exception": "Invalid network version", 309 | "network": "bitcoin", 310 | "string": "Ltpv73XYpw28ZyVe2zEVyiFnxUZxoKLGQNdZ8NxUi1WcqjNmMBgtLbh3KimGSnPHCoLv1RmvxHs4dnKmo1oXQ8dXuDu8uroxrbVxZPA1gXboYvx" 311 | }, 312 | { 313 | "exception": "Invalid buffer length", 314 | "string": "9XpNiB4DberdMn4jZiMhNGtuZUd7xUrCEGw4MG967zsVNvUKBEC9XLrmVmFasanWGp15zXfTNw4vW4KdvUAynEwyKjdho9QdLMPA2H5uyt" 315 | }, 316 | { 317 | "exception": "Invalid buffer length", 318 | "string": "7JJikZQ2NUXjSAnAF2SjFYE3KXbnnVxzRBNddFE1DjbDEHVGEJzYC7zqSgPoauBJS3cWmZwsER94oYSFrW9vZ4Ch5FtGeifdzmtS3FGYDB1vxFZsYKgMc" 319 | }, 320 | { 321 | "exception": "Invalid parent fingerprint", 322 | "string": "xpub67tVq9SuNQCfm2PXBqjGRAtNZ935kx2uHJaURePth4JBpMfEy6jum7Euj7FTpbs7fnjhfZcNEktCucWHcJf74dbKLKNSTZCQozdDVwvkJhs" 323 | }, 324 | { 325 | "exception": "Invalid index", 326 | "string": "xpub661MyMwTWkfYZq6BEh3ywGVXFvNj5hhzmWMhFBHSqmub31B1LZ9wbJ3DEYXZ8bHXGqnHKfepTud5a2XxGdnnePzZa2m2DyzTnFGBUXtaf9M" 327 | }, 328 | { 329 | "exception": "Point is not on the curve", 330 | "string": "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gYymDsxxRe3WWeZQ7TadaLSdKUffezzczTCpB8j3JP96UwE2n6w1" 331 | } 332 | ], 333 | "fromSeed": [ 334 | { 335 | "exception": "Seed should be at least 128 bits", 336 | "seed": "ffff" 337 | }, 338 | { 339 | "exception": "Seed should be at most 512 bits", 340 | "seed": "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" 341 | } 342 | ], 343 | "deriveHardened": [ 344 | 2147483648, 345 | -1 346 | ], 347 | "derive": [ 348 | 4294967296, 349 | -1 350 | ], 351 | "derivePath": [ 352 | "/", 353 | "m/m/123", 354 | "a/0/1/2", 355 | "m/0/ 1 /2", 356 | "m/0/1.5/2" 357 | ] 358 | } 359 | } --------------------------------------------------------------------------------