1 | 'use strict';
|
2 |
|
3 | var _ = require('lodash');
|
4 | var $ = require('./util/preconditions');
|
5 |
|
6 | var BN = require('./crypto/bn');
|
7 | var Base58 = require('./encoding/base58');
|
8 | var Base58Check = require('./encoding/base58check');
|
9 | var Hash = require('./crypto/hash');
|
10 | var HDPrivateKey = require('./hdprivatekey');
|
11 | var Network = require('./networks');
|
12 | var Point = require('./crypto/point');
|
13 | var PublicKey = require('./publickey');
|
14 |
|
15 | var bitcoreErrors = require('./errors');
|
16 | var errors = bitcoreErrors;
|
17 | var hdErrors = bitcoreErrors.HDPublicKey;
|
18 | var assert = require('assert');
|
19 |
|
20 | var JSUtil = require('./util/js');
|
21 | var BufferUtil = require('./util/buffer');
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 | function HDPublicKey(arg) {
|
32 |
|
33 |
|
34 | if (arg instanceof HDPublicKey) {
|
35 | return arg;
|
36 | }
|
37 | if (!(this instanceof HDPublicKey)) {
|
38 | return new HDPublicKey(arg);
|
39 | }
|
40 | if (arg) {
|
41 | if (_.isString(arg) || BufferUtil.isBuffer(arg)) {
|
42 | var error = HDPublicKey.getSerializedError(arg);
|
43 | if (!error) {
|
44 | return this._buildFromSerialized(arg);
|
45 | } else if (BufferUtil.isBuffer(arg) && !HDPublicKey.getSerializedError(arg.toString())) {
|
46 | return this._buildFromSerialized(arg.toString());
|
47 | } else {
|
48 | if (error instanceof hdErrors.ArgumentIsPrivateExtended) {
|
49 | return new HDPrivateKey(arg).hdPublicKey;
|
50 | }
|
51 | throw error;
|
52 | }
|
53 | } else {
|
54 | if (_.isObject(arg)) {
|
55 | if (arg instanceof HDPrivateKey) {
|
56 | return this._buildFromPrivate(arg);
|
57 | } else {
|
58 | return this._buildFromObject(arg);
|
59 | }
|
60 | } else {
|
61 | throw new hdErrors.UnrecognizedArgument(arg);
|
62 | }
|
63 | }
|
64 | } else {
|
65 | throw new hdErrors.MustSupplyArgument();
|
66 | }
|
67 | }
|
68 |
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 | HDPublicKey.isValidPath = function(arg) {
|
76 | if (_.isString(arg)) {
|
77 | var indexes = HDPrivateKey._getDerivationIndexes(arg);
|
78 | return indexes !== null && _.every(indexes, HDPublicKey.isValidPath);
|
79 | }
|
80 |
|
81 | if (_.isNumber(arg)) {
|
82 | return arg >= 0 && arg < HDPublicKey.Hardened;
|
83 | }
|
84 |
|
85 | return false;
|
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 | HDPublicKey.prototype.derive = function(arg, hardened) {
|
114 | return this.deriveChild(arg, hardened);
|
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 | HDPublicKey.prototype.deriveChild = function(arg, hardened) {
|
143 | if (_.isNumber(arg)) {
|
144 | return this._deriveWithNumber(arg, hardened);
|
145 | } else if (_.isString(arg)) {
|
146 | return this._deriveFromString(arg);
|
147 | } else {
|
148 | throw new hdErrors.InvalidDerivationArgument(arg);
|
149 | }
|
150 | };
|
151 |
|
152 | HDPublicKey.prototype._deriveWithNumber = function(index, hardened) {
|
153 | if (index >= HDPublicKey.Hardened || hardened) {
|
154 | throw new hdErrors.InvalidIndexCantDeriveHardened();
|
155 | }
|
156 | if (index < 0) {
|
157 | throw new hdErrors.InvalidPath(index);
|
158 | }
|
159 |
|
160 | var indexBuffer = BufferUtil.integerAsBuffer(index);
|
161 | var data = BufferUtil.concat([this.publicKey.toBuffer(), indexBuffer]);
|
162 | var hash = Hash.sha512hmac(data, this._buffers.chainCode);
|
163 | var leftPart = BN.fromBuffer(hash.slice(0, 32), {size: 32});
|
164 | var chainCode = hash.slice(32, 64);
|
165 |
|
166 | var publicKey;
|
167 | try {
|
168 | publicKey = PublicKey.fromPoint(Point.getG().mul(leftPart).add(this.publicKey.point));
|
169 | } catch (e) {
|
170 | return this._deriveWithNumber(index + 1);
|
171 | }
|
172 |
|
173 | var derived = new HDPublicKey({
|
174 | network: this.network,
|
175 | depth: this.depth + 1,
|
176 | parentFingerPrint: this.fingerPrint,
|
177 | childIndex: index,
|
178 | chainCode: chainCode,
|
179 | publicKey: publicKey
|
180 | });
|
181 |
|
182 | return derived;
|
183 | };
|
184 |
|
185 | HDPublicKey.prototype._deriveFromString = function(path) {
|
186 |
|
187 | if (_.includes(path, "'")) {
|
188 | throw new hdErrors.InvalidIndexCantDeriveHardened();
|
189 | } else if (!HDPublicKey.isValidPath(path)) {
|
190 | throw new hdErrors.InvalidPath(path);
|
191 | }
|
192 |
|
193 | var indexes = HDPrivateKey._getDerivationIndexes(path);
|
194 | var derived = indexes.reduce(function(prev, index) {
|
195 | return prev._deriveWithNumber(index);
|
196 | }, this);
|
197 |
|
198 | return derived;
|
199 | };
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 |
|
208 |
|
209 |
|
210 | HDPublicKey.isValidSerialized = function(data, network) {
|
211 | return _.isNull(HDPublicKey.getSerializedError(data, network));
|
212 | };
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 | HDPublicKey.getSerializedError = function(data, network) {
|
224 |
|
225 |
|
226 | if (!(_.isString(data) || BufferUtil.isBuffer(data))) {
|
227 | return new hdErrors.UnrecognizedArgument('expected buffer or string');
|
228 | }
|
229 | if (!Base58.validCharacters(data)) {
|
230 | return new errors.InvalidB58Char('(unknown)', data);
|
231 | }
|
232 | try {
|
233 | data = Base58Check.decode(data);
|
234 | } catch (e) {
|
235 | return new errors.InvalidB58Checksum(data);
|
236 | }
|
237 | if (data.length !== HDPublicKey.DataSize) {
|
238 | return new hdErrors.InvalidLength(data);
|
239 | }
|
240 | if (!_.isUndefined(network)) {
|
241 | var error = HDPublicKey._validateNetwork(data, network);
|
242 | if (error) {
|
243 | return error;
|
244 | }
|
245 | }
|
246 | var version = BufferUtil.integerFromBuffer(data.slice(0, 4));
|
247 | if (version === Network.livenet.xprivkey || version === Network.testnet.xprivkey ) {
|
248 | return new hdErrors.ArgumentIsPrivateExtended();
|
249 | }
|
250 | return null;
|
251 | };
|
252 |
|
253 | HDPublicKey._validateNetwork = function(data, networkArg) {
|
254 | var network = Network.get(networkArg);
|
255 | if (!network) {
|
256 | return new errors.InvalidNetworkArgument(networkArg);
|
257 | }
|
258 | var version = data.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd);
|
259 | if (BufferUtil.integerFromBuffer(version) !== network.xpubkey) {
|
260 | return new errors.InvalidNetwork(version);
|
261 | }
|
262 | return null;
|
263 | };
|
264 |
|
265 | HDPublicKey.prototype._buildFromPrivate = function (arg) {
|
266 | var args = _.clone(arg._buffers);
|
267 | var point = Point.getG().mul(BN.fromBuffer(args.privateKey));
|
268 | args.publicKey = Point.pointToCompressed(point);
|
269 | args.version = BufferUtil.integerAsBuffer(Network.get(BufferUtil.integerFromBuffer(args.version)).xpubkey);
|
270 | args.privateKey = undefined;
|
271 | args.checksum = undefined;
|
272 | args.xprivkey = undefined;
|
273 | return this._buildFromBuffers(args);
|
274 | };
|
275 |
|
276 | HDPublicKey.prototype._buildFromObject = function(arg) {
|
277 |
|
278 |
|
279 | var buffers = {
|
280 | version: arg.network ? BufferUtil.integerAsBuffer(Network.get(arg.network).xpubkey) : arg.version,
|
281 | depth: _.isNumber(arg.depth) ? BufferUtil.integerAsSingleByteBuffer(arg.depth) : arg.depth,
|
282 | parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? BufferUtil.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
|
283 | childIndex: _.isNumber(arg.childIndex) ? BufferUtil.integerAsBuffer(arg.childIndex) : arg.childIndex,
|
284 | chainCode: _.isString(arg.chainCode) ? Buffer.from(arg.chainCode,'hex') : arg.chainCode,
|
285 | publicKey: _.isString(arg.publicKey) ? Buffer.from(arg.publicKey,'hex') :
|
286 | BufferUtil.isBuffer(arg.publicKey) ? arg.publicKey : arg.publicKey.toBuffer(),
|
287 | checksum: _.isNumber(arg.checksum) ? BufferUtil.integerAsBuffer(arg.checksum) : arg.checksum
|
288 | };
|
289 | return this._buildFromBuffers(buffers);
|
290 | };
|
291 |
|
292 | HDPublicKey.prototype._buildFromSerialized = function(arg) {
|
293 | var decoded = Base58Check.decode(arg);
|
294 | var buffers = {
|
295 | version: decoded.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd),
|
296 | depth: decoded.slice(HDPublicKey.DepthStart, HDPublicKey.DepthEnd),
|
297 | parentFingerPrint: decoded.slice(HDPublicKey.ParentFingerPrintStart,
|
298 | HDPublicKey.ParentFingerPrintEnd),
|
299 | childIndex: decoded.slice(HDPublicKey.ChildIndexStart, HDPublicKey.ChildIndexEnd),
|
300 | chainCode: decoded.slice(HDPublicKey.ChainCodeStart, HDPublicKey.ChainCodeEnd),
|
301 | publicKey: decoded.slice(HDPublicKey.PublicKeyStart, HDPublicKey.PublicKeyEnd),
|
302 | checksum: decoded.slice(HDPublicKey.ChecksumStart, HDPublicKey.ChecksumEnd),
|
303 | xpubkey: arg
|
304 | };
|
305 | return this._buildFromBuffers(buffers);
|
306 | };
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 | HDPublicKey.prototype._buildFromBuffers = function(arg) {
|
325 |
|
326 |
|
327 |
|
328 | HDPublicKey._validateBufferArguments(arg);
|
329 |
|
330 | JSUtil.defineImmutable(this, {
|
331 | _buffers: arg
|
332 | });
|
333 |
|
334 | var sequence = [
|
335 | arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode,
|
336 | arg.publicKey
|
337 | ];
|
338 | var concat = BufferUtil.concat(sequence);
|
339 | var checksum = Base58Check.checksum(concat);
|
340 | if (!arg.checksum || !arg.checksum.length) {
|
341 | arg.checksum = checksum;
|
342 | } else {
|
343 | if (arg.checksum.toString('hex') !== checksum.toString('hex')) {
|
344 | throw new errors.InvalidB58Checksum(concat, checksum);
|
345 | }
|
346 | }
|
347 | var network = Network.get(BufferUtil.integerFromBuffer(arg.version));
|
348 |
|
349 | var xpubkey;
|
350 | xpubkey = Base58Check.encode(BufferUtil.concat(sequence));
|
351 | arg.xpubkey = Buffer.from(xpubkey);
|
352 |
|
353 | var publicKey = new PublicKey(arg.publicKey, {network: network});
|
354 | var size = HDPublicKey.ParentFingerPrintSize;
|
355 | var fingerPrint = Hash.sha256ripemd160(publicKey.toBuffer()).slice(0, size);
|
356 |
|
357 | JSUtil.defineImmutable(this, {
|
358 | xpubkey: xpubkey,
|
359 | network: network,
|
360 | depth: BufferUtil.integerFromSingleByteBuffer(arg.depth),
|
361 | publicKey: publicKey,
|
362 | fingerPrint: fingerPrint
|
363 | });
|
364 |
|
365 | return this;
|
366 | };
|
367 |
|
368 | HDPublicKey._validateBufferArguments = function(arg) {
|
369 | var checkBuffer = function(name, size) {
|
370 | var buff = arg[name];
|
371 | assert(BufferUtil.isBuffer(buff), name + ' argument is not a buffer, it\'s ' + typeof buff);
|
372 | assert(
|
373 | buff.length === size,
|
374 | name + ' has not the expected size: found ' + buff.length + ', expected ' + size
|
375 | );
|
376 | };
|
377 | checkBuffer('version', HDPublicKey.VersionSize);
|
378 | checkBuffer('depth', HDPublicKey.DepthSize);
|
379 | checkBuffer('parentFingerPrint', HDPublicKey.ParentFingerPrintSize);
|
380 | checkBuffer('childIndex', HDPublicKey.ChildIndexSize);
|
381 | checkBuffer('chainCode', HDPublicKey.ChainCodeSize);
|
382 | checkBuffer('publicKey', HDPublicKey.PublicKeySize);
|
383 | if (arg.checksum && arg.checksum.length) {
|
384 | checkBuffer('checksum', HDPublicKey.CheckSumSize);
|
385 | }
|
386 | };
|
387 |
|
388 | HDPublicKey.fromString = function(arg) {
|
389 | $.checkArgument(_.isString(arg), 'No valid string was provided');
|
390 | return new HDPublicKey(arg);
|
391 | };
|
392 |
|
393 | HDPublicKey.fromObject = function(arg) {
|
394 | $.checkArgument(_.isObject(arg), 'No valid argument was provided');
|
395 | return new HDPublicKey(arg);
|
396 | };
|
397 |
|
398 |
|
399 |
|
400 |
|
401 |
|
402 | HDPublicKey.prototype.toString = function() {
|
403 | return this.xpubkey;
|
404 | };
|
405 |
|
406 |
|
407 |
|
408 |
|
409 |
|
410 | HDPublicKey.prototype.inspect = function() {
|
411 | return '<HDPublicKey: ' + this.xpubkey + '>';
|
412 | };
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 | HDPublicKey.prototype.toObject = HDPublicKey.prototype.toJSON = function toObject() {
|
432 | return {
|
433 | network: Network.get(BufferUtil.integerFromBuffer(this._buffers.version)).name,
|
434 | depth: BufferUtil.integerFromSingleByteBuffer(this._buffers.depth),
|
435 | fingerPrint: BufferUtil.integerFromBuffer(this.fingerPrint),
|
436 | parentFingerPrint: BufferUtil.integerFromBuffer(this._buffers.parentFingerPrint),
|
437 | childIndex: BufferUtil.integerFromBuffer(this._buffers.childIndex),
|
438 | chainCode: BufferUtil.bufferToHex(this._buffers.chainCode),
|
439 | publicKey: this.publicKey.toString(),
|
440 | checksum: BufferUtil.integerFromBuffer(this._buffers.checksum),
|
441 | xpubkey: this.xpubkey
|
442 | };
|
443 | };
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 | HDPublicKey.fromBuffer = function(arg) {
|
452 | return new HDPublicKey(arg);
|
453 | };
|
454 |
|
455 |
|
456 |
|
457 |
|
458 |
|
459 |
|
460 | HDPublicKey.prototype.toBuffer = function() {
|
461 | return BufferUtil.copy(this._buffers.xpubkey);
|
462 | };
|
463 |
|
464 | HDPublicKey.Hardened = 0x80000000;
|
465 | HDPublicKey.RootElementAlias = ['m', 'M'];
|
466 |
|
467 | HDPublicKey.VersionSize = 4;
|
468 | HDPublicKey.DepthSize = 1;
|
469 | HDPublicKey.ParentFingerPrintSize = 4;
|
470 | HDPublicKey.ChildIndexSize = 4;
|
471 | HDPublicKey.ChainCodeSize = 32;
|
472 | HDPublicKey.PublicKeySize = 33;
|
473 | HDPublicKey.CheckSumSize = 4;
|
474 |
|
475 | HDPublicKey.DataSize = 78;
|
476 | HDPublicKey.SerializedByteSize = 82;
|
477 |
|
478 | HDPublicKey.VersionStart = 0;
|
479 | HDPublicKey.VersionEnd = HDPublicKey.VersionStart + HDPublicKey.VersionSize;
|
480 | HDPublicKey.DepthStart = HDPublicKey.VersionEnd;
|
481 | HDPublicKey.DepthEnd = HDPublicKey.DepthStart + HDPublicKey.DepthSize;
|
482 | HDPublicKey.ParentFingerPrintStart = HDPublicKey.DepthEnd;
|
483 | HDPublicKey.ParentFingerPrintEnd = HDPublicKey.ParentFingerPrintStart + HDPublicKey.ParentFingerPrintSize;
|
484 | HDPublicKey.ChildIndexStart = HDPublicKey.ParentFingerPrintEnd;
|
485 | HDPublicKey.ChildIndexEnd = HDPublicKey.ChildIndexStart + HDPublicKey.ChildIndexSize;
|
486 | HDPublicKey.ChainCodeStart = HDPublicKey.ChildIndexEnd;
|
487 | HDPublicKey.ChainCodeEnd = HDPublicKey.ChainCodeStart + HDPublicKey.ChainCodeSize;
|
488 | HDPublicKey.PublicKeyStart = HDPublicKey.ChainCodeEnd;
|
489 | HDPublicKey.PublicKeyEnd = HDPublicKey.PublicKeyStart + HDPublicKey.PublicKeySize;
|
490 | HDPublicKey.ChecksumStart = HDPublicKey.PublicKeyEnd;
|
491 | HDPublicKey.ChecksumEnd = HDPublicKey.ChecksumStart + HDPublicKey.CheckSumSize;
|
492 |
|
493 | assert(HDPublicKey.PublicKeyEnd === HDPublicKey.DataSize);
|
494 | assert(HDPublicKey.ChecksumEnd === HDPublicKey.SerializedByteSize);
|
495 |
|
496 | module.exports = HDPublicKey;
|