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

handshake-org / hsd / 12514821230

27 Dec 2024 10:15AM UTC coverage: 71.265% (+0.02%) from 71.248%
12514821230

push

github

nodech
Merge PR #912 from 'nodech/update-docs'

8055 of 13154 branches covered (61.24%)

Branch coverage included in aggregate %.

25721 of 34241 relevant lines covered (75.12%)

34505.68 hits per line

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

70.38
/lib/covenants/rules.js
1
/*!
2
 * rules.js - covenant rules 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');
68✔
10
const bio = require('bufio');
68✔
11
const {BufferSet} = require('buffer-map');
68✔
12
const blake2b = require('bcrypto/lib/blake2b');
68✔
13
const sha3 = require('bcrypto/lib/sha3');
68✔
14
const consensus = require('../protocol/consensus');
68✔
15
const reserved = require('./reserved');
68✔
16
const {locked} = require('./locked');
68✔
17
const OwnershipProof = require('./ownership').OwnershipProof;
68✔
18
const AirdropProof = require('../primitives/airdropproof');
68✔
19
const rules = exports;
68✔
20

21
/** @typedef {import('../types').Amount} AmountValue */
22
/** @typedef {import('../types').Hash} Hash */
23
/** @typedef {import('../protocol/network')} Network */
24
/** @typedef {import('../primitives/tx')} TX */
25
/** @typedef {import('../coins/coinview')} CoinView */
26
/** @typedef {import('./ownership').OwnershipProof} OwnershipProof */
27

28
/*
29
 * Constants
30
 */
31

32
const NAME_BUFFER = Buffer.allocUnsafe(63);
68✔
33

34
const CHARSET = new Uint8Array([
68✔
35
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
36
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
37
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0,
38
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
39
  0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
40
  2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 4,
41
  0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
42
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0
43
]);
44

45
/**
46
 * Covenant Types.
47
 * @enum {Number}
48
 * @default
49
 */
50

51
rules.types = {
68✔
52
  NONE: 0,
53
  CLAIM: 1,
54
  OPEN: 2,
55
  BID: 3,
56
  REVEAL: 4,
57
  REDEEM: 5,
58
  REGISTER: 6,
59
  UPDATE: 7,
60
  RENEW: 8,
61
  TRANSFER: 9,
62
  FINALIZE: 10,
63
  REVOKE: 11
64
};
65

66
const types = rules.types;
68✔
67

68
/**
69
 * Covenant types by value.
70
 * @const {Object}
71
 */
72

73
rules.typesByVal = {
68✔
74
  [types.NONE]: 'NONE',
75
  [types.CLAIM]: 'CLAIM',
76
  [types.OPEN]: 'OPEN',
77
  [types.BID]: 'BID',
78
  [types.REVEAL]: 'REVEAL',
79
  [types.REDEEM]: 'REDEEM',
80
  [types.REGISTER]: 'REGISTER',
81
  [types.UPDATE]: 'UPDATE',
82
  [types.RENEW]: 'RENEW',
83
  [types.TRANSFER]: 'TRANSFER',
84
  [types.FINALIZE]: 'FINALIZE',
85
  [types.REVOKE]: 'REVOKE'
86
};
87

88
/**
89
 * Blacklisted names.
90
 * @const {Set}
91
 */
92

93
rules.blacklist = new Set([
68✔
94
  'example', // ICANN reserved
95
  'invalid', // ICANN reserved
96
  'local', // mDNS
97
  'localhost', // ICANN reserved
98
  'test' // ICANN reserved
99
]);
100

101
/**
102
 * Maximum name size for a TLD.
103
 * @const {Number}
104
 * @default
105
 */
106

107
rules.MAX_NAME_SIZE = 63;
68✔
108

109
/**
110
 * Maximum resource size.
111
 * @const {Number}
112
 * @default
113
 */
114

115
rules.MAX_RESOURCE_SIZE = 512;
68✔
116

117
/**
118
 * Consensus name verification flags (used for block validation).
119
 * @enum {Number}
120
 * @default
121
 */
122

123
rules.nameFlags = {
68✔
124
  VERIFY_COVENANTS_NONE: 0,
125
  // Activated when hardening soft fork activates.
126
  VERIFY_COVENANTS_HARDENED: 1 << 0,
127
  // Activated when ICANN lockup soft fork activates.
128
  VERIFY_COVENANTS_LOCKUP: 1 << 1
129
};
130

131
/**
132
 * Standard verify flags for covenants.
133
 * @const {NameFlags}
134
 * @default
135
 */
136

137
rules.nameFlags.MANDATORY_VERIFY_COVENANT_FLAGS = 0;
68✔
138

139
/**
140
 * Maximum covenant size.
141
 * @const {Number}
142
 * @default
143
 */
144

145
rules.MAX_COVENANT_SIZE = (0
68✔
146
  + 1 + 32
147
  + 1 + 4
148
  + 2 + rules.MAX_RESOURCE_SIZE
149
  + 1 + 32);
150

151
/**
152
 * Hash a domain name.
153
 * @param {String|Buffer} name
154
 * @returns {Hash}
155
 */
156

157
rules.hashName = function hashName(name) {
68✔
158
  if (Buffer.isBuffer(name))
41,818✔
159
    return rules.hashBinary(name);
30,341✔
160
  return rules.hashString(name);
11,477✔
161
};
162

163
/**
164
 * Hash a domain name.
165
 * @param {String} name
166
 * @returns {Hash}
167
 */
168

169
rules.hashString = function hashString(name) {
68✔
170
  assert(typeof name === 'string');
11,482✔
171
  assert(rules.verifyString(name));
11,482✔
172

173
  const slab = NAME_BUFFER;
11,482✔
174
  const written = slab.write(name, 0, slab.length, 'ascii');
11,482✔
175

176
  assert(name.length === written);
11,482✔
177

178
  const buf = slab.slice(0, written);
11,482✔
179

180
  return rules.hashBinary(buf);
11,482✔
181
};
182

183
/**
184
 * Hash a domain name.
185
 * @param {Buffer} name
186
 * @returns {Buffer}
187
 */
188

189
rules.hashBinary = function hashBinary(name) {
68✔
190
  assert(Buffer.isBuffer(name));
41,823✔
191
  assert(rules.verifyBinary(name));
41,823✔
192
  return sha3.digest(name);
41,823✔
193
};
194

195
/**
196
 * Verify a domain name meets handshake requirements.
197
 * @param {String|Buffer} name
198
 * @returns {Boolean}
199
 */
200

201
rules.verifyName = function verifyName(name) {
68✔
202
  if (Buffer.isBuffer(name))
30,484✔
203
    return rules.verifyBinary(name);
13,213✔
204
  return rules.verifyString(name);
17,271✔
205
};
206

207
/**
208
 * Verify a domain name meets handshake requirements.
209
 * @param {String} str
210
 * @returns {Boolean}
211
 */
212

213
rules.verifyString = function verifyString(str) {
68✔
214
  assert(typeof str === 'string');
28,753✔
215

216
  if (str.length === 0)
28,753!
217
    return false;
×
218

219
  if (str.length > rules.MAX_NAME_SIZE)
28,753!
220
    return false;
×
221

222
  for (let i = 0; i < str.length; i++) {
28,753✔
223
    const ch = str.charCodeAt(i);
254,495✔
224

225
    // No unicode characters.
226
    if (ch & 0xff80)
254,495!
227
      return false;
×
228

229
    const type = CHARSET[ch];
254,495✔
230

231
    switch (type) {
254,495!
232
      case 0: // non-printable
233
        return false;
4✔
234
      case 1: // 0-9
235
        break;
66,629✔
236
      case 2: // A-Z
237
        return false;
×
238
      case 3: // a-z
239
        break;
157,795✔
240
      case 4: // - and _
241
        // Do not allow at end or beginning.
242
        if (i === 0 || i === str.length - 1)
30,067!
243
          return false;
×
244
        break;
30,067✔
245
    }
246
  }
247

248
  if (rules.blacklist.has(str))
28,749✔
249
    return false;
2✔
250

251
  return true;
28,747✔
252
};
253

254
/**
255
 * Verify a domain name meets handshake requirements.
256
 * @param {Buffer} buf
257
 * @returns {Boolean}
258
 */
259

260
rules.verifyBinary = function verifyBinary(buf) {
68✔
261
  assert(Buffer.isBuffer(buf));
55,036✔
262

263
  if (buf.length === 0)
55,036!
264
    return false;
×
265

266
  if (buf.length > rules.MAX_NAME_SIZE)
55,036!
267
    return false;
×
268

269
  for (let i = 0; i < buf.length; i++) {
55,036✔
270
    const ch = buf[i];
469,420✔
271

272
    // No unicode characters.
273
    if (ch & 0x80)
469,420!
274
      return false;
×
275

276
    const type = CHARSET[ch];
469,420✔
277

278
    switch (type) {
469,420!
279
      case 0: // non-printable
280
        return false;
×
281
      case 1: // 0-9
282
        break;
119,780✔
283
      case 2: // A-Z
284
        return false;
×
285
      case 3: // a-z
286
        break;
298,362✔
287
      case 4: // - and _
288
        // Do not allow at end or beginning.
289
        if (i === 0 || i === buf.length - 1)
51,278!
290
          return false;
×
291
        break;
51,278✔
292
    }
293
  }
294

295
  const str = buf.toString('binary');
55,036✔
296

297
  if (rules.blacklist.has(str))
55,036!
298
    return false;
×
299

300
  return true;
55,036✔
301
};
302

303
/**
304
 * Get height and week of name hash rollout.
305
 * @param {Buffer} nameHash
306
 * @param {Network} network
307
 * @returns {Array} [height, week]
308
 */
309

310
rules.getRollout = function getRollout(nameHash, network) {
68✔
311
  assert(Buffer.isBuffer(nameHash) && nameHash.length === 32);
11,313✔
312
  assert(network && network.names);
11,313✔
313

314
  if (network.names.noRollout)
11,313!
315
    return [0, 0];
×
316

317
  // Modulo the hash by 52 to get week number.
318
  const week = modBuffer(nameHash, 52);
11,313✔
319

320
  // Multiply result by a number of blocks-per-week.
321
  const height = week * network.names.rolloutInterval;
11,313✔
322

323
  // Add the auction start height to the rollout height.
324
  return [network.names.auctionStart + height, week];
11,313✔
325
};
326

327
/**
328
 * Verify a name hash meets the rollout.
329
 * @param {Buffer} hash
330
 * @param {Number} height
331
 * @param {Network} network
332
 * @returns {Boolean}
333
 */
334

335
rules.hasRollout = function hasRollout(hash, height, network) {
68✔
336
  assert((height >>> 0) === height);
7,797✔
337
  assert(network);
7,797✔
338

339
  const [start] = rules.getRollout(hash, network);
7,797✔
340

341
  if (height < start)
7,797!
342
    return false;
×
343

344
  return true;
7,797✔
345
};
346

347
/**
348
 * Grind a name for rollout.
349
 * Used for testing.
350
 * @param {Number} size
351
 * @param {Number} height
352
 * @param {Network} network
353
 * @returns {String}
354
 */
355

356
rules.grindName = function grindName(size, height, network) {
68✔
357
  assert((size >>> 0) === size);
973✔
358
  assert((height >>> 0) === height);
973✔
359
  assert(network && network.names);
973✔
360

361
  if (height < network.names.auctionStart)
973!
362
    height = network.names.auctionStart;
×
363

364
  for (let i = 0; i < 500000; i++) {
973✔
365
    const name = randomString(size);
3,476✔
366

367
    if (rules.blacklist.has(name))
3,476!
368
      continue;
×
369

370
    const hash = rules.hashName(name);
3,476✔
371
    const [start] = rules.getRollout(hash, network);
3,476✔
372

373
    if (height < start)
3,476✔
374
      continue;
2,503✔
375

376
    if (reserved.has(hash))
973!
377
      continue;
×
378

379
    return name;
973✔
380
  }
381

382
  throw new Error('Could not find available name.');
×
383
};
384

385
/**
386
 * Test whether a name is reserved.
387
 * @param {Buffer} nameHash
388
 * @param {Number} height
389
 * @param {Network} network
390
 * @returns {Boolean}
391
 */
392

393
rules.isReserved = function isReserved(nameHash, height, network) {
68✔
394
  assert(Buffer.isBuffer(nameHash));
8,026✔
395
  assert((height >>> 0) === height);
8,026✔
396
  assert(network && network.names);
8,026✔
397

398
  if (network.names.noReserved)
8,026!
399
    return false;
×
400

401
  if (height >= network.names.claimPeriod)
8,026✔
402
    return false;
102✔
403

404
  return reserved.has(nameHash);
7,924✔
405
};
406

407
/**
408
 * Test whether a name is locked up.
409
 * ICANNLOCKUP soft fork.
410
 * @param {Buffer} nameHash
411
 * @param {Number} height
412
 * @param {Network} network
413
 * @returns {Boolean}
414
 */
415

416
rules.isLockedUp = function isLockedUp(nameHash, height, network) {
68✔
417
  assert(Buffer.isBuffer(nameHash));
1,418✔
418
  assert((height >>> 0) === height);
1,418✔
419
  assert(network && network.names);
1,418✔
420

421
  if (network.names.noReserved)
1,418!
422
    return false;
×
423

424
  if (height < network.names.claimPeriod)
1,418✔
425
    return false;
1,354✔
426

427
  const item = locked.get(nameHash);
64✔
428

429
  if (!item)
64✔
430
    return false;
18✔
431

432
  // ICANN Names are always LOCKED.
433
  if (item.root)
46✔
434
    return true;
21✔
435

436
  // Alexa names will expire after 4 years, after claim period ends.
437
  if (height < network.names.alexaLockupPeriod)
25✔
438
    return true;
18✔
439

440
  return false;
7✔
441
};
442

443
/**
444
 * Create a blind bid hash from a value and nonce.
445
 * @param {AmountValue} value
446
 * @param {Buffer} nonce
447
 * @returns {Buffer}
448
 */
449

450
rules.blind = function blind(value, nonce) {
68✔
451
  assert(Number.isSafeInteger(value) && value >= 0);
11,848✔
452
  assert(Buffer.isBuffer(nonce) && nonce.length === 32);
11,848✔
453

454
  const bw = bio.write(40);
11,848✔
455
  bw.writeU64(value);
11,848✔
456
  bw.writeBytes(nonce);
11,848✔
457

458
  return blake2b.digest(bw.render());
11,848✔
459
};
460

461
/**
462
 * Test whether a transaction has
463
 * names contained in the set.
464
 * @param {TX} tx
465
 * @param {BufferSet} set
466
 * @returns {Boolean}
467
 */
468

469
rules.hasNames = function hasNames(tx, set) {
68✔
470
  assert(tx);
33,325✔
471
  assert(set instanceof BufferSet);
33,325✔
472

473
  for (const {covenant} of tx.outputs) {
33,325✔
474
    if (!covenant.isName())
81,540✔
475
      continue;
60,457✔
476

477
    const nameHash = covenant.getHash(0);
21,083✔
478

479
    switch (covenant.type) {
21,083✔
480
      case types.CLAIM:
481
      case types.OPEN:
482
        if (set.has(nameHash))
3,694✔
483
          return true;
1✔
484
        break;
3,693✔
485
      case types.BID:
486
      case types.REVEAL:
487
      case types.REDEEM:
488
        break;
9,632✔
489
      case types.REGISTER:
490
      case types.UPDATE:
491
      case types.RENEW:
492
      case types.TRANSFER:
493
      case types.FINALIZE:
494
      case types.REVOKE:
495
        if (set.has(nameHash))
7,757✔
496
          return true;
6✔
497
        break;
7,751✔
498
    }
499
  }
500

501
  return false;
33,318✔
502
};
503

504
/**
505
 * Add names to set.
506
 * @param {TX} tx
507
 * @param {BufferSet} set
508
 */
509

510
rules.addNames = function addNames(tx, set) {
68✔
511
  assert(tx);
31,957✔
512
  assert(set instanceof BufferSet);
31,957✔
513

514
  for (const {covenant} of tx.outputs) {
31,957✔
515
    if (!covenant.isName())
78,943✔
516
      continue;
57,867✔
517

518
    const nameHash = covenant.getHash(0);
21,076✔
519

520
    switch (covenant.type) {
21,076✔
521
      case types.CLAIM:
522
      case types.OPEN:
523
        set.add(nameHash);
3,690✔
524
        break;
3,690✔
525
      case types.BID:
526
      case types.REVEAL:
527
      case types.REDEEM:
528
        break;
9,630✔
529
      case types.REGISTER:
530
      case types.UPDATE:
531
      case types.RENEW:
532
      case types.TRANSFER:
533
      case types.FINALIZE:
534
      case types.REVOKE:
535
        set.add(nameHash);
7,756✔
536
        break;
7,756✔
537
    }
538
  }
539
};
540

541
/**
542
 * Remove names from set.
543
 * @param {TX} tx
544
 * @param {BufferSet} set
545
 */
546

547
rules.removeNames = function removeNames(tx, set) {
68✔
548
  assert(tx);
2,111✔
549
  assert(set instanceof BufferSet);
2,111✔
550

551
  for (const {covenant} of tx.outputs) {
2,111✔
552
    if (!covenant.isName())
4,416✔
553
      continue;
3,199✔
554

555
    const nameHash = covenant.getHash(0);
1,217✔
556

557
    switch (covenant.type) {
1,217!
558
      case types.CLAIM:
559
      case types.OPEN:
560
        set.delete(nameHash);
214✔
561
        break;
214✔
562
      case types.BID:
563
      case types.REVEAL:
564
      case types.REDEEM:
565
        break;
721✔
566
      case types.REGISTER:
567
      case types.UPDATE:
568
      case types.RENEW:
569
      case types.TRANSFER:
570
      case types.FINALIZE:
571
      case types.REVOKE:
572
        set.delete(nameHash);
282✔
573
        break;
282✔
574
    }
575
  }
576
};
577

578
/**
579
 * Count name opens.
580
 * @param {TX} tx
581
 * @returns {Number}
582
 */
583

584
rules.countOpens = function countOpens(tx) {
68✔
585
  assert(tx);
82,099✔
586

587
  let total = 0;
82,099✔
588

589
  for (const {covenant} of tx.outputs) {
82,099✔
590
    if (covenant.isOpen())
1,255,883✔
591
      total += 1;
95,926✔
592
  }
593

594
  return total;
82,099✔
595
};
596

597
/**
598
 * Count name updates.
599
 * @param {TX} tx
600
 * @returns {Number}
601
 */
602

603
rules.countUpdates = function countUpdates(tx) {
68✔
604
  assert(tx);
82,096✔
605

606
  let total = 0;
82,096✔
607

608
  for (const {covenant} of tx.outputs) {
82,096✔
609
    switch (covenant.type) {
1,255,278✔
610
      case types.NONE:
611
        break;
212,602✔
612
      case types.CLAIM:
613
      case types.OPEN:
614
        total += 1;
95,388✔
615
        break;
95,388✔
616
      case types.BID:
617
      case types.REVEAL:
618
      case types.REDEEM:
619
        break;
298,741✔
620
      case types.REGISTER:
621
        break;
47,030✔
622
      case types.UPDATE:
623
        total += 1;
185,176✔
624
        break;
185,176✔
625
      case types.RENEW:
626
        break;
187,052✔
627
      case types.TRANSFER:
628
        total += 1;
224,894✔
629
        break;
224,894✔
630
      case types.FINALIZE:
631
        break;
4,268✔
632
      case types.REVOKE:
633
        total += 1;
127✔
634
        break;
127✔
635
    }
636
  }
637

638
  return total;
82,096✔
639
};
640

641
/**
642
 * Count name renewals.
643
 * @param {TX} tx
644
 * @returns {Number}
645
 */
646

647
rules.countRenewals = function countRenewals(tx) {
68✔
648
  assert(tx);
82,092✔
649

650
  let total = 0;
82,092✔
651

652
  for (const {covenant} of tx.outputs) {
82,092✔
653
    switch (covenant.type) {
1,253,472✔
654
      case types.REGISTER:
655
        total += 1;
47,030✔
656
        break;
47,030✔
657
      case types.RENEW:
658
        total += 1;
187,052✔
659
        break;
187,052✔
660
      case types.FINALIZE:
661
        total += 1;
4,268✔
662
        break;
4,268✔
663
    }
664
  }
665

666
  return total;
82,092✔
667
};
668

669
/**
670
 * Check covenant sanity (called from `tx.checkSanity()`).
671
 * @param {TX} tx
672
 * @returns {Boolean}
673
 */
674

675
rules.hasSaneCovenants = function hasSaneCovenants(tx) {
68✔
676
  assert(tx);
34,580✔
677

678
  // Coinbases are only capable of creating claims.
679
  if (tx.isCoinbase()) {
34,580✔
680
    if (tx.inputs.length > tx.outputs.length)
19,181!
681
      return false;
×
682

683
    for (let i = 0; i < tx.outputs.length; i++) {
19,181✔
684
      const output = tx.outputs[i];
20,149✔
685
      const {covenant} = output;
20,149✔
686

687
      switch (covenant.type) {
20,149!
688
        case types.NONE: {
689
          // Just a regular payment.
690
          if (covenant.items.length !== 0)
20,117!
691
            return false;
×
692

693
          // Airdrop proof.
694
          if (i > 0 && i < tx.inputs.length) {
20,117✔
695
            const input = tx.inputs[i];
18✔
696
            const {witness} = input;
18✔
697

698
            // Must have exactly 1 witness item.
699
            if (witness.items.length !== 1)
18!
700
              return false;
×
701

702
            let proof;
703

704
            try {
18✔
705
              proof = AirdropProof.decode(witness.items[0]);
18✔
706
            } catch (e) {
707
              return false;
×
708
            }
709

710
            if (!proof.isSane())
18!
711
              return false;
×
712
          }
713

714
          break;
20,117✔
715
        }
716

717
        case types.CLAIM: {
718
          // Must not be the first input/output.
719
          if (i === 0)
32!
720
            return false;
×
721

722
          // Must be linked.
723
          if (i >= tx.inputs.length)
32!
724
            return false;
×
725

726
          const input = tx.inputs[i];
32✔
727
          const {witness} = input;
32✔
728

729
          // Must have exactly 1 witness item.
730
          if (witness.items.length !== 1)
32!
731
            return false;
×
732

733
          // Should contain a name hash, height,
734
          // name, flags, block hash, and block height.
735
          if (covenant.items.length !== 6)
32!
736
            return false;
×
737

738
          // Name hash is 32 bytes.
739
          if (covenant.items[0].length !== 32)
32!
740
            return false;
×
741

742
          // Height must be 4 bytes.
743
          if (covenant.items[1].length !== 4)
32!
744
            return false;
×
745

746
          // Name must be valid.
747
          if (!rules.verifyName(covenant.items[2]))
32!
748
            return false;
×
749

750
          // Flags must be 1 byte.
751
          if (covenant.items[3].length !== 1)
32!
752
            return false;
×
753

754
          // Block hash must be 32 bytes.
755
          if (covenant.items[4].length !== 32)
32!
756
            return false;
×
757

758
          // Block height must be 4 bytes.
759
          if (covenant.items[5].length !== 4)
32!
760
            return false;
×
761

762
          // Must be a reserved name.
763
          const nameHash = covenant.items[0];
32✔
764

765
          if (!reserved.has(nameHash))
32!
766
            return false;
×
767

768
          // Must match the hash.
769
          const name = covenant.items[2];
32✔
770
          const key = rules.hashName(name);
32✔
771

772
          if (!key.equals(nameHash))
32!
773
            return false;
×
774

775
          /** @type {OwnershipProof} */
776
          let proof;
777

778
          try {
32✔
779
            proof = OwnershipProof.decode(witness.items[0]);
32✔
780
          } catch (e) {
781
            return false;
×
782
          }
783

784
          if (!proof.isSane())
32!
785
            return false;
×
786

787
          if (proof.getName() !== name.toString('binary'))
32!
788
            return false;
×
789

790
          const flags = covenant.getU8(3);
32✔
791
          const weak = (flags & 1) !== 0;
32✔
792

793
          if (proof.isWeak() !== weak)
32!
794
            return false;
×
795

796
          break;
32✔
797
        }
798

799
        default: {
800
          return false;
×
801
        }
802
      }
803
    }
804

805
    return true;
19,181✔
806
  }
807

808
  for (let i = 0; i < tx.outputs.length; i++) {
15,399✔
809
    const output = tx.outputs[i];
77,273✔
810
    const {covenant} = output;
77,273✔
811

812
    switch (covenant.type) {
77,273!
813
      case types.NONE: {
814
        // Just a regular payment.
815
        // Can come from a NONE or a REDEEM.
816
        if (covenant.items.length !== 0)
42,600!
817
          return false;
×
818

819
        break;
42,600✔
820
      }
821
      case types.CLAIM: {
822
        // Cannot exist in a non-coinbase.
823
        return false;
×
824
      }
825
      case types.OPEN: {
826
        // Has to come from NONE or REDEEM.
827

828
        // Should contain a name hash, zero height, and name.
829
        if (covenant.items.length !== 3)
4,672!
830
          return false;
×
831

832
        // Name hash is 32 bytes.
833
        if (covenant.items[0].length !== 32)
4,672!
834
          return false;
×
835

836
        // Height is 4 bytes.
837
        if (covenant.items[1].length !== 4)
4,672!
838
          return false;
×
839

840
        // Height must be zero.
841
        if (covenant.getU32(1) !== 0)
4,672!
842
          return false;
×
843

844
        // Name must be valid.
845
        if (!rules.verifyName(covenant.items[2]))
4,672!
846
          return false;
×
847

848
        const key = rules.hashName(covenant.items[2]);
4,672✔
849

850
        if (!key.equals(covenant.items[0]))
4,672!
851
          return false;
×
852

853
        break;
4,672✔
854
      }
855
      case types.BID: {
856
        // Has to come from NONE or REDEEM.
857

858
        // Should contain a name hash, name, height, and hash.
859
        if (covenant.items.length !== 4)
6,783!
860
          return false;
×
861

862
        // Name hash is 32 bytes.
863
        if (covenant.items[0].length !== 32)
6,783!
864
          return false;
×
865

866
        // Height must be 4 bytes.
867
        if (covenant.items[1].length !== 4)
6,783!
868
          return false;
×
869

870
        // Name must be valid.
871
        if (!rules.verifyName(covenant.items[2]))
6,783!
872
          return false;
×
873

874
        // Hash must be 32 bytes.
875
        if (covenant.items[3].length !== 32)
6,783!
876
          return false;
×
877

878
        const key = rules.hashName(covenant.items[2]);
6,783✔
879

880
        if (!key.equals(covenant.items[0]))
6,783!
881
          return false;
×
882

883
        break;
6,783✔
884
      }
885
      case types.REVEAL: {
886
        // Has to come from a BID.
887
        if (i >= tx.inputs.length)
7,971!
888
          return false;
×
889

890
        // Should contain name hash, height, and nonce.
891
        if (covenant.items.length !== 3)
7,971!
892
          return false;
×
893

894
        // Name hash must be valid.
895
        if (covenant.items[0].length !== 32)
7,971!
896
          return false;
×
897

898
        // Height must be 4 bytes.
899
        if (covenant.items[1].length !== 4)
7,971!
900
          return false;
×
901

902
        // Nonce must be 32 bytes.
903
        if (covenant.items[2].length !== 32)
7,971!
904
          return false;
×
905

906
        break;
7,971✔
907
      }
908
      case types.REDEEM: {
909
        // Has to come from a REVEAL.
910
        if (i >= tx.inputs.length)
3,993!
911
          return false;
×
912

913
        // Should contain name hash and height.
914
        if (covenant.items.length !== 2)
3,993!
915
          return false;
×
916

917
        // Name hash must be valid.
918
        if (covenant.items[0].length !== 32)
3,993!
919
          return false;
×
920

921
        // Height must be 4 bytes.
922
        if (covenant.items[1].length !== 4)
3,993!
923
          return false;
×
924

925
        break;
3,993✔
926
      }
927
      case types.REGISTER: {
928
        // Has to come from a REVEAL.
929
        if (i >= tx.inputs.length)
3,298!
930
          return false;
×
931

932
        // Should contain name hash, height,
933
        // record data and block hash.
934
        if (covenant.items.length !== 4)
3,298!
935
          return false;
×
936

937
        // Name hash must be valid.
938
        if (covenant.items[0].length !== 32)
3,298!
939
          return false;
×
940

941
        // Height must be 4 bytes.
942
        if (covenant.items[1].length !== 4)
3,298!
943
          return false;
×
944

945
        // Record data is limited to 512 bytes.
946
        if (covenant.items[2].length > rules.MAX_RESOURCE_SIZE)
3,298!
947
          return false;
×
948

949
        // Must be a block hash.
950
        if (covenant.items[3].length !== 32)
3,298!
951
          return false;
×
952

953
        break;
3,298✔
954
      }
955
      case types.UPDATE: {
956
        // Has to come from a REGISTER, UPDATE, RENEW, or FINALIZE.
957
        if (i >= tx.inputs.length)
2,098!
958
          return false;
×
959

960
        // Should contain name hash, height, and record data.
961
        if (covenant.items.length !== 3)
2,098!
962
          return false;
×
963

964
        // Name hash must be valid.
965
        if (covenant.items[0].length !== 32)
2,098!
966
          return false;
×
967

968
        // Height must be 4 bytes.
969
        if (covenant.items[1].length !== 4)
2,098!
970
          return false;
×
971

972
        // Record data is limited to 512 bytes.
973
        if (covenant.items[2].length > rules.MAX_RESOURCE_SIZE)
2,098!
974
          return false;
×
975

976
        break;
2,098✔
977
      }
978
      case types.RENEW: {
979
        // Has to come from a REGISTER, UPDATE, RENEW, or FINALIZE.
980
        if (i >= tx.inputs.length)
2,275!
981
          return false;
×
982

983
        // Should contain name hash, height,
984
        // and a block hash.
985
        if (covenant.items.length !== 3)
2,275!
986
          return false;
×
987

988
        // Name hash must be valid.
989
        if (covenant.items[0].length !== 32)
2,275!
990
          return false;
×
991

992
        // Height must be 4 bytes.
993
        if (covenant.items[1].length !== 4)
2,275!
994
          return false;
×
995

996
        // Must be a block hash.
997
        if (covenant.items[2].length !== 32)
2,275!
998
          return false;
×
999

1000
        break;
2,275✔
1001
      }
1002
      case types.TRANSFER: {
1003
        // Has to come from a REGISTER, UPDATE, RENEW, or FINALIZE.
1004
        if (i >= tx.inputs.length)
1,792!
1005
          return false;
×
1006

1007
        // Should contain the address we
1008
        // _intend_ to transfer to.
1009
        if (covenant.items.length !== 4)
1,792!
1010
          return false;
×
1011

1012
        // Name hash must be valid.
1013
        if (covenant.items[0].length !== 32)
1,792!
1014
          return false;
×
1015

1016
        // Height must be 4 bytes.
1017
        if (covenant.items[1].length !== 4)
1,792!
1018
          return false;
×
1019

1020
        // Version must be 1 byte.
1021
        if (covenant.items[2].length !== 1)
1,792!
1022
          return false;
×
1023

1024
        // Must have an address.
1025
        const version = covenant.getU8(2);
1,792✔
1026
        const hash = covenant.items[3];
1,792✔
1027

1028
        // Todo: Add policy rule for high versions.
1029
        if (version > 31)
1,792!
1030
          return false;
×
1031

1032
        // Must obey address size limits.
1033
        if (hash.length < 2 || hash.length > 40)
1,792!
1034
          return false;
×
1035

1036
        break;
1,792✔
1037
      }
1038
      case types.FINALIZE: {
1039
        // Has to come from a TRANSFER.
1040
        if (i >= tx.inputs.length)
1,726!
1041
          return false;
×
1042

1043
        // Should contain name hash and state.
1044
        if (covenant.items.length !== 7)
1,726!
1045
          return false;
×
1046

1047
        // Name hash must be valid.
1048
        if (covenant.items[0].length !== 32)
1,726!
1049
          return false;
×
1050

1051
        // Must be height.
1052
        if (covenant.items[1].length !== 4)
1,726!
1053
          return false;
×
1054

1055
        // Name must be valid.
1056
        if (!rules.verifyName(covenant.items[2]))
1,726!
1057
          return false;
×
1058

1059
        // Must be flags byte.
1060
        if (covenant.items[3].length !== 1)
1,726!
1061
          return false;
×
1062

1063
        // Must be claim height.
1064
        if (covenant.items[4].length !== 4)
1,726!
1065
          return false;
×
1066

1067
        // Must be renewal count.
1068
        if (covenant.items[5].length !== 4)
1,726!
1069
          return false;
×
1070

1071
        // Must be a block hash.
1072
        if (covenant.items[6].length !== 32)
1,726!
1073
          return false;
×
1074

1075
        const key = rules.hashName(covenant.items[2]);
1,726✔
1076

1077
        if (!key.equals(covenant.items[0]))
1,726!
1078
          return false;
×
1079

1080
        break;
1,726✔
1081
      }
1082
      case types.REVOKE: {
1083
        // Has to come from a REGISTER, UPDATE, RENEW, or FINALIZE.
1084
        if (i >= tx.inputs.length)
65!
1085
          return false;
×
1086

1087
        // Should contain name hash and height.
1088
        if (covenant.items.length !== 2)
65!
1089
          return false;
×
1090

1091
        // Name hash must be valid.
1092
        if (covenant.items[0].length !== 32)
65!
1093
          return false;
×
1094

1095
        // Must be height.
1096
        if (covenant.items[1].length !== 4)
65!
1097
          return false;
×
1098

1099
        break;
65✔
1100
      }
1101
      default: {
1102
        // Unknown covenant.
1103
        // Don't enforce anything other than DoS limits.
1104
        if (covenant.items.length > consensus.MAX_SCRIPT_STACK)
×
1105
          return false;
×
1106

1107
        if (covenant.getSize() > rules.MAX_COVENANT_SIZE)
×
1108
          return false;
×
1109

1110
        break;
×
1111
      }
1112
    }
1113
  }
1114

1115
  return true;
15,399✔
1116
};
1117

1118
/**
1119
 * Perform contextual verification for covenants.
1120
 * Called from `tx.checkInputs()`.
1121
 * @param {TX} tx
1122
 * @param {CoinView} view
1123
 * @param {Number} height
1124
 * @param {Network} network
1125
 * @returns {Number}
1126
 */
1127

1128
rules.verifyCovenants = function verifyCovenants(tx, view, height, network) {
68✔
1129
  assert(tx && view);
33,030✔
1130
  assert(network && network.names);
33,030✔
1131

1132
  if (tx.isCoinbase()) {
33,030✔
1133
    let conjured = 0;
19,137✔
1134

1135
    for (let i = 1; i < tx.inputs.length; i++) {
19,137✔
1136
      const {witness} = tx.inputs[i];
46✔
1137
      const output = tx.output(i);
46✔
1138
      const covenant = tx.covenant(i);
46✔
1139

1140
      assert(output && covenant);
46✔
1141

1142
      // Airdrop Proof.
1143
      if (!covenant.isClaim()) {
46✔
1144
        assert(covenant.isNone());
14✔
1145

1146
        if (witness.items.length !== 1)
14!
1147
          return -1;
×
1148

1149
        let proof;
1150
        try {
14✔
1151
          proof = AirdropProof.decode(witness.items[0]);
14✔
1152
        } catch (e) {
1153
          return -1;
×
1154
        }
1155

1156
        assert(proof.isSane());
14✔
1157

1158
        const value = proof.getValue();
14✔
1159

1160
        if (output.value !== value - proof.fee)
14!
1161
          return -1;
×
1162

1163
        if (output.address.version !== proof.version)
14!
1164
          return -1;
×
1165

1166
        if (!output.address.hash.equals(proof.address))
14!
1167
          return -1;
×
1168

1169
        conjured += value;
14✔
1170

1171
        if (conjured > consensus.MAX_MONEY)
14!
1172
          return -1;
×
1173

1174
        continue;
14✔
1175
      }
1176

1177
      // DNSSEC Ownership Proof.
1178
      if (covenant.getU32(1) !== height)
32!
1179
        return -1;
×
1180

1181
      if (witness.items.length !== 1)
32!
1182
        return -1;
×
1183

1184
      /** @type {OwnershipProof} */
1185
      let proof;
1186
      try {
32✔
1187
        proof = OwnershipProof.decode(witness.items[0]);
32✔
1188
      } catch (e) {
1189
        return -1;
×
1190
      }
1191

1192
      const data = proof.getData(network);
32✔
1193

1194
      if (!data)
32!
1195
        return -1;
×
1196

1197
      if (output.address.version !== data.version)
32!
1198
        return -1;
×
1199

1200
      if (!output.address.hash.equals(data.hash))
32!
1201
        return -1;
×
1202

1203
      if (!covenant.getHash(4).equals(data.commitHash))
32!
1204
        return -1;
×
1205

1206
      if (covenant.getU32(5) !== data.commitHeight)
32!
1207
        return -1;
×
1208

1209
      if (output.value !== data.value - data.fee)
32!
1210
        return -1;
×
1211

1212
      if (data.commitHeight === 0) // Fail early.
32!
1213
        return -1;
×
1214

1215
      if (height >= network.deflationHeight) {
32✔
1216
        if (data.commitHeight === 1) {
14✔
1217
          if (data.fee > 1000 * consensus.COIN)
9!
1218
            return -1;
×
1219

1220
          conjured += data.value;
9✔
1221
        } else {
1222
          conjured += output.value;
5✔
1223
        }
1224
      } else {
1225
        conjured += data.value;
18✔
1226
      }
1227

1228
      if (conjured > consensus.MAX_MONEY)
32!
1229
        return -1;
×
1230
    }
1231

1232
    return conjured;
19,137✔
1233
  }
1234

1235
  for (let i = 0; i < tx.inputs.length; i++) {
13,893✔
1236
    const {prevout} = tx.inputs[i];
38,216✔
1237
    const entry = view.getEntry(prevout);
38,216✔
1238
    assert(entry);
38,216✔
1239

1240
    // coin is the UTXO being consumed as an input
1241
    const coin = entry.output;
38,216✔
1242
    const uc = coin.covenant;
38,216✔
1243
    // output is the UTXO being created by the tx
1244
    const output = tx.output(i);
38,216✔
1245
    const covenant = tx.covenant(i);
38,216✔
1246

1247
    switch (uc.type) {
38,216!
1248
      case types.NONE:
1249
      case types.OPEN:
1250
      case types.REDEEM: {
1251
        // Can go nowhere, does not need to be linked.
1252
        if (!output)
16,196✔
1253
          break;
2,676✔
1254

1255
        // Can only go to a NONE, OPEN, or BID.
1256
        switch (covenant.type) {
13,520!
1257
          case types.NONE:
1258
            break;
9,357✔
1259
          case types.OPEN:
1260
            break;
2,324✔
1261
          case types.BID:
1262
            break;
1,839✔
1263
          default:
1264
            return -1;
×
1265
        }
1266

1267
        break;
13,520✔
1268
      }
1269
      case types.BID: {
1270
        // Must be be linked.
1271
        if (!output)
7,970!
1272
          return -1;
×
1273

1274
        // Bid has to go to a reveal.
1275
        if (!covenant.isReveal())
7,970!
1276
          return -1;
×
1277

1278
        // Names must match.
1279
        if (!covenant.getHash(0).equals(uc.getHash(0)))
7,970!
1280
          return -1;
×
1281

1282
        // Heights must match.
1283
        if (covenant.getU32(1) !== uc.getU32(1))
7,970!
1284
          return -1;
×
1285

1286
        const nonce = covenant.getHash(2);
7,970✔
1287
        const blind = rules.blind(output.value, nonce);
7,970✔
1288

1289
        // The value and nonce must match the
1290
        // hash they presented in their bid.
1291
        if (!blind.equals(uc.getHash(3)))
7,970!
1292
          return -1;
×
1293

1294
        // If they lied to us, they can
1295
        // never redeem their money.
1296
        if (coin.value < output.value)
7,970!
1297
          return -1;
×
1298

1299
        break;
7,970✔
1300
      }
1301
      case types.CLAIM:
1302
      case types.REVEAL: {
1303
        // Must be be linked.
1304
        if (!output)
6,692!
1305
          return -1;
×
1306

1307
        // Reveal has to go to a REGISTER, or
1308
        // a REDEEM (in the case of the loser).
1309
        switch (covenant.type) {
6,692!
1310
          case types.REGISTER: {
1311
            // Names must match.
1312
            if (!covenant.getHash(0).equals(uc.getHash(0)))
2,698!
1313
              return -1;
×
1314

1315
            // Heights must match.
1316
            if (covenant.getU32(1) !== uc.getU32(1))
2,698!
1317
              return -1;
×
1318

1319
            // Addresses must match.
1320
            if (!output.address.equals(coin.address))
2,698!
1321
              return -1;
×
1322

1323
            // Note: We use a vickrey auction.
1324
            // Output value must match the second
1325
            // highest bid. This will be checked
1326
            // elsewhere.
1327

1328
            break;
2,698✔
1329
          }
1330
          case types.REDEEM: {
1331
            // Names must match.
1332
            if (!covenant.getHash(0).equals(uc.getHash(0)))
3,994!
1333
              return -1;
×
1334

1335
            // Heights must match.
1336
            if (covenant.getU32(1) !== uc.getU32(1))
3,994!
1337
              return -1;
×
1338

1339
            if (uc.isClaim())
3,994!
1340
              return -1;
×
1341

1342
            break;
3,994✔
1343
          }
1344
          default: {
1345
            return -1;
×
1346
          }
1347
        }
1348

1349
        break;
6,692✔
1350
      }
1351
      case types.REGISTER:
1352
      case types.UPDATE:
1353
      case types.RENEW:
1354
      case types.FINALIZE: {
1355
        // Must be be linked.
1356
        if (!output)
5,593!
1357
          return -1;
×
1358

1359
        // Money is now locked up forever.
1360
        if (output.value !== coin.value)
5,593!
1361
          return -1;
×
1362

1363
        // Addresses must match.
1364
        if (!output.address.equals(coin.address))
5,593!
1365
          return -1;
×
1366

1367
        // Can only send to an UPDATE, RENEW, TRANSFER, or REVOKE.
1368
        switch (covenant.type) {
5,593!
1369
          case types.UPDATE:
1370
          case types.RENEW:
1371
          case types.TRANSFER:
1372
          case types.REVOKE: {
1373
            // Names must match.
1374
            if (!covenant.getHash(0).equals(uc.getHash(0)))
5,593!
1375
              return -1;
×
1376

1377
            // Heights must match.
1378
            if (covenant.getU32(1) !== uc.getU32(1))
5,593!
1379
              return -1;
×
1380

1381
            break;
5,593✔
1382
          }
1383
          default: {
1384
            return -1;
×
1385
          }
1386
        }
1387

1388
        break;
5,593✔
1389
      }
1390
      case types.TRANSFER: {
1391
        // Must be be linked.
1392
        if (!output)
1,765!
1393
          return -1;
×
1394

1395
        // Money is now locked up forever.
1396
        if (output.value !== coin.value)
1,765!
1397
          return -1;
×
1398

1399
        // Can only send to an UPDATE, RENEW, FINALIZE, or REVOKE.
1400
        switch (covenant.type) {
1,765!
1401
          case types.UPDATE:
1402
          case types.RENEW:
1403
          case types.REVOKE: {
1404
            // Names must match.
1405
            if (!covenant.getHash(0).equals(uc.getHash(0)))
39!
1406
              return -1;
×
1407

1408
            // Heights must match.
1409
            if (covenant.getU32(1) !== uc.getU32(1))
39!
1410
              return -1;
×
1411

1412
            // Addresses must match.
1413
            if (!output.address.equals(coin.address))
39!
1414
              return -1;
×
1415

1416
            break;
39✔
1417
          }
1418
          case types.FINALIZE: {
1419
            // Names must match.
1420
            if (!covenant.getHash(0).equals(uc.getHash(0)))
1,726!
1421
              return -1;
×
1422

1423
            // Heights must match.
1424
            if (covenant.getU32(1) !== uc.getU32(1))
1,726!
1425
              return -1;
×
1426

1427
            // Address must match the one committed
1428
            // to in the original transfer covenant.
1429
            if (output.address.version !== uc.getU8(2))
1,726!
1430
              return -1;
×
1431

1432
            if (!output.address.hash.equals(uc.get(3)))
1,726!
1433
              return -1;
×
1434

1435
            break;
1,726✔
1436
          }
1437
          default: {
1438
            return -1;
×
1439
          }
1440
        }
1441

1442
        break;
1,765✔
1443
      }
1444
      case types.REVOKE: {
1445
        // Revocations are perma-burned.
1446
        return -1;
×
1447
      }
1448
      default: {
1449
        // Unknown covenant.
1450
        // Don't enforce anything.
1451
        if (covenant.isName())
×
1452
          return -1;
×
1453
        break;
×
1454
      }
1455
    }
1456
  }
1457

1458
  return 0;
13,893✔
1459
};
1460

1461
/*
1462
 * Helpers
1463
 */
1464

1465
function randomString(len) {
1466
  assert((len >>> 0) === len);
3,476✔
1467

1468
  let s = '';
3,476✔
1469

1470
  for (let i = 0; i < len; i++) {
3,476✔
1471
    const n = Math.random() * (0x7b - 0x61) + 0x61;
35,927✔
1472
    const c = Math.floor(n);
35,927✔
1473

1474
    s += String.fromCharCode(c);
35,927✔
1475
  }
1476

1477
  return s;
3,476✔
1478
}
1479

1480
function modBuffer(buf, num) {
1481
  assert(Buffer.isBuffer(buf));
11,313✔
1482
  assert((num & 0xff) === num);
11,313✔
1483
  assert(num !== 0);
11,313✔
1484

1485
  const p = 256 % num;
11,313✔
1486

1487
  let acc = 0;
11,313✔
1488

1489
  for (let i = 0; i < buf.length; i++)
11,313✔
1490
    acc = (p * acc + buf[i]) % num;
362,016✔
1491

1492
  return acc;
11,313✔
1493
}
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