UNPKG

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