1 | 'use strict'
|
2 |
|
3 | var _ = require('../lib/util/_')
|
4 | var assert = require('assert')
|
5 | var should = require('chai').should()
|
6 | var expect = require('chai').expect
|
7 | var bsv = require('..')
|
8 | var errors = bsv.errors
|
9 | var hdErrors = errors.HDPrivateKey
|
10 | var buffer = require('buffer')
|
11 | var Networks = bsv.Networks
|
12 | var JSUtil = require('../lib/util/js')
|
13 | var HDPrivateKey = bsv.HDPrivateKey
|
14 | var Base58Check = bsv.encoding.Base58Check
|
15 |
|
16 | var xprivkey = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
|
17 | var json = '{"network":"livenet","depth":0,"fingerPrint":876747070,"parentFingerPrint":0,"childIndex":0,"chainCode":"873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508","privateKey":"e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35","checksum":3883834737,"xprivkey":"xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"}'
|
18 | describe('HDPrivate key interface', function () {
|
19 | var expectFail = function (func, error) {
|
20 | var got = null
|
21 | try {
|
22 | func()
|
23 | } catch (e) {
|
24 | got = e instanceof error
|
25 | }
|
26 | expect(got).to.equal(true)
|
27 | }
|
28 |
|
29 | var expectDerivationFail = function (argument, error) {
|
30 | return expectFail(function () {
|
31 | var privateKey = new HDPrivateKey(xprivkey)
|
32 | privateKey.deriveChild(argument)
|
33 | }, error)
|
34 | }
|
35 |
|
36 | var expectFailBuilding = function (argument, error) {
|
37 | return expectFail(function () {
|
38 | return new HDPrivateKey(argument)
|
39 | }, error)
|
40 | }
|
41 |
|
42 | var expectSeedFail = function (argument, error) {
|
43 | return expectFail(function () {
|
44 | return HDPrivateKey.fromSeed(argument)
|
45 | }, error)
|
46 | }
|
47 |
|
48 | it('should make a new private key from random', function () {
|
49 | should.exist(new HDPrivateKey().xprivkey)
|
50 | })
|
51 |
|
52 | it('should make a new private key from random for testnet', function () {
|
53 | var key = new HDPrivateKey('testnet')
|
54 | should.exist(key.xprivkey)
|
55 | key.network.name.should.equal('testnet')
|
56 | })
|
57 |
|
58 | it('should not be able to change read-only properties', function () {
|
59 | var hdkey = new HDPrivateKey()
|
60 | expect(function () {
|
61 | hdkey.fingerPrint = 'notafingerprint'
|
62 | }).to.throw(TypeError)
|
63 | })
|
64 |
|
65 | it('should error with an invalid checksum', function () {
|
66 | expectFailBuilding(xprivkey + '1', errors.InvalidB58Checksum)
|
67 | })
|
68 |
|
69 | it('can be rebuilt from a json generated by itself', function () {
|
70 | var regenerate = new HDPrivateKey(json)
|
71 | regenerate.xprivkey.should.equal(xprivkey)
|
72 | })
|
73 |
|
74 | it('builds a json keeping the structure and same members', function () {
|
75 | assert.deepStrictEqual(
|
76 | new HDPrivateKey(json).toJSON(),
|
77 | new HDPrivateKey(xprivkey).toJSON()
|
78 | )
|
79 | })
|
80 |
|
81 | describe('instantiation', function () {
|
82 | it('invalid argument: can not instantiate from a number', function () {
|
83 | expectFailBuilding(1, hdErrors.UnrecognizedArgument)
|
84 | })
|
85 | it('allows no-new calling', function () {
|
86 | HDPrivateKey(xprivkey).toString().should.equal(xprivkey)
|
87 | })
|
88 | it('allows the use of a copy constructor', function () {
|
89 | HDPrivateKey(HDPrivateKey(xprivkey))
|
90 | .xprivkey.should.equal(xprivkey)
|
91 | })
|
92 | })
|
93 |
|
94 | describe('public key', function () {
|
95 | var testnetKey = new HDPrivateKey('tprv8ZgxMBicQKsPdEeU2KiGFnUgRGriMnQxrwrg6FWCBg4jeiidHRyCCdA357kfkZiGaXEapWZsGDKikeeEbvgXo3UmEdbEKNdQH9VXESmGuUK')
|
96 | var livenetKey = new HDPrivateKey('xprv9s21ZrQH143K3e39bnn1vyS7YFa1EAJAFGDoeHaSBsgBxgAkTEXeSx7xLvhNQNJxJwhzziWcK3znUFKRPRwWBPkKZ8ijUBa5YYpYPQmeBDX')
|
97 |
|
98 | it('matches the network', function () {
|
99 | testnetKey.publicKey.network.should.equal(Networks.testnet)
|
100 | livenetKey.publicKey.network.should.equal(Networks.livenet)
|
101 | })
|
102 |
|
103 | it('cache for xpubkey works', function () {
|
104 | var privateKey = new HDPrivateKey(xprivkey)
|
105 | should.not.exist(privateKey._hdPublicKey)
|
106 | privateKey.xpubkey.should.equal(privateKey.xpubkey)
|
107 | should.exist(privateKey._hdPublicKey)
|
108 | })
|
109 | })
|
110 |
|
111 | it('inspect() displays correctly', function () {
|
112 | HDPrivateKey(xprivkey).inspect().should.equal('<HDPrivateKey: ' + xprivkey + '>')
|
113 | })
|
114 | it('fails when trying to derive with an invalid argument', function () {
|
115 | expectDerivationFail([], hdErrors.InvalidDerivationArgument)
|
116 | })
|
117 |
|
118 | it('catches early invalid paths', function () {
|
119 | expectDerivationFail('s', hdErrors.InvalidPath)
|
120 | })
|
121 |
|
122 | it('allows derivation of hardened keys by passing a very big number', function () {
|
123 | var privateKey = new HDPrivateKey(xprivkey)
|
124 | var derivedByNumber = privateKey.deriveChild(0x80000000)
|
125 | var derivedByArgument = privateKey.deriveChild(0, true)
|
126 | derivedByNumber.xprivkey.should.equal(derivedByArgument.xprivkey)
|
127 | })
|
128 |
|
129 | it('returns itself with \'m\' parameter', function () {
|
130 | var privateKey = new HDPrivateKey(xprivkey)
|
131 | privateKey.should.equal(privateKey.deriveChild('m'))
|
132 | })
|
133 |
|
134 | it('returns InvalidArgument if invalid data is given to getSerializedError', function () {
|
135 | expect(
|
136 | HDPrivateKey.getSerializedError(1) instanceof hdErrors.UnrecognizedArgument
|
137 | ).to.equal(true)
|
138 | })
|
139 |
|
140 | it('returns InvalidLength if data of invalid length is given to getSerializedError', function () {
|
141 | var b58s = Base58Check.encode(buffer.Buffer.from('onestring'))
|
142 | expect(
|
143 | HDPrivateKey.getSerializedError(b58s) instanceof hdErrors.InvalidLength
|
144 | ).to.equal(true)
|
145 | })
|
146 |
|
147 | it('returns InvalidNetworkArgument if an invalid network is provided', function () {
|
148 | expect(
|
149 | HDPrivateKey.getSerializedError(xprivkey, 'invalidNetwork') instanceof errors.InvalidNetworkArgument
|
150 | ).to.equal(true)
|
151 | })
|
152 |
|
153 | it('recognizes that the wrong network was asked for', function () {
|
154 | expect(
|
155 | HDPrivateKey.getSerializedError(xprivkey, 'testnet') instanceof errors.InvalidNetwork
|
156 | ).to.equal(true)
|
157 | })
|
158 |
|
159 | it('recognizes the correct network', function () {
|
160 | expect(HDPrivateKey.getSerializedError(xprivkey, 'livenet')).to.equal(null)
|
161 | })
|
162 |
|
163 | describe('on creation from seed', function () {
|
164 | it('converts correctly from an hexa string', function () {
|
165 | should.exist(HDPrivateKey.fromSeed('01234567890abcdef01234567890abcdef').xprivkey)
|
166 | })
|
167 | it('fails when argument is not a buffer or string', function () {
|
168 | expectSeedFail(1, hdErrors.InvalidEntropyArgument)
|
169 | })
|
170 | it('fails when argument doesn\'t provide enough entropy', function () {
|
171 | expectSeedFail('01', hdErrors.InvalidEntropyArgument.NotEnoughEntropy)
|
172 | })
|
173 | it('fails when argument provides too much entropy', function () {
|
174 | var entropy = '0'
|
175 | for (var i = 0; i < 129; i++) {
|
176 | entropy += '1'
|
177 | }
|
178 | expectSeedFail(entropy, hdErrors.InvalidEntropyArgument.TooMuchEntropy)
|
179 | })
|
180 | })
|
181 |
|
182 | it('correctly errors if an invalid checksum is provided', function () {
|
183 | var privKey = new HDPrivateKey(xprivkey)
|
184 | var error = null
|
185 | try {
|
186 | var buffers = privKey._buffers
|
187 | buffers.checksum = JSUtil.integerAsBuffer(0)
|
188 | new HDPrivateKey(buffers)
|
189 | } catch (e) {
|
190 | error = e
|
191 | }
|
192 | expect(error instanceof errors.InvalidB58Checksum).to.equal(true)
|
193 | })
|
194 | it('correctly validates the checksum', function () {
|
195 | var privKey = new HDPrivateKey(xprivkey)
|
196 | expect(function () {
|
197 | var buffers = privKey._buffers
|
198 | return new HDPrivateKey(buffers)
|
199 | }).to.not.throw()
|
200 | })
|
201 |
|
202 | it('shouldn\'t matter if derivations are made with strings or numbers', function () {
|
203 | var privateKey = new HDPrivateKey(xprivkey)
|
204 | var derivedByString = privateKey.deriveChild('m/0\'/1/2\'')
|
205 | var derivedByNumber = privateKey.deriveChild(0, true).deriveChild(1).deriveChild(2, true)
|
206 | derivedByNumber.xprivkey.should.equal(derivedByString.xprivkey)
|
207 | })
|
208 |
|
209 | describe('validates paths', function () {
|
210 | it('validates correct paths', function () {
|
211 | var valid
|
212 |
|
213 | valid = HDPrivateKey.isValidPath('m/0\'/1/2\'')
|
214 | valid.should.equal(true)
|
215 |
|
216 | valid = HDPrivateKey.isValidPath('m')
|
217 | valid.should.equal(true)
|
218 |
|
219 | valid = HDPrivateKey.isValidPath(123, true)
|
220 | valid.should.equal(true)
|
221 |
|
222 | valid = HDPrivateKey.isValidPath(123)
|
223 | valid.should.equal(true)
|
224 |
|
225 | valid = HDPrivateKey.isValidPath(HDPrivateKey.Hardened + 123)
|
226 | valid.should.equal(true)
|
227 |
|
228 | valid = HDPrivateKey.isValidPath(HDPrivateKey.Hardened + 123, true)
|
229 | valid.should.equal(true)
|
230 | })
|
231 |
|
232 | var invalid = [
|
233 | 'm/-1/12',
|
234 | 'bad path',
|
235 | 'K',
|
236 | 'm/',
|
237 | 'm/12asd',
|
238 | 'm/1/2//3'
|
239 | ]
|
240 |
|
241 | invalid.forEach(function (datum) {
|
242 | it('rejects illegal path ' + datum, function () {
|
243 | HDPrivateKey.isValidPath(datum).should.equal(false)
|
244 | expect(HDPrivateKey._getDerivationIndexes(datum)).to.equal(null)
|
245 | })
|
246 | })
|
247 |
|
248 | it('generates deriving indexes correctly', function () {
|
249 | var indexes
|
250 |
|
251 | indexes = HDPrivateKey._getDerivationIndexes('m/-1/12')
|
252 | expect(indexes).to.equal(null)
|
253 |
|
254 | indexes = HDPrivateKey._getDerivationIndexes('m/0/12/12\'')
|
255 | indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12])
|
256 |
|
257 | indexes = HDPrivateKey._getDerivationIndexes('m/0/12/12\'')
|
258 | indexes.should.eql([0, 12, HDPrivateKey.Hardened + 12])
|
259 | })
|
260 | })
|
261 |
|
262 | describe('conversion to/from buffer', function () {
|
263 | var str = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
|
264 | it('should roundtrip to/from a buffer', function () {
|
265 | var priv = new HDPrivateKey(str)
|
266 | var toBuffer = priv.toBuffer()
|
267 | var fromBuffer = HDPrivateKey.fromBuffer(toBuffer)
|
268 | var roundTrip = new HDPrivateKey(fromBuffer.toString())
|
269 | roundTrip.xprivkey.should.equal(str)
|
270 | })
|
271 | })
|
272 |
|
273 | describe('conversion to/from hex', function () {
|
274 | var str = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
|
275 | it('should roundtrip to/from a buffer', function () {
|
276 | var priv = new HDPrivateKey(str)
|
277 | var toBuffer = priv.toBuffer()
|
278 | var fromBuffer = HDPrivateKey.fromBuffer(toBuffer)
|
279 | var roundTrip = new HDPrivateKey(fromBuffer.toString())
|
280 | roundTrip.xprivkey.should.equal(str)
|
281 | })
|
282 | })
|
283 |
|
284 | describe('from random', function () {
|
285 | var str = 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
|
286 | it('should roundtrip to/from a buffer', function () {
|
287 | var xprv1 = new HDPrivateKey(str)
|
288 | var xprv2 = HDPrivateKey.fromRandom()
|
289 | var xprv3 = HDPrivateKey.fromRandom()
|
290 | xprv1.toString().should.not.equal(xprv2.toString())
|
291 | xprv2.toString().should.not.equal(xprv3.toString())
|
292 | xprv1.toString().should.not.equal(xprv3.toString())
|
293 | })
|
294 | })
|
295 |
|
296 | describe('conversion to plain object/json', function () {
|
297 | var plainObject = {
|
298 | 'network': 'livenet',
|
299 | 'depth': 0,
|
300 | 'fingerPrint': 876747070,
|
301 | 'parentFingerPrint': 0,
|
302 | 'childIndex': 0,
|
303 | 'chainCode': '873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508',
|
304 | 'privateKey': 'e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35',
|
305 | 'checksum': 3883834737,
|
306 | 'xprivkey': 'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvN' +
|
307 | 'KmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi'
|
308 | }
|
309 | it('toObject leaves no Buffer instances', function () {
|
310 | var privKey = new HDPrivateKey(xprivkey)
|
311 | var object = privKey.toObject()
|
312 | _.each(_.values(object), function (value) {
|
313 | expect(Buffer.isBuffer(value)).to.equal(false)
|
314 | })
|
315 | })
|
316 | it('roundtrips toObject', function () {
|
317 | expect(HDPrivateKey.fromObject(new HDPrivateKey(xprivkey).toObject()).xprivkey).to.equal(xprivkey)
|
318 | })
|
319 | it('roundtrips to JSON and to Object', function () {
|
320 | var privkey = new HDPrivateKey(xprivkey)
|
321 | expect(HDPrivateKey.fromObject(privkey.toJSON()).xprivkey).to.equal(xprivkey)
|
322 | })
|
323 | it('recovers state from JSON', function () {
|
324 | new HDPrivateKey(JSON.stringify(plainObject)).xprivkey.should.equal(xprivkey)
|
325 | })
|
326 | it('recovers state from Object', function () {
|
327 | new HDPrivateKey(plainObject).xprivkey.should.equal(xprivkey)
|
328 | })
|
329 | })
|
330 | })
|