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

handshake-org / hsd / 12791301410

15 Jan 2025 03:12PM UTC coverage: 71.261% (+0.02%) from 71.24%
12791301410

push

github

nodech
Merge PR #918 from 'nodech/wallet-doc-updates'

8059 of 13158 branches covered (61.25%)

Branch coverage included in aggregate %.

91 of 94 new or added lines in 6 files covered. (96.81%)

8 existing lines in 4 files now uncovered.

25735 of 34265 relevant lines covered (75.11%)

34481.61 hits per line

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

79.86
/lib/wallet/account.js
1
/*!
2
 * account.js - account 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');
1✔
10
const bio = require('bufio');
1✔
11
const binary = require('../utils/binary');
1✔
12
const Path = require('./path');
1✔
13
const common = require('./common');
1✔
14
const Script = require('../script/script');
1✔
15
const WalletKey = require('./walletkey');
1✔
16
const HDPublicKey = require('../hd/public');
1✔
17

18
/** @typedef {import('bdb').DB} DB */
19
/** @typedef {ReturnType<DB['batch']>} Batch */
20
/** @typedef {import('../types').BufioWriter} BufioWriter */
21
/** @typedef {import('./walletdb')} WalletDB */
22
/** @typedef {import('./masterkey')} MasterKey */
23
/** @typedef {import('../primitives/address')} Address */
24

25
/**
26
 * Account
27
 * Represents a BIP44 Account belonging to a {@link Wallet}.
28
 * Note that this object does not enforce locks. Any method
29
 * that does a write is internal API only and will lead
30
 * to race conditions if used elsewhere.
31
 * @alias module:wallet.Account
32
 */
33

34
class Account extends bio.Struct {
35
  /**
36
   * Create an account.
37
   * @constructor
38
   * @param {WalletDB} wdb
39
   * @param {Object} options
40
   */
41

42
  constructor(wdb, options) {
43
    super();
53,942✔
44

45
    assert(wdb, 'Database is required.');
53,942✔
46

47
    /** @type {WalletDB} */
48
    this.wdb = wdb;
53,942✔
49
    this.network = wdb.network;
53,942✔
50

51
    this.wid = 0;
53,942✔
52
    /** @type {String|null} */
53
    this.id = null;
53,942✔
54
    this.accountIndex = 0;
53,942✔
55
    /** @type {String|null} */
56
    this.name = null;
53,942✔
57
    this.initialized = false;
53,942✔
58
    this.watchOnly = false;
53,942✔
59
    /** @type {Account.types} */
60
    this.type = Account.types.PUBKEYHASH;
53,942✔
61
    this.m = 1;
53,942✔
62
    this.n = 1;
53,942✔
63
    this.receiveDepth = 0;
53,942✔
64
    this.changeDepth = 0;
53,942✔
65
    this.lookahead = 200;
53,942✔
66
    /** @type {HDPublicKey|null} */
67
    this.accountKey = null;
53,942✔
68
    /** @type {HDPublicKey[]} */
69
    this.keys = [];
53,942✔
70

71
    if (options)
53,942!
72
      this.fromOptions(options);
×
73
  }
74

75
  /**
76
   * Inject properties from options object.
77
   * @param {Object} options
78
   * @returns {this}
79
   */
80

81
  fromOptions(options) {
82
    assert(options, 'Options are required.');
575✔
83
    assert((options.wid >>> 0) === options.wid);
575✔
84
    assert(common.isName(options.id), 'Bad Wallet ID.');
575✔
85
    assert(HDPublicKey.isHDPublicKey(options.accountKey),
575✔
86
      'Account key is required.');
87
    assert((options.accountIndex >>> 0) === options.accountIndex,
575✔
88
      'Account index is required.');
89

90
    this.wid = options.wid;
575✔
91
    this.id = options.id;
575✔
92

93
    if (options.accountIndex != null) {
575!
94
      assert((options.accountIndex >>> 0) === options.accountIndex);
575✔
95
      this.accountIndex = options.accountIndex;
575✔
96
    }
97

98
    if (options.name != null) {
575✔
99
      assert(common.isName(options.name), 'Bad account name.');
567✔
100
      this.name = options.name;
567✔
101
    }
102

103
    if (options.initialized != null) {
575!
104
      assert(typeof options.initialized === 'boolean');
×
105
      this.initialized = options.initialized;
×
106
    }
107

108
    if (options.watchOnly != null) {
575✔
109
      assert(typeof options.watchOnly === 'boolean');
567✔
110
      this.watchOnly = options.watchOnly;
567✔
111
    }
112

113
    if (options.type != null) {
575✔
114
      if (typeof options.type === 'string') {
9!
115
        this.type = Account.types[options.type.toUpperCase()];
9✔
116
        assert(this.type != null);
9✔
117
      } else {
118
        assert(typeof options.type === 'number');
×
119
        this.type = options.type;
×
120
        assert(Account.typesByVal[this.type]);
×
121
      }
122
    }
123

124
    if (options.m != null) {
575✔
125
      assert((options.m & 0xff) === options.m);
9✔
126
      this.m = options.m;
9✔
127
    }
128

129
    if (options.n != null) {
575✔
130
      assert((options.n & 0xff) === options.n);
9✔
131
      this.n = options.n;
9✔
132
    }
133

134
    if (options.receiveDepth != null) {
575!
135
      assert((options.receiveDepth >>> 0) === options.receiveDepth);
×
136
      this.receiveDepth = options.receiveDepth;
×
137
    }
138

139
    if (options.changeDepth != null) {
575!
140
      assert((options.changeDepth >>> 0) === options.changeDepth);
×
141
      this.changeDepth = options.changeDepth;
×
142
    }
143

144
    if (options.lookahead != null) {
575✔
145
      assert((options.lookahead >>> 0) === options.lookahead);
23✔
146
      assert(options.lookahead >= 0);
15✔
147
      this.lookahead = options.lookahead;
15✔
148
    }
149

150
    this.accountKey = options.accountKey;
567✔
151

152
    if (this.n > 1)
567✔
153
      this.type = Account.types.MULTISIG;
9✔
154

155
    if (!this.name)
567✔
156
      this.name = this.accountIndex.toString(10);
4✔
157

158
    if (this.m < 1 || this.m > this.n)
567!
159
      throw new Error('m ranges between 1 and n');
×
160

161
    if (options.keys) {
567!
162
      assert(Array.isArray(options.keys));
×
163
      for (const key of options.keys)
×
164
        this.pushKey(key);
×
165
    }
166

167
    return this;
567✔
168
  }
169

170
  /**
171
   * Inject properties from options object.
172
   * @param {WalletDB} wdb
173
   * @param {Object} options
174
   */
175

176
  static fromOptions(wdb, options) {
177
    return new this(wdb).fromOptions(options);
575✔
178
  }
179

180
  /**
181
   * Attempt to intialize the account (generating
182
   * the first addresses along with the lookahead
183
   * addresses). Called automatically from the
184
   * walletdb.
185
   * @param {Batch} b
186
   * @returns {Promise}
187
   */
188

189
  async init(b) {
190
    // Waiting for more keys.
191
    if (this.keys.length !== this.n - 1) {
574✔
192
      assert(!this.initialized);
12✔
193
      this.save(b);
12✔
194
      return;
12✔
195
    }
196

197
    assert(this.receiveDepth === 0);
562✔
198
    assert(this.changeDepth === 0);
562✔
199

200
    this.initialized = true;
562✔
201

202
    await this.initDepth(b);
562✔
203
  }
204

205
  /**
206
   * Add a public account key to the account (multisig).
207
   * Does not update the database.
208
   * @param {HDPublicKey} key - Account (bip44)
209
   * key (can be in base58 form).
210
   * @throws Error on non-hdkey/non-accountkey.
211
   * @returns {Boolean} - Whether the key was added.
212
   */
213

214
  pushKey(key) {
215
    if (typeof key === 'string')
11!
216
      key = HDPublicKey.fromBase58(key, this.network);
×
217

218
    if (!HDPublicKey.isHDPublicKey(key))
11!
219
      throw new Error('Must add HD keys to wallet.');
×
220

221
    if (!key.isAccount())
11!
222
      throw new Error('Must add HD account keys to BIP44 wallet.');
×
223

224
    if (this.type !== Account.types.MULTISIG)
11!
225
      throw new Error('Cannot add keys to non-multisig wallet.');
×
226

227
    if (key.equals(this.accountKey))
11!
228
      throw new Error('Cannot add own key.');
×
229

230
    const index = binary.insert(this.keys, key, cmp, true);
11✔
231

232
    if (index === -1)
11!
233
      return false;
×
234

235
    if (this.keys.length > this.n - 1) {
11!
236
      binary.remove(this.keys, key, cmp);
×
237
      throw new Error('Cannot add more keys.');
×
238
    }
239

240
    return true;
11✔
241
  }
242

243
  /**
244
   * Remove a public account key to the account (multisig).
245
   * Does not update the database.
246
   * @param {HDPublicKey} key - Account (bip44)
247
   * key (can be in base58 form).
248
   * @throws Error on non-hdkey/non-accountkey.
249
   * @returns {Boolean} - Whether the key was removed.
250
   */
251

252
  spliceKey(key) {
253
    if (typeof key === 'string')
×
254
      key = HDPublicKey.fromBase58(key, this.network);
×
255

256
    if (!HDPublicKey.isHDPublicKey(key))
×
257
      throw new Error('Must add HD keys to wallet.');
×
258

259
    if (!key.isAccount())
×
260
      throw new Error('Must add HD account keys to BIP44 wallet.');
×
261

262
    if (this.type !== Account.types.MULTISIG)
×
263
      throw new Error('Cannot remove keys from non-multisig wallet.');
×
264

265
    if (this.keys.length === this.n - 1)
×
266
      throw new Error('Cannot remove key.');
×
267

268
    return binary.remove(this.keys, key, cmp);
×
269
  }
270

271
  /**
272
   * Add a public account key to the account (multisig).
273
   * Saves the key in the wallet database.
274
   * @param {Batch} b
275
   * @param {HDPublicKey} key
276
   * @returns {Promise<Boolean>}
277
   */
278

279
  async addSharedKey(b, key) {
280
    const result = this.pushKey(key);
11✔
281

282
    if (await this.hasDuplicate()) {
11!
283
      this.spliceKey(key);
×
284
      throw new Error('Cannot add a key from another account.');
×
285
    }
286

287
    // Try to initialize again.
288
    await this.init(b);
11✔
289

290
    return result;
11✔
291
  }
292

293
  /**
294
   * Ensure accounts are not sharing keys.
295
   * @private
296
   * @returns {Promise<Boolean>}
297
   */
298

299
  async hasDuplicate() {
300
    if (this.keys.length !== this.n - 1)
11✔
301
      return false;
3✔
302

303
    const ring = this.deriveReceive(0);
8✔
304
    const hash = ring.getScriptHash();
8✔
305

306
    return this.wdb.hasPath(this.wid, hash);
8✔
307
  }
308

309
  /**
310
   * Remove a public account key from the account (multisig).
311
   * Remove the key from the wallet database.
312
   * @param {Batch} b
313
   * @param {HDPublicKey} key
314
   * @returns {Boolean}
315
   */
316

317
  removeSharedKey(b, key) {
318
    const result = this.spliceKey(key);
×
319

320
    if (!result)
×
321
      return false;
×
322

323
    this.save(b);
×
324

325
    return true;
×
326
  }
327

328
  /**
329
   * Create a new receiving address (increments receiveDepth).
330
   * @param {Batch} b
331
   * @returns {Promise<WalletKey>}
332
   */
333

334
  createReceive(b) {
NEW
335
    return this.createKey(b, 0);
×
336
  }
337

338
  /**
339
   * Create a new change address (increments changeDepth).
340
   * @param {Batch} b
341
   * @returns {Promise<WalletKey>}
342
   */
343

344
  createChange(b) {
NEW
345
    return this.createKey(b, 1);
×
346
  }
347

348
  /**
349
   * Create a new address (increments depth).
350
   * @param {Batch} b
351
   * @param {Number} branch
352
   * @returns {Promise<WalletKey>} - Returns {@link WalletKey}.
353
   */
354

355
  async createKey(b, branch) {
356
    let key, lookahead;
357

358
    switch (branch) {
3,690!
359
      case 0:
360
        key = this.deriveReceive(this.receiveDepth);
3,668✔
361
        lookahead = this.deriveReceive(this.receiveDepth + this.lookahead);
3,668✔
362
        await this.saveKey(b, lookahead);
3,668✔
363
        this.receiveDepth += 1;
3,668✔
364
        this.receive = key;
3,668✔
365
        break;
3,668✔
366
      case 1:
367
        key = this.deriveChange(this.changeDepth);
22✔
368
        lookahead = this.deriveChange(this.changeDepth + this.lookahead);
22✔
369
        await this.saveKey(b, lookahead);
22✔
370
        this.changeDepth += 1;
22✔
371
        this.change = key;
22✔
372
        break;
22✔
373
      default:
374
        throw new Error(`Bad branch: ${branch}.`);
×
375
    }
376

377
    this.save(b);
3,690✔
378

379
    return key;
3,690✔
380
  }
381

382
  /**
383
   * Derive a receiving address at `index`. Do not increment depth.
384
   * @param {Number} index
385
   * @param {MasterKey} [master]
386
   * @returns {WalletKey}
387
   */
388

389
  deriveReceive(index, master) {
390
    return this.deriveKey(0, index, master);
164,371✔
391
  }
392

393
  /**
394
   * Derive a change address at `index`. Do not increment depth.
395
   * @param {Number} index
396
   * @param {MasterKey} [master]
397
   * @returns {WalletKey}
398
   */
399

400
  deriveChange(index, master) {
401
    return this.deriveKey(1, index, master);
119,786✔
402
  }
403

404
  /**
405
   * Derive an address from `path` object.
406
   * @param {Path} path
407
   * @param {MasterKey} master
408
   * @returns {WalletKey?}
409
   */
410

411
  derivePath(path, master) {
412
    switch (path.keyType) {
24,224!
413
      case Path.types.HD: {
414
        return this.deriveKey(path.branch, path.index, master);
24,216✔
415
      }
416
      case Path.types.KEY: {
417
        assert(this.type === Account.types.PUBKEYHASH);
7✔
418

419
        let data = path.data;
7✔
420

421
        if (path.encrypted) {
7✔
422
          data = master.decipher(data, path.hash);
5✔
423
          if (!data)
5✔
424
            return null;
1✔
425
        }
426

427
        return WalletKey.fromImport(this, data);
6✔
428
      }
429
      case Path.types.ADDRESS: {
430
        return null;
1✔
431
      }
432
      default: {
433
        throw new Error('Bad key type.');
×
434
      }
435
    }
436
  }
437

438
  /**
439
   * Derive an address at `index`. Do not increment depth.
440
   * @param {Number} branch
441
   * @param {Number} index
442
   * @param {MasterKey} [master]
443
   * @returns {WalletKey}
444
   */
445

446
  deriveKey(branch, index, master) {
447
    assert(typeof branch === 'number');
308,373✔
448

449
    const keys = [];
308,373✔
450

451
    let key;
452
    if (master && master.key && !this.watchOnly) {
308,373✔
453
      const type = this.network.keyPrefix.coinType;
24,180✔
454
      key = master.key.deriveAccount(44, type, this.accountIndex);
24,180✔
455
      key = key.derive(branch).derive(index);
24,180✔
456
    } else {
457
      key = this.accountKey.derive(branch).derive(index);
284,193✔
458
    }
459

460
    const ring = WalletKey.fromHD(this, key, branch, index);
308,373✔
461

462
    switch (this.type) {
308,373✔
463
      case Account.types.PUBKEYHASH:
464
        break;
305,075✔
465
      case Account.types.MULTISIG:
466
        keys.push(key.publicKey);
3,298✔
467

468
        for (const shared of this.keys) {
3,298✔
469
          const key = shared.derive(branch).derive(index);
4,534✔
470
          keys.push(key.publicKey);
4,534✔
471
        }
472

473
        ring.script = Script.fromMultisig(this.m, this.n, keys);
3,298✔
474

475
        break;
3,298✔
476
    }
477

478
    return ring;
308,373✔
479
  }
480

481
  /**
482
   * Save the account to the database. Necessary
483
   * when address depth and keys change.
484
   * @param {Batch} b
485
   * @returns {void}
486
   */
487

488
  save(b) {
489
    return this.wdb.saveAccount(b, this);
8,762✔
490
  }
491

492
  /**
493
   * Save addresses to path map.
494
   * @param {Batch} b
495
   * @param {WalletKey} ring
496
   * @returns {Promise}
497
   */
498

499
  saveKey(b, ring) {
500
    return this.wdb.saveKey(b, this.wid, ring);
270,958✔
501
  }
502

503
  /**
504
   * Save paths to path map.
505
   * @param {Batch} b
506
   * @param {Path} path
507
   * @returns {Promise}
508
   */
509

510
  savePath(b, path) {
511
    return this.wdb.savePath(b, this.wid, path);
5✔
512
  }
513

514
  /**
515
   * Initialize address depths (including lookahead).
516
   * @param {Batch} b
517
   * @returns {Promise}
518
   */
519

520
  async initDepth(b) {
521
    // Receive Address
522
    this.receiveDepth = 1;
562✔
523

524
    for (let i = 0; i <= this.lookahead; i++) {
562✔
525
      const key = this.deriveReceive(i);
113,884✔
526
      await this.saveKey(b, key);
113,884✔
527
    }
528

529
    // Change Address
530
    this.changeDepth = 1;
562✔
531

532
    for (let i = 0; i <= this.lookahead; i++) {
562✔
533
      const key = this.deriveChange(i);
113,884✔
534
      await this.saveKey(b, key);
113,884✔
535
    }
536

537
    this.save(b);
562✔
538
  }
539

540
  /**
541
   * Allocate new lookahead addresses if necessary.
542
   * @param {Batch} b
543
   * @param {Number} receive
544
   * @param {Number} change
545
   * @returns {Promise<WalletKey?>}
546
   */
547

548
  async syncDepth(b, receive, change) {
549
    let derived = false;
15,903✔
550
    let result = null;
15,903✔
551

552
    if (receive > this.receiveDepth) {
15,903✔
553
      const depth = this.receiveDepth + this.lookahead;
2,751✔
554

555
      assert(receive <= depth + 1);
2,751✔
556

557
      for (let i = depth; i < receive + this.lookahead; i++) {
2,751✔
558
        const key = this.deriveReceive(i);
35,408✔
559
        await this.saveKey(b, key);
35,408✔
560
        result = key;
35,408✔
561
      }
562

563
      this.receiveDepth = receive;
2,751✔
564

565
      derived = true;
2,751✔
566
    }
567

568
    if (change > this.changeDepth) {
15,903✔
569
      const depth = this.changeDepth + this.lookahead;
2,287✔
570

571
      assert(change <= depth + 1);
2,287✔
572

573
      for (let i = depth; i < change + this.lookahead; i++) {
2,287✔
574
        const key = this.deriveChange(i);
2,290✔
575
        await this.saveKey(b, key);
2,290✔
576
      }
577

578
      this.changeDepth = change;
2,287✔
579

580
      derived = true;
2,287✔
581
    }
582

583
    if (derived)
15,903✔
584
      this.save(b);
4,489✔
585

586
    return result;
15,903✔
587
  }
588

589
  /**
590
   * Allocate new lookahead addresses.
591
   * @param {Batch} b
592
   * @param {Number} lookahead
593
   * @returns {Promise}
594
   */
595

596
  async setLookahead(b, lookahead) {
597
    assert((lookahead >>> 0) === lookahead, 'Lookahead must be a number.');
9✔
598

599
    if (lookahead === this.lookahead)
9!
600
      return;
×
601

602
    if (lookahead < this.lookahead) {
9✔
603
      const diff = this.lookahead - lookahead;
7✔
604

605
      this.receiveDepth += diff;
7✔
606
      this.changeDepth += diff;
7✔
607

608
      this.lookahead = lookahead;
7✔
609

610
      this.save(b);
7✔
611

612
      return;
7✔
613
    }
614

615
    {
616
      const depth = this.receiveDepth + this.lookahead;
2✔
617
      const target = this.receiveDepth + lookahead;
2✔
618

619
      for (let i = depth; i < target; i++) {
2✔
620
        const key = this.deriveReceive(i);
901✔
621
        await this.saveKey(b, key);
901✔
622
      }
623
    }
624

625
    {
626
      const depth = this.changeDepth + this.lookahead;
2✔
627
      const target = this.changeDepth + lookahead;
2✔
628

629
      for (let i = depth; i < target; i++) {
2✔
630
        const key = this.deriveChange(i);
901✔
631
        await this.saveKey(b, key);
901✔
632
      }
633
    }
634

635
    this.lookahead = lookahead;
2✔
636
    this.save(b);
2✔
637
  }
638

639
  /**
640
   * Get current receive key.
641
   * @returns {WalletKey?}
642
   */
643

644
  receiveKey() {
645
    if (!this.initialized)
3,077✔
646
      return null;
3✔
647

648
    return this.deriveReceive(this.receiveDepth - 1);
3,074✔
649
  }
650

651
  /**
652
   * Get current change key.
653
   * @returns {WalletKey?}
654
   */
655

656
  changeKey() {
657
    if (!this.initialized)
2,670✔
658
      return null;
3✔
659

660
    return this.deriveChange(this.changeDepth - 1);
2,667✔
661
  }
662

663
  /**
664
   * Get current receive address.
665
   * @returns {Address?}
666
   */
667

668
  receiveAddress() {
669
    const key = this.receiveKey();
3,075✔
670

671
    if (!key)
3,075✔
672
      return null;
3✔
673

674
    return key.getAddress();
3,072✔
675
  }
676

677
  /**
678
   * Get current change address.
679
   * @returns {Address?}
680
   */
681

682
  changeAddress() {
683
    const key = this.changeKey();
2,670✔
684

685
    if (!key)
2,670✔
686
      return null;
3✔
687

688
    return key.getAddress();
2,667✔
689
  }
690

691
  /**
692
   * Convert the account to a more inspection-friendly object.
693
   * @returns {Object}
694
   */
695

696
  format() {
697
    const receive = this.receiveAddress();
×
698
    const change = this.changeAddress();
×
699

700
    return {
×
701
      id: this.id,
702
      wid: this.wid,
703
      name: this.name,
704
      network: this.network.type,
705
      initialized: this.initialized,
706
      watchOnly: this.watchOnly,
707
      type: Account.typesByVal[this.type].toLowerCase(),
708
      m: this.m,
709
      n: this.n,
710
      accountIndex: this.accountIndex,
711
      receiveDepth: this.receiveDepth,
712
      changeDepth: this.changeDepth,
713
      lookahead: this.lookahead,
714
      receiveAddress: receive ? receive.toString(this.network) : null,
×
715
      changeAddress: change ? change.toString(this.network) : null,
×
716
      accountKey: this.accountKey.toBase58(this.network),
717
      keys: this.keys.map(key => key.toBase58(this.network))
×
718
    };
719
  }
720

721
  /**
722
   * Convert the account to an object suitable for
723
   * serialization.
724
   * @param {Object} [balance=null]
725
   * @returns {Object}
726
   */
727

728
  getJSON(balance) {
729
    const receive = this.receiveAddress();
124✔
730
    const change = this.changeAddress();
124✔
731

732
    return {
124✔
733
      name: this.name,
734
      initialized: this.initialized,
735
      watchOnly: this.watchOnly,
736
      type: Account.typesByVal[this.type].toLowerCase(),
737
      m: this.m,
738
      n: this.n,
739
      accountIndex: this.accountIndex,
740
      receiveDepth: this.receiveDepth,
741
      changeDepth: this.changeDepth,
742
      lookahead: this.lookahead,
743
      receiveAddress: receive ? receive.toString(this.network) : null,
124✔
744
      changeAddress: change ? change.toString(this.network) : null,
124✔
745
      accountKey: this.accountKey.toBase58(this.network),
746
      keys: this.keys.map(key => key.toBase58(this.network)),
1✔
747
      balance: balance ? balance.toJSON(true) : null
124!
748
    };
749
  }
750

751
  /**
752
   * Calculate serialization size.
753
   * @returns {Number}
754
   */
755

756
  getSize() {
757
    let size = 0;
8,768✔
758
    size += 91;
8,768✔
759
    size += this.keys.length * 74;
8,768✔
760
    return size;
8,768✔
761
  }
762

763
  /**
764
   * Serialize the account.
765
   * @param {BufioWriter} bw
766
   * @returns {BufioWriter}
767
   */
768

769
  write(bw) {
770
    let flags = 0;
8,768✔
771

772
    if (this.initialized)
8,768✔
773
      flags |= 1;
8,756✔
774

775
    bw.writeU8(flags);
8,768✔
776
    bw.writeU8(this.type);
8,768✔
777
    bw.writeU8(this.m);
8,768✔
778
    bw.writeU8(this.n);
8,768✔
779
    bw.writeU32(this.receiveDepth);
8,768✔
780
    bw.writeU32(this.changeDepth);
8,768✔
781
    bw.writeU32(this.lookahead);
8,768✔
782
    writeKey(this.accountKey, bw);
8,768✔
783
    bw.writeU8(this.keys.length);
8,768✔
784

785
    for (const key of this.keys)
8,768✔
786
      writeKey(key, bw);
45✔
787

788
    return bw;
8,768✔
789
  }
790

791
  /**
792
   * Inject properties from serialized data.
793
   * @param {bio.BufferReader} br
794
   */
795

796
  read(br) {
797
    const flags = br.readU8();
53,367✔
798

799
    this.initialized = (flags & 1) !== 0;
53,367✔
800
    this.type = br.readU8();
53,367✔
801
    this.m = br.readU8();
53,367✔
802
    this.n = br.readU8();
53,367✔
803
    this.receiveDepth = br.readU32();
53,367✔
804
    this.changeDepth = br.readU32();
53,367✔
805
    this.lookahead = br.readU32();
53,367✔
806
    this.accountKey = readKey(br);
53,367✔
807

808
    assert(this.type < Account.typesByVal.length);
53,365✔
809

810
    const count = br.readU8();
53,365✔
811

812
    for (let i = 0; i < count; i++) {
53,365✔
813
      const key = readKey(br);
375✔
814
      binary.insert(this.keys, key, cmp, true);
375✔
815
    }
816

817
    return this;
53,365✔
818
  }
819

820
  /**
821
   * Decode account.
822
   * @param {WalletDB} wdb
823
   * @param {Buffer} data
824
   * @returns {Account}
825
   */
826

827
  static decode(wdb, data) {
828
    return new this(wdb).decode(data);
53,367✔
829
  }
830

831
  /**
832
   * Test an object to see if it is a Account.
833
   * @param {Object} obj
834
   * @returns {Boolean}
835
   */
836

837
  static isAccount(obj) {
838
    return obj instanceof Account;
×
839
  }
840
}
841

842
/**
843
 * Account types.
844
 * @enum {Number}
845
 * @default
846
 */
847

848
Account.types = {
1✔
849
  PUBKEYHASH: 0,
850
  MULTISIG: 1
851
};
852

853
/**
854
 * Account types by value.
855
 * @const {Object}
856
 */
857

858
Account.typesByVal = [
1✔
859
  'PUBKEYHASH',
860
  'MULTISIG'
861
];
862

863
/*
864
 * Helpers
865
 */
866

867
function cmp(a, b) {
868
  return a.compare(b);
41✔
869
}
870

871
/**
872
 * @param {HDPublicKey} key
873
 * @param {BufioWriter} bw
874
 * @returns {void}
875
 */
876

877
function writeKey(key, bw) {
878
  bw.writeU8(key.depth);
8,813✔
879
  bw.writeU32BE(key.parentFingerPrint);
8,813✔
880
  bw.writeU32BE(key.childIndex);
8,813✔
881
  bw.writeBytes(key.chainCode);
8,813✔
882
  bw.writeBytes(key.publicKey);
8,813✔
883
}
884

885
/**
886
 * @param {bio.BufferReader} br
887
 * @returns {HDPublicKey}
888
 */
889

890
function readKey(br) {
891
  const key = new HDPublicKey();
53,742✔
892
  key.depth = br.readU8();
53,742✔
893
  key.parentFingerPrint = br.readU32BE();
53,742✔
894
  key.childIndex = br.readU32BE();
53,742✔
895
  key.chainCode = br.readBytes(32);
53,742✔
896
  key.publicKey = br.readBytes(33);
53,742✔
897
  return key;
53,740✔
898
}
899

900
/*
901
 * Expose
902
 */
903

904
module.exports = Account;
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

© 2025 Coveralls, Inc