UNPKG

16.9 kBJavaScriptView Raw
1'use strict';
2
3var _ = require('lodash');
4var $ = require('./util/preconditions');
5
6var BN = require('./crypto/bn');
7var Base58 = require('./encoding/base58');
8var Base58Check = require('./encoding/base58check');
9var Hash = require('./crypto/hash');
10var HDPrivateKey = require('./hdprivatekey');
11var Network = require('./networks');
12var Point = require('./crypto/point');
13var PublicKey = require('./publickey');
14
15var bitcoreErrors = require('./errors');
16var errors = bitcoreErrors;
17var hdErrors = bitcoreErrors.HDPublicKey;
18var assert = require('assert');
19
20var JSUtil = require('./util/js');
21var BufferUtil = require('./util/buffer');
22
23/**
24 * The representation of an hierarchically derived public key.
25 *
26 * See https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
27 *
28 * @constructor
29 * @param {Object|string|Buffer} arg
30 */
31function HDPublicKey(arg) {
32 /* jshint maxcomplexity: 12 */
33 /* jshint maxstatements: 20 */
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 * Verifies that a given path is valid.
71 *
72 * @param {string|number} arg
73 * @return {boolean}
74 */
75HDPublicKey.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 * WARNING: This method is deprecated. Use deriveChild instead.
90 *
91 *
92 * Get a derivated child based on a string or number.
93 *
94 * If the first argument is a string, it's parsed as the full path of
95 * derivation. Valid values for this argument include "m" (which returns the
96 * same public key), "m/0/1/40/2/1000".
97 *
98 * Note that hardened keys can't be derived from a public extended key.
99 *
100 * If the first argument is a number, the child with that index will be
101 * derived. See the example usage for clarification.
102 *
103 * @example
104 * ```javascript
105 * var parent = new HDPublicKey('xpub...');
106 * var child_0_1_2 = parent.derive(0).derive(1).derive(2);
107 * var copy_of_child_0_1_2 = parent.derive("m/0/1/2");
108 * assert(child_0_1_2.xprivkey === copy_of_child_0_1_2);
109 * ```
110 *
111 * @param {string|number} arg
112 */
113HDPublicKey.prototype.derive = function(arg, hardened) {
114 return this.deriveChild(arg, hardened);
115};
116
117/**
118 * WARNING: This method will not be officially supported until v1.0.0.
119 *
120 *
121 * Get a derivated child based on a string or number.
122 *
123 * If the first argument is a string, it's parsed as the full path of
124 * derivation. Valid values for this argument include "m" (which returns the
125 * same public key), "m/0/1/40/2/1000".
126 *
127 * Note that hardened keys can't be derived from a public extended key.
128 *
129 * If the first argument is a number, the child with that index will be
130 * derived. See the example usage for clarification.
131 *
132 * @example
133 * ```javascript
134 * var parent = new HDPublicKey('xpub...');
135 * var child_0_1_2 = parent.deriveChild(0).deriveChild(1).deriveChild(2);
136 * var copy_of_child_0_1_2 = parent.deriveChild("m/0/1/2");
137 * assert(child_0_1_2.xprivkey === copy_of_child_0_1_2);
138 * ```
139 *
140 * @param {string|number} arg
141 */
142HDPublicKey.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
152HDPublicKey.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
185HDPublicKey.prototype._deriveFromString = function(path) {
186 /* jshint maxcomplexity: 8 */
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 * Verifies that a given serialized public key in base58 with checksum format
203 * is valid.
204 *
205 * @param {string|Buffer} data - the serialized public key
206 * @param {string|Network=} network - optional, if present, checks that the
207 * network provided matches the network serialized.
208 * @return {boolean}
209 */
210HDPublicKey.isValidSerialized = function(data, network) {
211 return _.isNull(HDPublicKey.getSerializedError(data, network));
212};
213
214/**
215 * Checks what's the error that causes the validation of a serialized public key
216 * in base58 with checksum to fail.
217 *
218 * @param {string|Buffer} data - the serialized public key
219 * @param {string|Network=} network - optional, if present, checks that the
220 * network provided matches the network serialized.
221 * @return {errors|null}
222 */
223HDPublicKey.getSerializedError = function(data, network) {
224 /* jshint maxcomplexity: 10 */
225 /* jshint maxstatements: 20 */
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
253HDPublicKey._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
265HDPublicKey.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
276HDPublicKey.prototype._buildFromObject = function(arg) {
277 /* jshint maxcomplexity: 10 */
278 // TODO: Type validation
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
292HDPublicKey.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 * Receives a object with buffers in all the properties and populates the
310 * internal structure
311 *
312 * @param {Object} arg
313 * @param {buffer.Buffer} arg.version
314 * @param {buffer.Buffer} arg.depth
315 * @param {buffer.Buffer} arg.parentFingerPrint
316 * @param {buffer.Buffer} arg.childIndex
317 * @param {buffer.Buffer} arg.chainCode
318 * @param {buffer.Buffer} arg.publicKey
319 * @param {buffer.Buffer} arg.checksum
320 * @param {string=} arg.xpubkey - if set, don't recalculate the base58
321 * representation
322 * @return {HDPublicKey} this
323 */
324HDPublicKey.prototype._buildFromBuffers = function(arg) {
325 /* jshint maxcomplexity: 8 */
326 /* jshint maxstatements: 20 */
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
368HDPublicKey._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
388HDPublicKey.fromString = function(arg) {
389 $.checkArgument(_.isString(arg), 'No valid string was provided');
390 return new HDPublicKey(arg);
391};
392
393HDPublicKey.fromObject = function(arg) {
394 $.checkArgument(_.isObject(arg), 'No valid argument was provided');
395 return new HDPublicKey(arg);
396};
397
398/**
399 * Returns the base58 checked representation of the public key
400 * @return {string} a string starting with "xpub..." in livenet
401 */
402HDPublicKey.prototype.toString = function() {
403 return this.xpubkey;
404};
405
406/**
407 * Returns the console representation of this extended public key.
408 * @return string
409 */
410HDPublicKey.prototype.inspect = function() {
411 return '<HDPublicKey: ' + this.xpubkey + '>';
412};
413
414/**
415 * Returns a plain JavaScript object with information to reconstruct a key.
416 *
417 * Fields are: <ul>
418 * <li> network: 'livenet' or 'testnet'
419 * <li> depth: a number from 0 to 255, the depth to the master extended key
420 * <li> fingerPrint: a number of 32 bits taken from the hash of the public key
421 * <li> fingerPrint: a number of 32 bits taken from the hash of this key's
422 * <li> parent's public key
423 * <li> childIndex: index with which this key was derived
424 * <li> chainCode: string in hexa encoding used for derivation
425 * <li> publicKey: string, hexa encoded, in compressed key format
426 * <li> checksum: BufferUtil.integerFromBuffer(this._buffers.checksum),
427 * <li> xpubkey: the string with the base58 representation of this extended key
428 * <li> checksum: the base58 checksum of xpubkey
429 * </ul>
430 */
431HDPublicKey.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 * Create a HDPublicKey from a buffer argument
447 *
448 * @param {Buffer} arg
449 * @return {HDPublicKey}
450 */
451HDPublicKey.fromBuffer = function(arg) {
452 return new HDPublicKey(arg);
453};
454
455/**
456 * Return a buffer representation of the xpubkey
457 *
458 * @return {Buffer}
459 */
460HDPublicKey.prototype.toBuffer = function() {
461 return BufferUtil.copy(this._buffers.xpubkey);
462};
463
464HDPublicKey.Hardened = 0x80000000;
465HDPublicKey.RootElementAlias = ['m', 'M'];
466
467HDPublicKey.VersionSize = 4;
468HDPublicKey.DepthSize = 1;
469HDPublicKey.ParentFingerPrintSize = 4;
470HDPublicKey.ChildIndexSize = 4;
471HDPublicKey.ChainCodeSize = 32;
472HDPublicKey.PublicKeySize = 33;
473HDPublicKey.CheckSumSize = 4;
474
475HDPublicKey.DataSize = 78;
476HDPublicKey.SerializedByteSize = 82;
477
478HDPublicKey.VersionStart = 0;
479HDPublicKey.VersionEnd = HDPublicKey.VersionStart + HDPublicKey.VersionSize;
480HDPublicKey.DepthStart = HDPublicKey.VersionEnd;
481HDPublicKey.DepthEnd = HDPublicKey.DepthStart + HDPublicKey.DepthSize;
482HDPublicKey.ParentFingerPrintStart = HDPublicKey.DepthEnd;
483HDPublicKey.ParentFingerPrintEnd = HDPublicKey.ParentFingerPrintStart + HDPublicKey.ParentFingerPrintSize;
484HDPublicKey.ChildIndexStart = HDPublicKey.ParentFingerPrintEnd;
485HDPublicKey.ChildIndexEnd = HDPublicKey.ChildIndexStart + HDPublicKey.ChildIndexSize;
486HDPublicKey.ChainCodeStart = HDPublicKey.ChildIndexEnd;
487HDPublicKey.ChainCodeEnd = HDPublicKey.ChainCodeStart + HDPublicKey.ChainCodeSize;
488HDPublicKey.PublicKeyStart = HDPublicKey.ChainCodeEnd;
489HDPublicKey.PublicKeyEnd = HDPublicKey.PublicKeyStart + HDPublicKey.PublicKeySize;
490HDPublicKey.ChecksumStart = HDPublicKey.PublicKeyEnd;
491HDPublicKey.ChecksumEnd = HDPublicKey.ChecksumStart + HDPublicKey.CheckSumSize;
492
493assert(HDPublicKey.PublicKeyEnd === HDPublicKey.DataSize);
494assert(HDPublicKey.ChecksumEnd === HDPublicKey.SerializedByteSize);
495
496module.exports = HDPublicKey;