UNPKG

16.5 kBJavaScriptView Raw
1'use strict'
2
3var _ = require('./util/_')
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 bsvErrors = require('./errors')
16var errors = bsvErrors
17var hdErrors = bsvErrors.HDPublicKey
18var assert = require('assert')
19
20var JSUtil = require('./util/js')
21
22/**
23 * The representation of an hierarchically derived public key.
24 *
25 * See https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
26 *
27 * @constructor
28 * @param {Object|string|Buffer} arg
29 */
30function HDPublicKey (arg) {
31 if (arg instanceof HDPublicKey) {
32 return arg
33 }
34 if (!(this instanceof HDPublicKey)) {
35 return new HDPublicKey(arg)
36 }
37 if (arg) {
38 if (_.isString(arg) || Buffer.isBuffer(arg)) {
39 var error = HDPublicKey.getSerializedError(arg)
40 if (!error) {
41 return this._buildFromSerialized(arg)
42 } else if (Buffer.isBuffer(arg) && !HDPublicKey.getSerializedError(arg.toString())) {
43 return this._buildFromSerialized(arg.toString())
44 } else {
45 if (error instanceof hdErrors.ArgumentIsPrivateExtended) {
46 return new HDPrivateKey(arg).hdPublicKey
47 }
48 throw error
49 }
50 } else {
51 if (_.isObject(arg)) {
52 if (arg instanceof HDPrivateKey) {
53 return this._buildFromPrivate(arg)
54 } else {
55 return this._buildFromObject(arg)
56 }
57 } else {
58 throw new hdErrors.UnrecognizedArgument(arg)
59 }
60 }
61 } else {
62 throw new hdErrors.MustSupplyArgument()
63 }
64}
65
66HDPublicKey.fromHDPrivateKey = function (hdPrivateKey) {
67 return new HDPublicKey(hdPrivateKey)
68}
69
70/**
71 * Verifies that a given path is valid.
72 *
73 * @param {string|number} arg
74 * @return {boolean}
75 */
76HDPublicKey.isValidPath = function (arg) {
77 if (_.isString(arg)) {
78 var indexes = HDPrivateKey._getDerivationIndexes(arg)
79 return indexes !== null && _.every(indexes, HDPublicKey.isValidPath)
80 }
81
82 if (_.isNumber(arg)) {
83 return arg >= 0 && arg < HDPublicKey.Hardened
84 }
85
86 return false
87}
88
89/**
90 * WARNING: This method is deprecated. Use deriveChild instead.
91 *
92 *
93 * Get a derivated child based on a string or number.
94 *
95 * If the first argument is a string, it's parsed as the full path of
96 * derivation. Valid values for this argument include "m" (which returns the
97 * same public key), "m/0/1/40/2/1000".
98 *
99 * Note that hardened keys can't be derived from a public extended key.
100 *
101 * If the first argument is a number, the child with that index will be
102 * derived. See the example usage for clarification.
103 *
104 * @example
105 * ```javascript
106 * var parent = new HDPublicKey('xpub...');
107 * var child_0_1_2 = parent.derive(0).derive(1).derive(2);
108 * var copy_of_child_0_1_2 = parent.derive("m/0/1/2");
109 * assert(child_0_1_2.xprivkey === copy_of_child_0_1_2);
110 * ```
111 *
112 * @param {string|number} arg
113 */
114HDPublicKey.prototype.derive = function () {
115 throw new Error('derive has been deprecated. use deriveChild or, for the old way, deriveNonCompliantChild.')
116}
117
118/**
119 * WARNING: This method will not be officially supported until v1.0.0.
120 *
121 *
122 * Get a derivated child based on a string or number.
123 *
124 * If the first argument is a string, it's parsed as the full path of
125 * derivation. Valid values for this argument include "m" (which returns the
126 * same public key), "m/0/1/40/2/1000".
127 *
128 * Note that hardened keys can't be derived from a public extended key.
129 *
130 * If the first argument is a number, the child with that index will be
131 * derived. See the example usage for clarification.
132 *
133 * @example
134 * ```javascript
135 * var parent = new HDPublicKey('xpub...');
136 * var child_0_1_2 = parent.deriveChild(0).deriveChild(1).deriveChild(2);
137 * var copy_of_child_0_1_2 = parent.deriveChild("m/0/1/2");
138 * assert(child_0_1_2.xprivkey === copy_of_child_0_1_2);
139 * ```
140 *
141 * @param {string|number} arg
142 */
143HDPublicKey.prototype.deriveChild = function (arg, hardened) {
144 if (_.isNumber(arg)) {
145 return this._deriveWithNumber(arg, hardened)
146 } else if (_.isString(arg)) {
147 return this._deriveFromString(arg)
148 } else {
149 throw new hdErrors.InvalidDerivationArgument(arg)
150 }
151}
152
153HDPublicKey.prototype._deriveWithNumber = function (index, hardened) {
154 if (index >= HDPublicKey.Hardened || hardened) {
155 throw new hdErrors.InvalidIndexCantDeriveHardened()
156 }
157 if (index < 0) {
158 throw new hdErrors.InvalidPath(index)
159 }
160
161 var indexBuffer = JSUtil.integerAsBuffer(index)
162 var data = Buffer.concat([this.publicKey.toBuffer(), indexBuffer])
163 var hash = Hash.sha512hmac(data, this._buffers.chainCode)
164 var leftPart = BN.fromBuffer(hash.slice(0, 32), { size: 32 })
165 var chainCode = hash.slice(32, 64)
166
167 var publicKey
168 try {
169 publicKey = PublicKey.fromPoint(Point.getG().mul(leftPart).add(this.publicKey.point))
170 } catch (e) {
171 return this._deriveWithNumber(index + 1)
172 }
173
174 var derived = new HDPublicKey({
175 network: this.network,
176 depth: this.depth + 1,
177 parentFingerPrint: this.fingerPrint,
178 childIndex: index,
179 chainCode: chainCode,
180 publicKey: publicKey
181 })
182
183 return derived
184}
185
186HDPublicKey.prototype._deriveFromString = function (path) {
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 if (!(_.isString(data) || Buffer.isBuffer(data))) {
225 return new hdErrors.UnrecognizedArgument('expected buffer or string')
226 }
227 if (!Base58.validCharacters(data)) {
228 return new errors.InvalidB58Char('(unknown)', data)
229 }
230 try {
231 data = Base58Check.decode(data)
232 } catch (e) {
233 return new errors.InvalidB58Checksum(data)
234 }
235 if (data.length !== HDPublicKey.DataSize) {
236 return new hdErrors.InvalidLength(data)
237 }
238 if (!_.isUndefined(network)) {
239 var error = HDPublicKey._validateNetwork(data, network)
240 if (error) {
241 return error
242 }
243 }
244 var version = data.readUInt32BE(0)
245 if (version === Network.livenet.xprivkey || version === Network.testnet.xprivkey) {
246 return new hdErrors.ArgumentIsPrivateExtended()
247 }
248 return null
249}
250
251HDPublicKey._validateNetwork = function (data, networkArg) {
252 var network = Network.get(networkArg)
253 if (!network) {
254 return new errors.InvalidNetworkArgument(networkArg)
255 }
256 var version = data.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd)
257 if (version.readUInt32BE(0) !== network.xpubkey) {
258 return new errors.InvalidNetwork(version)
259 }
260 return null
261}
262
263HDPublicKey.prototype._buildFromPrivate = function (arg) {
264 var args = _.clone(arg._buffers)
265 var point = Point.getG().mul(BN.fromBuffer(args.privateKey))
266 args.publicKey = Point.pointToCompressed(point)
267 args.version = JSUtil.integerAsBuffer(Network.get(args.version.readUInt32BE(0)).xpubkey)
268 args.privateKey = undefined
269 args.checksum = undefined
270 args.xprivkey = undefined
271 return this._buildFromBuffers(args)
272}
273
274HDPublicKey.prototype._buildFromObject = function (arg) {
275 // TODO: Type validation
276 var buffers = {
277 version: arg.network ? JSUtil.integerAsBuffer(Network.get(arg.network).xpubkey) : arg.version,
278 depth: _.isNumber(arg.depth) ? Buffer.from([arg.depth & 0xff]) : arg.depth,
279 parentFingerPrint: _.isNumber(arg.parentFingerPrint) ? JSUtil.integerAsBuffer(arg.parentFingerPrint) : arg.parentFingerPrint,
280 childIndex: _.isNumber(arg.childIndex) ? JSUtil.integerAsBuffer(arg.childIndex) : arg.childIndex,
281 chainCode: _.isString(arg.chainCode) ? Buffer.from(arg.chainCode, 'hex') : arg.chainCode,
282 publicKey: _.isString(arg.publicKey) ? Buffer.from(arg.publicKey, 'hex')
283 : Buffer.isBuffer(arg.publicKey) ? arg.publicKey : arg.publicKey.toBuffer(),
284 checksum: _.isNumber(arg.checksum) ? JSUtil.integerAsBuffer(arg.checksum) : arg.checksum
285 }
286 return this._buildFromBuffers(buffers)
287}
288
289HDPublicKey.prototype._buildFromSerialized = function (arg) {
290 var decoded = Base58Check.decode(arg)
291 var buffers = {
292 version: decoded.slice(HDPublicKey.VersionStart, HDPublicKey.VersionEnd),
293 depth: decoded.slice(HDPublicKey.DepthStart, HDPublicKey.DepthEnd),
294 parentFingerPrint: decoded.slice(HDPublicKey.ParentFingerPrintStart,
295 HDPublicKey.ParentFingerPrintEnd),
296 childIndex: decoded.slice(HDPublicKey.ChildIndexStart, HDPublicKey.ChildIndexEnd),
297 chainCode: decoded.slice(HDPublicKey.ChainCodeStart, HDPublicKey.ChainCodeEnd),
298 publicKey: decoded.slice(HDPublicKey.PublicKeyStart, HDPublicKey.PublicKeyEnd),
299 checksum: decoded.slice(HDPublicKey.ChecksumStart, HDPublicKey.ChecksumEnd),
300 xpubkey: arg
301 }
302 return this._buildFromBuffers(buffers)
303}
304
305/**
306 * Receives a object with buffers in all the properties and populates the
307 * internal structure
308 *
309 * @param {Object} arg
310 * @param {buffer.Buffer} arg.version
311 * @param {buffer.Buffer} arg.depth
312 * @param {buffer.Buffer} arg.parentFingerPrint
313 * @param {buffer.Buffer} arg.childIndex
314 * @param {buffer.Buffer} arg.chainCode
315 * @param {buffer.Buffer} arg.publicKey
316 * @param {buffer.Buffer} arg.checksum
317 * @param {string=} arg.xpubkey - if set, don't recalculate the base58
318 * representation
319 * @return {HDPublicKey} this
320 */
321HDPublicKey.prototype._buildFromBuffers = function (arg) {
322 HDPublicKey._validateBufferArguments(arg)
323
324 JSUtil.defineImmutable(this, {
325 _buffers: arg
326 })
327
328 var sequence = [
329 arg.version, arg.depth, arg.parentFingerPrint, arg.childIndex, arg.chainCode,
330 arg.publicKey
331 ]
332 var concat = Buffer.concat(sequence)
333 var checksum = Base58Check.checksum(concat)
334 if (!arg.checksum || !arg.checksum.length) {
335 arg.checksum = checksum
336 } else {
337 if (arg.checksum.toString('hex') !== checksum.toString('hex')) {
338 throw new errors.InvalidB58Checksum(concat, checksum)
339 }
340 }
341 var network = Network.get(arg.version.readUInt32BE(0))
342
343 var xpubkey
344 xpubkey = Base58Check.encode(Buffer.concat(sequence))
345 arg.xpubkey = Buffer.from(xpubkey)
346
347 var publicKey = new PublicKey(arg.publicKey, { network: network })
348 var size = HDPublicKey.ParentFingerPrintSize
349 var fingerPrint = Hash.sha256ripemd160(publicKey.toBuffer()).slice(0, size)
350
351 JSUtil.defineImmutable(this, {
352 xpubkey: xpubkey,
353 network: network,
354 depth: arg.depth[0],
355 publicKey: publicKey,
356 fingerPrint: fingerPrint
357 })
358
359 return this
360}
361
362HDPublicKey._validateBufferArguments = function (arg) {
363 var checkBuffer = function (name, size) {
364 var buff = arg[name]
365 assert(Buffer.isBuffer(buff), name + ' argument is not a buffer, it\'s ' + typeof buff)
366 assert(
367 buff.length === size,
368 name + ' has not the expected size: found ' + buff.length + ', expected ' + size
369 )
370 }
371 checkBuffer('version', HDPublicKey.VersionSize)
372 checkBuffer('depth', HDPublicKey.DepthSize)
373 checkBuffer('parentFingerPrint', HDPublicKey.ParentFingerPrintSize)
374 checkBuffer('childIndex', HDPublicKey.ChildIndexSize)
375 checkBuffer('chainCode', HDPublicKey.ChainCodeSize)
376 checkBuffer('publicKey', HDPublicKey.PublicKeySize)
377 if (arg.checksum && arg.checksum.length) {
378 checkBuffer('checksum', HDPublicKey.CheckSumSize)
379 }
380}
381
382HDPublicKey.fromString = function (arg) {
383 $.checkArgument(_.isString(arg), 'No valid string was provided')
384 return new HDPublicKey(arg)
385}
386
387HDPublicKey.fromObject = function (arg) {
388 $.checkArgument(_.isObject(arg), 'No valid argument was provided')
389 return new HDPublicKey(arg)
390}
391
392/**
393 * Returns the base58 checked representation of the public key
394 * @return {string} a string starting with "xpub..." in livenet
395 */
396HDPublicKey.prototype.toString = function () {
397 return this.xpubkey
398}
399
400/**
401 * Returns the console representation of this extended public key.
402 * @return string
403 */
404HDPublicKey.prototype.inspect = function () {
405 return '<HDPublicKey: ' + this.xpubkey + '>'
406}
407
408/**
409 * Returns a plain JavaScript object with information to reconstruct a key.
410 *
411 * Fields are: <ul>
412 * <li> network: 'livenet' or 'testnet'
413 * <li> depth: a number from 0 to 255, the depth to the master extended key
414 * <li> fingerPrint: a number of 32 bits taken from the hash of the public key
415 * <li> fingerPrint: a number of 32 bits taken from the hash of this key's
416 * <li> parent's public key
417 * <li> childIndex: index with which this key was derived
418 * <li> chainCode: string in hexa encoding used for derivation
419 * <li> publicKey: string, hexa encoded, in compressed key format
420 * <li> checksum: this._buffers.checksum.readUInt32BE(0),
421 * <li> xpubkey: the string with the base58 representation of this extended key
422 * <li> checksum: the base58 checksum of xpubkey
423 * </ul>
424 */
425HDPublicKey.prototype.toObject = HDPublicKey.prototype.toJSON = function toObject () {
426 return {
427 network: Network.get(this._buffers.version.readUInt32BE(0)).name,
428 depth: this._buffers.depth[0],
429 fingerPrint: this.fingerPrint.readUInt32BE(0),
430 parentFingerPrint: this._buffers.parentFingerPrint.readUInt32BE(0),
431 childIndex: this._buffers.childIndex.readUInt32BE(0),
432 chainCode: this._buffers.chainCode.toString('hex'),
433 publicKey: this.publicKey.toString(),
434 checksum: this._buffers.checksum.readUInt32BE(0),
435 xpubkey: this.xpubkey
436 }
437}
438
439/**
440 * Create a HDPublicKey from a buffer argument
441 *
442 * @param {Buffer} arg
443 * @return {HDPublicKey}
444 */
445HDPublicKey.fromBuffer = function (arg) {
446 return new HDPublicKey(arg)
447}
448
449/**
450 * Create a HDPublicKey from a hex string argument
451 *
452 * @param {Buffer} arg
453 * @return {HDPublicKey}
454 */
455HDPublicKey.fromHex = function (hex) {
456 return HDPublicKey.fromBuffer(Buffer.from(hex, 'hex'))
457}
458
459/**
460 * Return a buffer representation of the xpubkey
461 *
462 * @return {Buffer}
463 */
464HDPublicKey.prototype.toBuffer = function () {
465 return Buffer.from(this._buffers.xpubkey)
466}
467
468/**
469 * Return a hex string representation of the xpubkey
470 *
471 * @return {Buffer}
472 */
473HDPublicKey.prototype.toHex = function () {
474 return this.toBuffer().toString('hex')
475}
476
477HDPublicKey.Hardened = 0x80000000
478HDPublicKey.RootElementAlias = ['m', 'M']
479
480HDPublicKey.VersionSize = 4
481HDPublicKey.DepthSize = 1
482HDPublicKey.ParentFingerPrintSize = 4
483HDPublicKey.ChildIndexSize = 4
484HDPublicKey.ChainCodeSize = 32
485HDPublicKey.PublicKeySize = 33
486HDPublicKey.CheckSumSize = 4
487
488HDPublicKey.DataSize = 78
489HDPublicKey.SerializedByteSize = 82
490
491HDPublicKey.VersionStart = 0
492HDPublicKey.VersionEnd = HDPublicKey.VersionStart + HDPublicKey.VersionSize
493HDPublicKey.DepthStart = HDPublicKey.VersionEnd
494HDPublicKey.DepthEnd = HDPublicKey.DepthStart + HDPublicKey.DepthSize
495HDPublicKey.ParentFingerPrintStart = HDPublicKey.DepthEnd
496HDPublicKey.ParentFingerPrintEnd = HDPublicKey.ParentFingerPrintStart + HDPublicKey.ParentFingerPrintSize
497HDPublicKey.ChildIndexStart = HDPublicKey.ParentFingerPrintEnd
498HDPublicKey.ChildIndexEnd = HDPublicKey.ChildIndexStart + HDPublicKey.ChildIndexSize
499HDPublicKey.ChainCodeStart = HDPublicKey.ChildIndexEnd
500HDPublicKey.ChainCodeEnd = HDPublicKey.ChainCodeStart + HDPublicKey.ChainCodeSize
501HDPublicKey.PublicKeyStart = HDPublicKey.ChainCodeEnd
502HDPublicKey.PublicKeyEnd = HDPublicKey.PublicKeyStart + HDPublicKey.PublicKeySize
503HDPublicKey.ChecksumStart = HDPublicKey.PublicKeyEnd
504HDPublicKey.ChecksumEnd = HDPublicKey.ChecksumStart + HDPublicKey.CheckSumSize
505
506assert(HDPublicKey.PublicKeyEnd === HDPublicKey.DataSize)
507assert(HDPublicKey.ChecksumEnd === HDPublicKey.SerializedByteSize)
508
509module.exports = HDPublicKey