• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

handshake-org / hsd / 11142522588

02 Oct 2024 10:56AM UTC coverage: 70.04% (+0.007%) from 70.033%
11142522588

push

github

nodech
Merge PR #902 from 'nodech/update-types'

7761 of 12916 branches covered (60.09%)

Branch coverage included in aggregate %.

67 of 92 new or added lines in 25 files covered. (72.83%)

8 existing lines in 5 files now uncovered.

24790 of 33559 relevant lines covered (73.87%)

34019.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

65.45
/lib/hd/private.js
1
/*!
2
 * private.js - hd private keys for hsd
3
 * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
4
 * https://github.com/handshake-org/hsd
5
 */
6

7
'use strict';
8

9
const assert = require('bsert');
1✔
10
const bio = require('bufio');
1✔
11
const base58 = require('bcrypto/lib/encoding/base58');
1✔
12
const sha512 = require('bcrypto/lib/sha512');
1✔
13
const hash160 = require('bcrypto/lib/hash160');
1✔
14
const hash256 = require('bcrypto/lib/hash256');
1✔
15
const cleanse = require('bcrypto/lib/cleanse');
1✔
16
const random = require('bcrypto/lib/random');
1✔
17
const secp256k1 = require('bcrypto/lib/secp256k1');
1✔
18
const Network = require('../protocol/network');
1✔
19
const consensus = require('../protocol/consensus');
1✔
20
const common = require('./common');
1✔
21
const Mnemonic = require('./mnemonic');
1✔
22
const HDPublicKey = require('./public');
1✔
23

24
/** @typedef {import('../types').Base58String} Base58String */
25
/** @typedef {import('../types').NetworkType} NetworkType */
26
/** @typedef {import('../types').BufioWriter} BufioWriter */
27

28
/*
29
 * Constants
30
 */
31

32
const SEED_SALT = Buffer.from('Bitcoin seed', 'ascii');
1✔
33

34
/**
35
 * @typedef {Object} HDPrivateKeyOptions
36
 * @property {Number} depth
37
 * @property {Number} parentFingerPrint
38
 * @property {Number} childIndex
39
 * @property {Buffer} chainCode
40
 * @property {Buffer} privateKey
41
 */
42

43
/**
44
 * HDPrivateKey
45
 * @alias module:hd.PrivateKey
46
 * @property {Number} depth
47
 * @property {Number} parentFingerPrint
48
 * @property {Number} childIndex
49
 * @property {Buffer} chainCode
50
 * @property {Buffer} privateKey
51
 */
52

53
class HDPrivateKey extends bio.Struct {
54
  /**
55
   * Create an hd private key.
56
   * @constructor
57
   * @param {HDPrivateKeyOptions} [options]
58
   */
59

60
  constructor(options) {
61
    super();
40,274✔
62

63
    this.depth = 0;
40,274✔
64
    this.parentFingerPrint = 0;
40,274✔
65
    this.childIndex = 0;
40,274✔
66
    this.chainCode = consensus.ZERO_HASH;
40,274✔
67
    this.privateKey = consensus.ZERO_HASH;
40,274✔
68

69
    this.publicKey = common.ZERO_KEY;
40,274✔
70
    this.fingerPrint = -1;
40,274✔
71

72
    this._hdPublicKey = null;
40,274✔
73

74
    if (options)
40,274!
75
      this.fromOptions(options);
×
76
  }
77

78
  /**
79
   * Inject properties from options object.
80
   * @param {HDPrivateKeyOptions} options
81
   */
82

83
  fromOptions(options) {
84
    assert(options, 'No options for HD private key.');
×
85
    assert((options.depth & 0xff) === options.depth);
×
86
    assert((options.parentFingerPrint >>> 0) === options.parentFingerPrint);
×
87
    assert((options.childIndex >>> 0) === options.childIndex);
×
88
    assert(Buffer.isBuffer(options.chainCode));
×
89
    assert(Buffer.isBuffer(options.privateKey));
×
90

91
    this.depth = options.depth;
×
92
    this.parentFingerPrint = options.parentFingerPrint;
×
93
    this.childIndex = options.childIndex;
×
94
    this.chainCode = options.chainCode;
×
95
    this.privateKey = options.privateKey;
×
96
    this.publicKey = secp256k1.publicKeyCreate(options.privateKey, true);
×
97

98
    return this;
×
99
  }
100

101
  /**
102
   * Get HD public key.
103
   * @returns {HDPublicKey}
104
   */
105

106
  toPublic() {
107
    let key = this._hdPublicKey;
527✔
108

109
    if (!key) {
527✔
110
      key = new HDPublicKey();
410✔
111
      key.depth = this.depth;
410✔
112
      key.parentFingerPrint = this.parentFingerPrint;
410✔
113
      key.childIndex = this.childIndex;
410✔
114
      key.chainCode = this.chainCode;
410✔
115
      key.publicKey = this.publicKey;
410✔
116
      this._hdPublicKey = key;
410✔
117
    }
118

119
    return key;
527✔
120
  }
121

122
  /**
123
   * Get cached base58 xprivkey.
124
   * @param {(NetworkType|Network)?} [network]
125
   * @returns {Base58String}
126
   */
127

128
  xprivkey(network) {
129
    return this.toBase58(network);
11✔
130
  }
131

132
  /**
133
   * Get cached base58 xpubkey.
134
   * @param {(NetworkType|Network)?} [network]
135
   * @returns {Base58String}
136
   */
137

138
  xpubkey(network) {
139
    return this.toPublic().xpubkey(network);
1✔
140
  }
141

142
  /**
143
   * Destroy the key (zeroes chain code, privkey, and pubkey).
144
   * @param {Boolean} pub - Destroy hd public key as well.
145
   */
146

147
  destroy(pub) {
148
    this.depth = 0;
27✔
149
    this.childIndex = 0;
27✔
150
    this.parentFingerPrint = 0;
27✔
151

152
    cleanse(this.chainCode);
27✔
153
    cleanse(this.privateKey);
27✔
154
    cleanse(this.publicKey);
27✔
155

156
    this.fingerPrint = -1;
27✔
157

158
    if (this._hdPublicKey) {
27!
159
      if (pub)
×
160
        this._hdPublicKey.destroy();
×
161
      this._hdPublicKey = null;
×
162
    }
163
  }
164

165
  /**
166
   * Derive a child key.
167
   * @param {Number} index - Derivation index.
168
   * @param {Boolean?} [hardened] - Whether the derivation should be hardened.
169
   * @returns {HDPrivateKey}
170
   */
171

172
  derive(index, hardened) {
173
    assert(typeof index === 'number');
266,878✔
174

175
    if ((index >>> 0) !== index)
266,878!
176
      throw new Error('Index out of range.');
×
177

178
    if (this.depth >= 0xff)
266,878!
179
      throw new Error('Depth too high.');
×
180

181
    if (hardened) {
266,878✔
182
      index |= common.HARDENED;
160,353✔
183
      index >>>= 0;
160,353✔
184
    }
185

186
    const id = this.getID(index);
266,878✔
187

188
    /** @type {HDPrivateKey} */
189
    // @ts-ignore
190
    const cache =  common.cache.get(id);
266,878✔
191

192
    if (cache)
266,878✔
193
      return cache;
227,336✔
194

195
    const bw = bio.pool(37);
39,542✔
196

197
    if (index & common.HARDENED) {
39,542✔
198
      bw.writeU8(0);
1,849✔
199
      bw.writeBytes(this.privateKey);
1,849✔
200
      bw.writeU32BE(index);
1,849✔
201
    } else {
202
      bw.writeBytes(this.publicKey);
37,693✔
203
      bw.writeU32BE(index);
37,693✔
204
    }
205

206
    const data = bw.render();
39,542✔
207

208
    const hash = sha512.mac(data, this.chainCode);
39,542✔
209
    const left = hash.slice(0, 32);
39,542✔
210
    const right = hash.slice(32, 64);
39,542✔
211

212
    let key;
213
    try {
39,542✔
214
      key = secp256k1.privateKeyTweakAdd(this.privateKey, left);
39,542✔
215
    } catch (e) {
216
      return this.derive(index + 1);
×
217
    }
218

219
    if (this.fingerPrint === -1) {
39,542✔
220
      const fp = hash160.digest(this.publicKey);
2,466✔
221
      this.fingerPrint = fp.readUInt32BE(0, true);
2,466✔
222
    }
223

224
    /** @type {HDPrivateKey} */
225
    // @ts-ignore
226
    const child = new this.constructor();
39,542✔
227
    child.depth = this.depth + 1;
39,542✔
228
    child.parentFingerPrint = this.fingerPrint;
39,542✔
229
    child.childIndex = index;
39,542✔
230
    child.chainCode = right;
39,542✔
231
    child.privateKey = key;
39,542✔
232
    child.publicKey = secp256k1.publicKeyCreate(key, true);
39,542✔
233

234
    common.cache.set(id, child);
39,542✔
235

236
    return child;
39,542✔
237
  }
238

239
  /**
240
   * Unique HD key ID.
241
   * @private
242
   * @param {Number} index
243
   * @returns {String}
244
   */
245

246
  getID(index) {
247
    return 'v' + this.publicKey.toString('hex') + index;
266,878✔
248
  }
249

250
  /**
251
   * Derive a BIP44 account key.
252
   * @param {Number} purpose
253
   * @param {Number} type
254
   * @param {Number} account
255
   * @returns {HDPrivateKey}
256
   * @throws Error if key is not a master key.
257
   */
258

259
  deriveAccount(purpose, type, account) {
260
    assert((purpose >>> 0) === purpose, 'Purpose must be a number.');
53,284✔
261
    assert((type >>> 0) === type, 'Account index must be a number.');
53,284✔
262
    assert((account >>> 0) === account, 'Account index must be a number.');
53,284✔
263
    assert(this.isMaster(), 'Cannot derive account index.');
53,284✔
264
    return this
53,284✔
265
      .derive(purpose, true)
266
      .derive(type, true)
267
      .derive(account, true);
268
  }
269

270
  /**
271
   * Test whether the key is a master key.
272
   * @returns {Boolean}
273
   */
274

275
  isMaster() {
276
    return common.isMaster(this);
53,284✔
277
  }
278

279
  /**
280
   * Test whether the key is (most likely) a BIP44 account key.
281
   * @param {Number?} account
282
   * @returns {Boolean}
283
   */
284

285
  isAccount(account) {
286
    return common.isAccount(this, account);
×
287
  }
288

289
  /**
290
   * Test whether an object is in the form of a base58 xprivkey.
291
   * @param {String} data
292
   * @param {(Network|NetworkType)?} [network]
293
   * @returns {Boolean}
294
   */
295

296
  static isBase58(data, network) {
297
    if (typeof data !== 'string')
×
298
      return false;
×
299

300
    if (data.length < 4)
×
301
      return false;
×
302

303
    const prefix = data.substring(0, 4);
×
304

305
    try {
×
306
      Network.fromPrivate58(prefix, network);
×
307
      return true;
×
308
    } catch (e) {
309
      return false;
×
310
    }
311
  }
312

313
  /**
314
   * Test whether a buffer has a valid network prefix.
315
   * @param {Buffer} data
316
   * @param {(Network|NetworkType)?} [network]
317
   * @returns {Boolean}
318
   */
319

320
  static isRaw(data, network) {
321
    if (!Buffer.isBuffer(data))
×
322
      return false;
×
323

324
    if (data.length < 4)
×
325
      return false;
×
326

NEW
327
    const version = data.readUInt32BE(0);
×
328

329
    try {
×
330
      Network.fromPrivate(version, network);
×
331
      return true;
×
332
    } catch (e) {
333
      return false;
×
334
    }
335
  }
336

337
  /**
338
   * Test whether a string is a valid path.
339
   * @param {String} path
340
   * @returns {Boolean}
341
   */
342

343
  static isValidPath(path) {
344
    try {
×
345
      common.parsePath(path, true);
×
346
      return true;
×
347
    } catch (e) {
348
      return false;
×
349
    }
350
  }
351

352
  /**
353
   * Derive a key from a derivation path.
354
   * @param {String} path
355
   * @returns {HDPrivateKey}
356
   * @throws Error if `path` is not a valid path.
357
   */
358

359
  derivePath(path) {
360
    const indexes = common.parsePath(path, true);
11✔
361

362
    /** @type {HDPrivateKey} */
363
    let key = this;
11✔
364

365
    for (const index of indexes)
11✔
366
      key = key.derive(index);
35✔
367

368
    return key;
11✔
369
  }
370

371
  /**
372
   * Compare a key against an object.
373
   * @param {HDPrivateKey} obj
374
   * @returns {Boolean}
375
   */
376

377
  equals(obj) {
378
    assert(HDPrivateKey.isHDPrivateKey(obj));
×
379

380
    return this.depth === obj.depth
×
381
      && this.parentFingerPrint === obj.parentFingerPrint
382
      && this.childIndex === obj.childIndex
383
      && this.chainCode.equals(obj.chainCode)
384
      && this.privateKey.equals(obj.privateKey);
385
  }
386

387
  /**
388
   * Compare a key against an object.
389
   * @param {HDPrivateKey} key
390
   * @returns {Number}
391
   */
392

393
  compare(key) {
394
    assert(HDPrivateKey.isHDPrivateKey(key));
×
395

396
    let cmp = this.depth - key.depth;
×
397

398
    if (cmp !== 0)
×
399
      return cmp;
×
400

401
    cmp = this.parentFingerPrint - key.parentFingerPrint;
×
402

403
    if (cmp !== 0)
×
404
      return cmp;
×
405

406
    cmp = this.childIndex - key.childIndex;
×
407

408
    if (cmp !== 0)
×
409
      return cmp;
×
410

411
    cmp = this.chainCode.compare(key.chainCode);
×
412

413
    if (cmp !== 0)
×
414
      return cmp;
×
415

416
    cmp = this.privateKey.compare(key.privateKey);
×
417

418
    if (cmp !== 0)
×
419
      return cmp;
×
420

421
    return 0;
×
422
  }
423

424
  /**
425
   * Inject properties from seed.
426
   * @private
427
   * @param {Buffer} seed
428
   */
429

430
  fromSeed(seed) {
431
    assert(Buffer.isBuffer(seed));
599✔
432

433
    if (seed.length * 8 < common.MIN_ENTROPY
599!
434
        || seed.length * 8 > common.MAX_ENTROPY) {
435
      throw new Error('Entropy not in range.');
×
436
    }
437

438
    const hash = sha512.mac(seed, SEED_SALT);
599✔
439
    const left = hash.slice(0, 32);
599✔
440
    const right = hash.slice(32, 64);
599✔
441

442
    // Only a 1 in 2^127 chance of happening.
443
    if (!secp256k1.privateKeyVerify(left))
599!
444
      throw new Error('Master private key is invalid.');
×
445

446
    this.depth = 0;
599✔
447
    this.parentFingerPrint = 0;
599✔
448
    this.childIndex = 0;
599✔
449
    this.chainCode = right;
599✔
450
    this.privateKey = left;
599✔
451
    this.publicKey = secp256k1.publicKeyCreate(left, true);
599✔
452

453
    return this;
599✔
454
  }
455

456
  /**
457
   * Instantiate an hd private key from a 512 bit seed.
458
   * @param {Buffer} seed
459
   * @returns {HDPrivateKey}
460
   */
461

462
  static fromSeed(seed) {
463
    return new this().fromSeed(seed);
3✔
464
  }
465

466
  /**
467
   * Inject properties from a mnemonic.
468
   * @param {Mnemonic} mnemonic
469
   * @param {String?} [passphrase]
470
   */
471

472
  fromMnemonic(mnemonic, passphrase) {
473
    assert(mnemonic instanceof Mnemonic);
596✔
474
    return this.fromSeed(mnemonic.toSeed(passphrase));
596✔
475
  }
476

477
  /**
478
   * Instantiate an hd private key from a mnemonic.
479
   * @param {Mnemonic} mnemonic
480
   * @param {String?} [passphrase]
481
   * @returns {HDPrivateKey}
482
   */
483

484
  static fromMnemonic(mnemonic, passphrase) {
485
    return new this().fromMnemonic(mnemonic, passphrase);
595✔
486
  }
487

488
  /**
489
   * Inject properties from a mnemonic.
490
   * @param {String} phrase
491
   */
492

493
  fromPhrase(phrase) {
494
    const mnemonic = Mnemonic.fromPhrase(phrase);
1✔
495
    this.fromMnemonic(mnemonic);
1✔
496
    return this;
1✔
497
  }
498

499
  /**
500
   * Instantiate an hd private key from a phrase.
501
   * @param {String} phrase
502
   * @returns {HDPrivateKey}
503
   */
504

505
  static fromPhrase(phrase) {
506
    return new this().fromPhrase(phrase);
1✔
507
  }
508

509
  /**
510
   * Inject properties from privateKey and entropy.
511
   * @private
512
   * @param {Buffer} key
513
   * @param {Buffer} entropy
514
   */
515

516
  fromKey(key, entropy) {
517
    assert(Buffer.isBuffer(key) && key.length === 32);
64✔
518
    assert(Buffer.isBuffer(entropy) && entropy.length === 32);
64✔
519
    this.depth = 0;
64✔
520
    this.parentFingerPrint = 0;
64✔
521
    this.childIndex = 0;
64✔
522
    this.chainCode = entropy;
64✔
523
    this.privateKey = key;
64✔
524
    this.publicKey = secp256k1.publicKeyCreate(key, true);
64✔
525
    return this;
64✔
526
  }
527

528
  /**
529
   * Create an hd private key from a key and entropy bytes.
530
   * @param {Buffer} key
531
   * @param {Buffer} entropy
532
   * @returns {HDPrivateKey}
533
   */
534

535
  static fromKey(key, entropy) {
536
    return new this().fromKey(key, entropy);
64✔
537
  }
538

539
  /**
540
   * Generate an hd private key.
541
   * @returns {HDPrivateKey}
542
   */
543

544
  static generate() {
545
    const key = secp256k1.privateKeyGenerate();
64✔
546
    const entropy = random.randomBytes(32);
64✔
547
    return HDPrivateKey.fromKey(key, entropy);
64✔
548
  }
549

550
  /**
551
   * Inject properties from base58 key.
552
   * @private
553
   * @param {Base58String} xkey
554
   * @param {(Network|NetworkType)?} [network]
555
   */
556

557
  fromBase58(xkey, network) {
558
    assert(typeof xkey === 'string');
13✔
559
    return this.decode(base58.decode(xkey), network);
13✔
560
  }
561

562
  /**
563
   * Inject properties from serialized data.
564
   * @param {bio.BufferReader} br
565
   * @param {(Network|NetworkType)?} [network]
566
   */
567

568
  read(br, network) {
569
    const version = br.readU32BE();
13✔
570

571
    Network.fromPrivate(version, network);
13✔
572

573
    this.depth = br.readU8();
13✔
574
    this.parentFingerPrint = br.readU32BE();
13✔
575
    this.childIndex = br.readU32BE();
13✔
576
    this.chainCode = br.readBytes(32);
13✔
577
    assert(br.readU8() === 0);
13✔
578
    this.privateKey = br.readBytes(32);
13✔
579
    this.publicKey = secp256k1.publicKeyCreate(this.privateKey, true);
13✔
580

581
    br.verifyChecksum(hash256.digest);
13✔
582

583
    return this;
13✔
584
  }
585

586
  /**
587
   * Serialize key to a base58 string.
588
   * @param {(Network|NetworkType)?} network
589
   * @returns {Base58String}
590
   */
591

592
  toBase58(network) {
593
    return base58.encode(this.encode(network));
136✔
594
  }
595

596
  /**
597
   * Calculate serialization size.
598
   * @returns {Number}
599
   */
600

601
  getSize() {
602
    return 82;
136✔
603
  }
604

605
  /**
606
   * Write the key to a buffer writer.
607
   * @param {BufioWriter} bw
608
   * @param {(Network|NetworkType)?} [network]
609
   * @returns {BufioWriter}
610
   */
611

612
  write(bw, network) {
613
    network = Network.get(network);
136✔
614

615
    bw.writeU32BE(network.keyPrefix.xprivkey);
136✔
616
    bw.writeU8(this.depth);
136✔
617
    bw.writeU32BE(this.parentFingerPrint);
136✔
618
    bw.writeU32BE(this.childIndex);
136✔
619
    bw.writeBytes(this.chainCode);
136✔
620
    bw.writeU8(0);
136✔
621
    bw.writeBytes(this.privateKey);
136✔
622
    bw.writeChecksum(hash256.digest);
136✔
623

624
    return bw;
136✔
625
  }
626

627
  /**
628
   * Instantiate an HD private key from a base58 string.
629
   * @param {Base58String} xkey
630
   * @param {(Network|NetworkType)?} [network]
631
   * @returns {HDPrivateKey}
632
   */
633

634
  static fromBase58(xkey, network) {
635
    return new this().fromBase58(xkey, network);
12✔
636
  }
637

638
  /**
639
   * Convert key to a more json-friendly object.
640
   * @param {(Network|NetworkType)?} [network]
641
   * @returns {Object}
642
   */
643

644
  getJSON(network) {
645
    return {
4✔
646
      xprivkey: this.xprivkey(network)
647
    };
648
  }
649

650
  /**
651
   * Inject properties from json object.
652
   * @param {Object} json
653
   * @param {(Network|NetworkType)?} [network]
654
   */
655

656
  fromJSON(json, network) {
657
    assert(json.xprivkey, 'Could not handle key JSON.');
1✔
658

659
    this.fromBase58(json.xprivkey, network);
1✔
660

661
    return this;
1✔
662
  }
663

664
  /**
665
   * Test whether an object is an HDPrivateKey.
666
   * @param {Object} obj
667
   * @returns {Boolean}
668
   */
669

670
  static isHDPrivateKey(obj) {
671
    return obj instanceof HDPrivateKey;
12✔
672
  }
673
}
674

675
/*
676
 * Expose
677
 */
678

679
module.exports = HDPrivateKey;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc