1 | 'use strict'
|
2 |
|
3 | var _ = require('./util/_')
|
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 bsvErrors = require('./errors')
|
16 | var errors = bsvErrors
|
17 | var hdErrors = bsvErrors.HDPublicKey
|
18 | var assert = require('assert')
|
19 |
|
20 | var JSUtil = require('./util/js')
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | function 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 |
|
66 | HDPublicKey.fromHDPrivateKey = function (hdPrivateKey) {
|
67 | return new HDPublicKey(hdPrivateKey)
|
68 | }
|
69 |
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | HDPublicKey.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 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 |
|
98 |
|
99 |
|
100 |
|
101 |
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | HDPublicKey.prototype.derive = function () {
|
115 | throw new Error('derive has been deprecated. use deriveChild or, for the old way, deriveNonCompliantChild.')
|
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 | HDPublicKey.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 |
|
153 | HDPublicKey.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 |
|
186 | HDPublicKey.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 |
|
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 | 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 |
|
251 | HDPublicKey._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 |
|
263 | HDPublicKey.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 |
|
274 | HDPublicKey.prototype._buildFromObject = function (arg) {
|
275 |
|
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 |
|
289 | HDPublicKey.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 |
|
307 |
|
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 | HDPublicKey.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 |
|
362 | HDPublicKey._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 |
|
382 | HDPublicKey.fromString = function (arg) {
|
383 | $.checkArgument(_.isString(arg), 'No valid string was provided')
|
384 | return new HDPublicKey(arg)
|
385 | }
|
386 |
|
387 | HDPublicKey.fromObject = function (arg) {
|
388 | $.checkArgument(_.isObject(arg), 'No valid argument was provided')
|
389 | return new HDPublicKey(arg)
|
390 | }
|
391 |
|
392 |
|
393 |
|
394 |
|
395 |
|
396 | HDPublicKey.prototype.toString = function () {
|
397 | return this.xpubkey
|
398 | }
|
399 |
|
400 |
|
401 |
|
402 |
|
403 |
|
404 | HDPublicKey.prototype.inspect = function () {
|
405 | return '<HDPublicKey: ' + this.xpubkey + '>'
|
406 | }
|
407 |
|
408 |
|
409 |
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 | HDPublicKey.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 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 | HDPublicKey.fromBuffer = function (arg) {
|
446 | return new HDPublicKey(arg)
|
447 | }
|
448 |
|
449 |
|
450 |
|
451 |
|
452 |
|
453 |
|
454 |
|
455 | HDPublicKey.fromHex = function (hex) {
|
456 | return HDPublicKey.fromBuffer(Buffer.from(hex, 'hex'))
|
457 | }
|
458 |
|
459 |
|
460 |
|
461 |
|
462 |
|
463 |
|
464 | HDPublicKey.prototype.toBuffer = function () {
|
465 | return Buffer.from(this._buffers.xpubkey)
|
466 | }
|
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 | HDPublicKey.prototype.toHex = function () {
|
474 | return this.toBuffer().toString('hex')
|
475 | }
|
476 |
|
477 | HDPublicKey.Hardened = 0x80000000
|
478 | HDPublicKey.RootElementAlias = ['m', 'M']
|
479 |
|
480 | HDPublicKey.VersionSize = 4
|
481 | HDPublicKey.DepthSize = 1
|
482 | HDPublicKey.ParentFingerPrintSize = 4
|
483 | HDPublicKey.ChildIndexSize = 4
|
484 | HDPublicKey.ChainCodeSize = 32
|
485 | HDPublicKey.PublicKeySize = 33
|
486 | HDPublicKey.CheckSumSize = 4
|
487 |
|
488 | HDPublicKey.DataSize = 78
|
489 | HDPublicKey.SerializedByteSize = 82
|
490 |
|
491 | HDPublicKey.VersionStart = 0
|
492 | HDPublicKey.VersionEnd = HDPublicKey.VersionStart + HDPublicKey.VersionSize
|
493 | HDPublicKey.DepthStart = HDPublicKey.VersionEnd
|
494 | HDPublicKey.DepthEnd = HDPublicKey.DepthStart + HDPublicKey.DepthSize
|
495 | HDPublicKey.ParentFingerPrintStart = HDPublicKey.DepthEnd
|
496 | HDPublicKey.ParentFingerPrintEnd = HDPublicKey.ParentFingerPrintStart + HDPublicKey.ParentFingerPrintSize
|
497 | HDPublicKey.ChildIndexStart = HDPublicKey.ParentFingerPrintEnd
|
498 | HDPublicKey.ChildIndexEnd = HDPublicKey.ChildIndexStart + HDPublicKey.ChildIndexSize
|
499 | HDPublicKey.ChainCodeStart = HDPublicKey.ChildIndexEnd
|
500 | HDPublicKey.ChainCodeEnd = HDPublicKey.ChainCodeStart + HDPublicKey.ChainCodeSize
|
501 | HDPublicKey.PublicKeyStart = HDPublicKey.ChainCodeEnd
|
502 | HDPublicKey.PublicKeyEnd = HDPublicKey.PublicKeyStart + HDPublicKey.PublicKeySize
|
503 | HDPublicKey.ChecksumStart = HDPublicKey.PublicKeyEnd
|
504 | HDPublicKey.ChecksumEnd = HDPublicKey.ChecksumStart + HDPublicKey.CheckSumSize
|
505 |
|
506 | assert(HDPublicKey.PublicKeyEnd === HDPublicKey.DataSize)
|
507 | assert(HDPublicKey.ChecksumEnd === HDPublicKey.SerializedByteSize)
|
508 |
|
509 | module.exports = HDPublicKey
|