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