1 | let BIP32 = require('../')
|
2 | let tape = require('tape')
|
3 | let fixtures = require('./fixtures/index.json')
|
4 | let LITECOIN = {
|
5 | wif: 0xb0,
|
6 | bip32: {
|
7 | public: 0x019da462,
|
8 | private: 0x019d9cfe
|
9 | }
|
10 | }
|
11 |
|
12 |
|
13 | let validAll = []
|
14 | fixtures.valid.forEach((f) => {
|
15 | f.master.network = f.network
|
16 | f.master.children = f.children
|
17 | f.master.comment = f.comment
|
18 | f.children.forEach((fc) => {
|
19 | fc.network = f.network
|
20 | validAll.push(fc)
|
21 | })
|
22 | delete f.children
|
23 | validAll.push(f.master)
|
24 | })
|
25 |
|
26 | function verify (t, hd, prv, f, network) {
|
27 | t.equal(hd.chainCode.toString('hex'), f.chainCode)
|
28 | t.equal(hd.depth, f.depth >>> 0)
|
29 | t.equal(hd.index, f.index >>> 0)
|
30 | t.equal(hd.fingerprint.toString('hex'), f.fingerprint)
|
31 | t.equal(hd.identifier.toString('hex'), f.identifier)
|
32 | t.equal(hd.publicKey.toString('hex'), f.pubKey)
|
33 | if (prv) t.equal(hd.toBase58(), f.base58Priv)
|
34 | if (prv) t.equal(hd.privateKey.toString('hex'), f.privKey)
|
35 | if (prv) t.equal(hd.toWIF(), f.wif)
|
36 | if (!prv) t.throws(() => hd.toWIF(), /Missing private key/)
|
37 | if (!prv) t.equal(hd.privateKey, null)
|
38 | t.equal(hd.neutered().toBase58(), f.base58)
|
39 | t.equal(hd.isNeutered(), !prv)
|
40 |
|
41 | if (!f.children) return
|
42 | if (!prv && f.children.some(x => x.hardened)) return
|
43 |
|
44 |
|
45 | f.children.forEach((cf) => {
|
46 | let chd = hd.derivePath(cf.path)
|
47 | verify(t, chd, prv, cf, network)
|
48 |
|
49 | let chdNoM = hd.derivePath(cf.path.slice(2))
|
50 | verify(t, chdNoM, prv, cf, network)
|
51 | })
|
52 |
|
53 |
|
54 | let shd = hd
|
55 | f.children.forEach((cf) => {
|
56 | if (cf.m === undefined) return
|
57 | if (cf.hardened) {
|
58 | shd = shd.deriveHardened(cf.m)
|
59 | } else {
|
60 |
|
61 | if (cf.base58) verify(t, shd.neutered().derive(cf.m), false, cf, network)
|
62 |
|
63 | shd = shd.derive(cf.m)
|
64 | verify(t, shd, prv, cf, network)
|
65 | }
|
66 |
|
67 | t.throws(() => {
|
68 | shd.derivePath('m/0')
|
69 | }, /Expected master, got child/)
|
70 |
|
71 | verify(t, shd, prv, cf, network)
|
72 | })
|
73 | }
|
74 |
|
75 | validAll.forEach((ff) => {
|
76 | tape(ff.comment || ff.base58Priv, (t) => {
|
77 | let network
|
78 | if (ff.network === 'litecoin') network = LITECOIN
|
79 |
|
80 | let hd = BIP32.fromBase58(ff.base58Priv, network)
|
81 | verify(t, hd, true, ff, network)
|
82 |
|
83 | hd = BIP32.fromBase58(ff.base58, network)
|
84 | verify(t, hd, false, ff, network)
|
85 |
|
86 | if (ff.seed) {
|
87 | let seed = Buffer.from(ff.seed, 'hex')
|
88 | hd = BIP32.fromSeed(seed, network)
|
89 | verify(t, hd, true, ff, network)
|
90 | }
|
91 |
|
92 | t.end()
|
93 | })
|
94 | })
|
95 |
|
96 | tape('fromBase58 throws', (t) => {
|
97 | fixtures.invalid.fromBase58.forEach((f) => {
|
98 | t.throws(() => {
|
99 | let network
|
100 | if (f.network === 'litecoin') network = LITECOIN
|
101 |
|
102 | BIP32.fromBase58(f.string, network)
|
103 | }, new RegExp(f.exception))
|
104 | })
|
105 |
|
106 | t.end()
|
107 | })
|
108 |
|
109 | tape('works for Private -> public (neutered)', (t) => {
|
110 | let f = fixtures.valid[1]
|
111 | let c = f.master.children[0]
|
112 |
|
113 | let master = BIP32.fromBase58(f.master.base58Priv)
|
114 | let child = master.derive(c.m).neutered()
|
115 |
|
116 | t.plan(1)
|
117 | t.equal(child.toBase58(), c.base58)
|
118 | })
|
119 |
|
120 | tape('works for Private -> public (neutered, hardened)', (t) => {
|
121 | let f = fixtures.valid[0]
|
122 | let c = f.master.children[0]
|
123 |
|
124 | let master = BIP32.fromBase58(f.master.base58Priv)
|
125 | let child = master.deriveHardened(c.m).neutered()
|
126 |
|
127 | t.plan(1)
|
128 | t.equal(c.base58, child.toBase58())
|
129 | })
|
130 |
|
131 | tape('works for Public -> public', (t) => {
|
132 | let f = fixtures.valid[1]
|
133 | let c = f.master.children[0]
|
134 |
|
135 | let master = BIP32.fromBase58(f.master.base58)
|
136 | let child = master.derive(c.m)
|
137 |
|
138 | t.plan(1)
|
139 | t.equal(c.base58, child.toBase58())
|
140 | })
|
141 |
|
142 | tape('throws on Public -> public (hardened)', (t) => {
|
143 | let f = fixtures.valid[0]
|
144 | let c = f.master.children[0]
|
145 |
|
146 | let master = BIP32.fromBase58(f.master.base58)
|
147 |
|
148 | t.plan(1)
|
149 | t.throws(() => {
|
150 | master.deriveHardened(c.m)
|
151 | }, /Missing private key for hardened child key/)
|
152 | })
|
153 |
|
154 | tape('throws on wrong types', (t) => {
|
155 | let f = fixtures.valid[0]
|
156 | let master = BIP32.fromBase58(f.master.base58)
|
157 |
|
158 | fixtures.invalid.derive.forEach((fx) => {
|
159 | t.throws(() => {
|
160 | master.derive(fx)
|
161 | }, /Expected UInt32/)
|
162 | })
|
163 |
|
164 | fixtures.invalid.deriveHardened.forEach((fx) => {
|
165 | t.throws(() => {
|
166 | master.deriveHardened(fx)
|
167 | }, /Expected UInt31/)
|
168 | })
|
169 |
|
170 | fixtures.invalid.derivePath.forEach((fx) => {
|
171 | t.throws(() => {
|
172 | master.derivePath(fx)
|
173 | }, /Expected BIP32Path, got/)
|
174 | })
|
175 |
|
176 | let ZERO = Buffer.alloc(32, 0)
|
177 | let ONES = Buffer.alloc(32, 1)
|
178 |
|
179 | t.throws(() => {
|
180 | BIP32.fromPrivateKey(Buffer.alloc(2), ONES)
|
181 | }, /Expected property "privateKey" of type Buffer\(Length: 32\), got Buffer\(Length: 2\)/)
|
182 |
|
183 | t.throws(() => {
|
184 | BIP32.fromPrivateKey(ZERO, ONES)
|
185 | }, /Private key not in range \[1, n\)/)
|
186 |
|
187 | t.end()
|
188 | })
|
189 |
|
190 | tape('works when private key has leading zeros', (t) => {
|
191 | let key = 'xprv9s21ZrQH143K3ckY9DgU79uMTJkQRLdbCCVDh81SnxTgPzLLGax6uHeBULTtaEtcAvKjXfT7ZWtHzKjTpujMkUd9dDb8msDeAfnJxrgAYhr'
|
192 | let hdkey = BIP32.fromBase58(key)
|
193 |
|
194 | t.plan(2)
|
195 | t.equal(hdkey.privateKey.toString('hex'), '00000055378cf5fafb56c711c674143f9b0ee82ab0ba2924f19b64f5ae7cdbfd')
|
196 | let child = hdkey.derivePath('m/44\'/0\'/0\'/0/0\'')
|
197 | t.equal(child.privateKey.toString('hex'), '3348069561d2a0fb925e74bf198762acc47dce7db27372257d2d959a9e6f8aeb')
|
198 | })
|
199 |
|
200 | tape('fromSeed', (t) => {
|
201 |
|
202 |
|
203 | fixtures.invalid.fromSeed.forEach((f) => {
|
204 | t.throws(() => {
|
205 | BIP32.fromSeed(Buffer.from(f.seed, 'hex'))
|
206 | }, new RegExp(f.exception))
|
207 | })
|
208 |
|
209 | t.end()
|
210 | })
|
211 |
|
212 | tape('ecdsa', (t) => {
|
213 | let seed = Buffer.alloc(32, 1)
|
214 | let hash = Buffer.alloc(32, 2)
|
215 | let signature = Buffer.from('9636ee2fac31b795a308856b821ebe297dda7b28220fb46ea1fbbd7285977cc04c82b734956246a0f15a9698f03f546d8d96fe006c8e7bd2256ca7c8229e6f5c', 'hex')
|
216 | let node = BIP32.fromSeed(seed)
|
217 |
|
218 | t.plan(3)
|
219 | t.equal(node.sign(hash).toString('hex'), signature.toString('hex'))
|
220 | t.equal(node.verify(hash, signature), true)
|
221 | t.equal(node.verify(seed, signature), false)
|
222 | })
|