• 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

72.76
/lib/primitives/keyring.js
1
/*!
2
 * keyring.js - keyring object 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');
70✔
10
const base58 = require('bcrypto/lib/encoding/base58');
70✔
11
const bio = require('bufio');
70✔
12
const blake2b = require('bcrypto/lib/blake2b');
70✔
13
const hash256 = require('bcrypto/lib/hash256');
70✔
14
const Network = require('../protocol/network');
70✔
15
const Script = require('../script/script');
70✔
16
const Address = require('./address');
70✔
17
const Output = require('./output');
70✔
18
const secp256k1 = require('bcrypto/lib/secp256k1');
70✔
19

20
/** @typedef {import('../types').NetworkType} NetworkType */
21
/** @typedef {import('../types').Base58String} Base58String */
22
/** @typedef {import('../types').Hash} Hash */
23
/** @typedef {import('../types').BufioWriter} BufioWriter */
24
/** @typedef {import('./tx')} TX */
25

26
/*
27
 * Constants
28
 */
29

30
const ZERO_KEY = Buffer.alloc(33, 0x00);
70✔
31

32
/**
33
 * Key Ring
34
 * Represents a key ring which amounts to an address.
35
 * @alias module:primitives.KeyRing
36
 */
37

38
class KeyRing extends bio.Struct {
39
  /**
40
   * Create a key ring.
41
   * @constructor
42
   * @param {Object?} [options]
43
   */
44

45
  constructor(options) {
46
    super();
308,446✔
47

48
    this.publicKey = ZERO_KEY;
308,446✔
49
    /** @type {Buffer?} */
50
    this.privateKey = null;
308,446✔
51
    /** @type {Script?} */
52
    this.script = null;
308,446✔
53

54
    /** @type {Hash?} */
55
    this._keyHash = null;
308,446✔
56
    /** @type {Address?} */
57
    this._keyAddress = null;
308,446✔
58
    /** @type {Hash?} */
59
    this._scriptHash = null;
308,446✔
60
    /** @type {Address?} */
61
    this._scriptAddress = null;
308,446✔
62

63
    if (options)
308,446✔
64
      this.fromOptions(options);
30,290✔
65
  }
66

67
  /**
68
   * Inject properties from options object.
69
   * @param {Object} options
70
   */
71

72
  fromOptions(options) {
73
    let key = toKey(options);
30,294✔
74

75
    if (Buffer.isBuffer(key))
30,294!
76
      return this.fromKey(key);
30,294✔
77

78
    key = toKey(options.key);
×
79

80
    if (options.publicKey)
×
81
      key = toKey(options.publicKey);
×
82

83
    if (options.privateKey)
×
84
      key = toKey(options.privateKey);
×
85

86
    const script = options.script;
×
87

88
    if (script)
×
89
      return this.fromScript(key, script);
×
90

91
    return this.fromKey(key);
×
92
  }
93

94
  /**
95
   * Clear cached key/script hashes.
96
   */
97

98
  refresh() {
99
    this._keyHash = null;
×
100
    this._keyAddress = null;
×
101
    this._scriptHash = null;
×
102
    this._scriptAddress = null;
×
103
    return this;
×
104
  }
105

106
  /**
107
   * Inject data from private key.
108
   * @param {Buffer} key
109
   */
110

111
  fromPrivate(key) {
112
    assert(Buffer.isBuffer(key), 'Private key must be a buffer.');
52,990✔
113
    assert(secp256k1.privateKeyVerify(key), 'Not a valid private key.');
52,990✔
114

115
    this.privateKey = key;
52,990✔
116
    this.publicKey = secp256k1.publicKeyCreate(key, true);
52,990✔
117

118
    return this;
52,990✔
119
  }
120

121
  /**
122
   * Instantiate keyring from a private key.
123
   * @param {Buffer} key
124
   * @returns {KeyRing}
125
   */
126

127
  static fromPrivate(key) {
128
    return new this().fromPrivate(key);
6✔
129
  }
130

131
  /**
132
   * Inject data from public key.
133
   * @param {Buffer} key
134
   */
135

136
  fromPublic(key) {
137
    assert(Buffer.isBuffer(key), 'Public key must be a buffer.');
255,274✔
138
    assert(secp256k1.publicKeyVerify(key) && key.length === 33,
255,274✔
139
      'Not a valid public key.');
140
    this.publicKey = key;
255,274✔
141
    return this;
255,274✔
142
  }
143

144
  /**
145
   * Generate a keyring.
146
   * @returns {KeyRing}
147
   */
148

149
  generate() {
150
    const key = secp256k1.privateKeyGenerate();
244✔
151
    return this.fromKey(key);
244✔
152
  }
153

154
  /**
155
   * Generate a keyring.
156
   * @returns {KeyRing}
157
   */
158

159
  static generate() {
160
    return new this().generate();
244✔
161
  }
162

163
  /**
164
   * Instantiate keyring from a public key.
165
   * @param {Buffer} publicKey
166
   * @returns {KeyRing}
167
   */
168

169
  static fromPublic(publicKey) {
NEW
170
    return new this().fromPublic(publicKey);
×
171
  }
172

173
  /**
174
   * Inject data from public key.
175
   * @param {Buffer} key
176
   */
177

178
  fromKey(key) {
179
    assert(Buffer.isBuffer(key), 'Key must be a buffer.');
30,538✔
180

181
    if (key.length === 32)
30,538✔
182
      return this.fromPrivate(key);
30,536✔
183

184
    return this.fromPublic(key);
2✔
185
  }
186

187
  /**
188
   * Instantiate keyring from a public key.
189
   * @param {Buffer} key
190
   * @returns {KeyRing}
191
   */
192

193
  static fromKey(key) {
194
    return new this().fromKey(key);
×
195
  }
196

197
  /**
198
   * Inject data from script.
199
   * @private
200
   * @param {Buffer} key
201
   * @param {Script} script
202
   */
203

204
  fromScript(key, script) {
205
    assert(script instanceof Script, 'Non-script passed into KeyRing.');
×
206

207
    this.fromKey(key);
×
208
    this.script = script;
×
209

210
    return this;
×
211
  }
212

213
  /**
214
   * Instantiate keyring from script.
215
   * @param {Buffer} key
216
   * @param {Script} script
217
   * @returns {KeyRing}
218
   */
219

220
  static fromScript(key, script) {
221
    return new this().fromScript(key, script);
×
222
  }
223

224
  /**
225
   * Calculate WIF serialization size.
226
   * @returns {Number}
227
   */
228

229
  getSecretSize() {
230
    let size = 0;
16✔
231

232
    size += 1;
16✔
233
    size += this.privateKey.length;
16✔
234
    size += 1;
16✔
235
    size += 4;
16✔
236

237
    return size;
16✔
238
  }
239

240
  /**
241
   * Convert key to a secret.
242
   * @param {(Network|NetworkType)?} network
243
   * @returns {Base58String}
244
   */
245

246
  toSecret(network) {
247
    const size = this.getSecretSize();
16✔
248
    const bw = bio.write(size);
16✔
249

250
    assert(this.privateKey, 'Cannot serialize without private key.');
16✔
251

252
    network = Network.get(network);
16✔
253

254
    bw.writeU8(network.keyPrefix.privkey);
16✔
255
    bw.writeBytes(this.privateKey);
16✔
256
    bw.writeU8(1);
16✔
257

258
    bw.writeChecksum(hash256.digest);
16✔
259

260
    return base58.encode(bw.render());
16✔
261
  }
262

263
  /**
264
   * Inject properties from serialized secret.
265
   * @param {Base58String} data
266
   * @param {(Network|NetworkType)?} [network]
267
   */
268

269
  fromSecret(data, network) {
270
    const br = bio.read(base58.decode(data), true);
16✔
271

272
    const version = br.readU8();
16✔
273

274
    Network.fromWIF(version, network);
16✔
275

276
    const key = br.readBytes(32);
16✔
277

278
    assert(br.readU8() === 1, 'Bad compression flag.');
16✔
279
    br.verifyChecksum(hash256.digest);
16✔
280

281
    return this.fromPrivate(key);
16✔
282
  }
283

284
  /**
285
   * Instantiate a keyring from a serialized secret.
286
   * @param {Base58String} data
287
   * @param {(Network|NetworkType)?} network
288
   * @returns {KeyRing}
289
   */
290

291
  static fromSecret(data, network) {
292
    return new this().fromSecret(data, network);
16✔
293
  }
294

295
  /**
296
   * Get private key.
297
   * @returns {Buffer} Private key.
298
   */
299

300
  getPrivateKey() {
301
    if (!this.privateKey)
×
302
      return null;
×
303

304
    return this.privateKey;
×
305
  }
306

307
  /**
308
   * Get public key.
309
   * @returns {Buffer}
310
   */
311

312
  getPublicKey() {
313
    return this.publicKey;
×
314
  }
315

316
  /**
317
   * Get redeem script.
318
   * @returns {Script}
319
   */
320

321
  getScript() {
322
    return this.script;
×
323
  }
324

325
  /**
326
   * Get scripthash.
327
   * @returns {Buffer}
328
   */
329

330
  getScriptHash() {
331
    if (!this.script)
3,324!
332
      return null;
×
333

334
    if (!this._scriptHash)
3,324✔
335
      this._scriptHash = this.script.sha3();
3,319✔
336

337
    return this._scriptHash;
3,324✔
338
  }
339

340
  /**
341
   * Get scripthash address.
342
   * @returns {Address}
343
   */
344

345
  getScriptAddress() {
346
    if (!this.script)
33!
347
      return null;
×
348

349
    if (!this._scriptAddress) {
33!
350
      const hash = this.getScriptHash();
33✔
351
      const addr = Address.fromScripthash(hash);
33✔
352
      this._scriptAddress = addr;
33✔
353
    }
354

355
    return this._scriptAddress;
33✔
356
  }
357

358
  /**
359
   * Get public key hash.
360
   * @returns {Buffer}
361
   */
362

363
  getKeyHash() {
364
    if (!this._keyHash)
9,065,559✔
365
      this._keyHash = blake2b.digest(this.publicKey, 20);
303,132✔
366

367
    return this._keyHash;
9,065,559✔
368
  }
369

370
  /**
371
   * Get pubkeyhash address.
372
   * @returns {Address}
373
   */
374

375
  getKeyAddress() {
376
    if (!this._keyAddress) {
108,171✔
377
      const hash = this.getKeyHash();
19,647✔
378
      const addr = Address.fromPubkeyhash(hash);
19,647✔
379
      this._keyAddress = addr;
19,647✔
380
    }
381

382
    return this._keyAddress;
108,171✔
383
  }
384

385
  /**
386
   * Get hash.
387
   * @returns {Buffer}
388
   */
389

390
  getHash() {
391
    if (this.script)
257,212✔
392
      return this.getScriptHash();
3,266✔
393

394
    return this.getKeyHash();
253,946✔
395
  }
396

397
  /**
398
   * Get base58 address.
399
   * @returns {Address}
400
   */
401

402
  getAddress() {
403
    if (this.script)
108,204✔
404
      return this.getScriptAddress();
33✔
405

406
    return this.getKeyAddress();
108,171✔
407
  }
408

409
  /**
410
   * Test an address hash against hash and program hash.
411
   * @param {Buffer} hash
412
   * @returns {Boolean}
413
   */
414

415
  ownHash(hash) {
416
    if (!hash)
8,734,676!
417
      return false;
×
418

419
    if (hash.equals(this.getKeyHash()))
8,734,676✔
420
      return true;
57,133✔
421

422
    if (this.script) {
8,677,543✔
423
      if (hash.equals(this.getScriptHash()))
12!
424
        return true;
12✔
425
    }
426

427
    return false;
8,677,531✔
428
  }
429

430
  /**
431
   * Check whether transaction output belongs to this address.
432
   * @param {TX|Output} tx - Transaction or Output.
433
   * @param {Number?} [index] - Output index.
434
   * @returns {Boolean}
435
   */
436

437
  ownOutput(tx, index) {
438
    let output;
439

440
    if (tx instanceof Output) {
8,734,676!
441
      output = tx;
8,734,676✔
442
    } else {
443
      output = tx.outputs[index];
×
444
      assert(output, 'Output does not exist.');
×
445
    }
446

447
    return this.ownHash(output.getHash());
8,734,676✔
448
  }
449

450
  /**
451
   * Test a hash against script hashes to
452
   * find the correct redeem script, if any.
453
   * @param {Buffer} hash
454
   * @returns {Script|null}
455
   */
456

457
  getRedeem(hash) {
458
    if (this.script) {
5!
459
      if (hash.equals(this.getScriptHash()))
5!
460
        return this.script;
5✔
461
    }
462

463
    return null;
×
464
  }
465

466
  /**
467
   * Sign a message.
468
   * @param {Buffer} msg
469
   * @returns {Buffer} Signature in DER format.
470
   */
471

472
  sign(msg) {
473
    assert(this.privateKey, 'Cannot sign without private key.');
3✔
474
    return secp256k1.sign(msg, this.privateKey);
3✔
475
  }
476

477
  /**
478
   * Verify a message.
479
   * @param {Buffer} msg
480
   * @param {Buffer} sig - Signature in DER format.
481
   * @returns {Boolean}
482
   */
483

484
  verify(msg, sig) {
485
    return secp256k1.verify(msg, sig, this.publicKey);
×
486
  }
487

488
  /**
489
   * Get witness program version.
490
   * @returns {Number}
491
   */
492

493
  getVersion() {
494
    return 0;
244,109✔
495
  }
496

497
  /**
498
   * Inspect keyring.
499
   * @returns {Object}
500
   */
501

502
  format() {
503
    return this.toJSON();
×
504
  }
505

506
  /**
507
   * Convert an KeyRing to a more json-friendly object.
508
   * @param {(NetworkType|Network)?} [network]
509
   * @returns {Object}
510
   */
511

512
  getJSON(network) {
513
    return {
×
514
      publicKey: this.publicKey.toString('hex'),
515
      script: this.script ? this.script.toHex() : null,
×
516
      address: this.getAddress().toString(network)
517
    };
518
  }
519

520
  /**
521
   * Inject properties from json object.
522
   * @param {Object} json
523
   */
524

525
  fromJSON(json) {
526
    assert(json);
×
527
    assert(typeof json.publicKey === 'string');
×
528
    assert(!json.script || typeof json.script === 'string');
×
529

530
    this.publicKey = Buffer.from(json.publicKey, 'hex');
×
531

532
    if (json.script)
×
NEW
533
      this.script = Script.fromHex(json.script);
×
534

535
    return this;
×
536
  }
537

538
  /**
539
   * Calculate serialization size.
540
   * @returns {Number}
541
   */
542

543
  getSize() {
544
    let size = 0;
106✔
545
    size += 1;
106✔
546

547
    if (this.privateKey)
106✔
548
      size += 32;
105✔
549
    else
550
      size += 33;
1✔
551

552
    size += this.script
106✔
553
      ? this.script.getVarSize()
554
      : 1;
555

556
    return size;
106✔
557
  }
558

559
  /**
560
   * Write the keyring to a buffer writer.
561
   * @param {BufioWriter} bw
562
   * @returns {BufioWriter}
563
   */
564

565
  write(bw) {
566
    if (this.privateKey) {
106✔
567
      bw.writeU8(0);
105✔
568
      bw.writeBytes(this.privateKey);
105✔
569
    } else {
570
      bw.writeU8(1);
1✔
571
      bw.writeBytes(this.publicKey);
1✔
572
    }
573

574
    if (this.script)
106✔
575
      bw.writeVarBytes(this.script.encode());
2✔
576
    else
577
      bw.writeVarint(0);
104✔
578

579
    return bw;
106✔
580
  }
581

582
  /**
583
   * Inject properties from buffer reader.
584
   * @param {bio.BufferReader} br
585
   */
586

587
  read(br) {
588
    const type = br.readU8();
108✔
589

590
    switch (type) {
108!
591
      case 0: {
592
        const key = br.readBytes(32);
107✔
593
        this.privateKey = key;
107✔
594
        this.publicKey = secp256k1.publicKeyCreate(key, true);
107✔
595
        break;
107✔
596
      }
597
      case 1: {
598
        const key = br.readBytes(33);
1✔
599
        assert(secp256k1.publicKeyVerify(key), 'Invalid public key.');
1✔
600
        this.publicKey = key;
1✔
601
        break;
1✔
602
      }
603
      default: {
604
        throw new Error('Invalid key.');
×
605
      }
606
    }
607

608
    const script = br.readVarBytes();
108✔
609

610
    if (script.length > 0)
108✔
611
      this.script = Script.decode(script);
2✔
612

613
    return this;
108✔
614
  }
615

616
  /**
617
   * Test whether an object is a KeyRing.
618
   * @param {Object} obj
619
   * @returns {Boolean}
620
   */
621

622
  static isKeyRing(obj) {
623
    return obj instanceof KeyRing;
×
624
  }
625
}
626

627
/*
628
 * Helpers
629
 */
630

631
function toKey(opt) {
632
  if (!opt)
30,294!
633
    return opt;
×
634

635
  if (opt.privateKey)
30,294✔
636
    return opt.privateKey;
30,292✔
637

638
  if (opt.publicKey)
2✔
639
    return opt.publicKey;
1✔
640

641
  return opt;
1✔
642
}
643

644
/*
645
 * Expose
646
 */
647

648
module.exports = KeyRing;
70✔
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