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