@secondlayer/verify 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1720 @@
1
+ // src/bytes.ts
2
+ import { sha512_256 } from "@noble/hashes/sha2.js";
3
+ import { bytesToHex, concatBytes, hexToBytes } from "@noble/hashes/utils.js";
4
+ var hex = (b) => bytesToHex(b);
5
+ var unhex = (s) => hexToBytes(s.startsWith("0x") ? s.slice(2) : s);
6
+ var concat = (parts) => concatBytes(...parts);
7
+ var bytesEqual = (a, b) => {
8
+ if (a.length !== b.length)
9
+ return false;
10
+ for (let i = 0;i < a.length; i++)
11
+ if (a[i] !== b[i])
12
+ return false;
13
+ return true;
14
+ };
15
+ var hashAll = (parts) => {
16
+ const h = sha512_256.create();
17
+ for (const p of parts)
18
+ h.update(p);
19
+ return h.digest();
20
+ };
21
+
22
+ class Reader {
23
+ buf;
24
+ pos = 0;
25
+ constructor(buf) {
26
+ this.buf = buf;
27
+ }
28
+ bytes(n) {
29
+ if (n < 0 || this.pos + n > this.buf.length)
30
+ throw new Error(`unexpected end of input at ${this.pos} reading ${n}`);
31
+ const out = this.buf.subarray(this.pos, this.pos + n);
32
+ this.pos += n;
33
+ return out;
34
+ }
35
+ view(n) {
36
+ const b = this.bytes(n);
37
+ return new DataView(b.buffer, b.byteOffset, n);
38
+ }
39
+ u8 = () => this.bytes(1)[0];
40
+ u16 = () => this.view(2).getUint16(0);
41
+ u32 = () => this.view(4).getUint32(0);
42
+ u64 = () => this.view(8).getBigUint64(0);
43
+ i64 = () => this.view(8).getBigInt64(0);
44
+ vec(item, max) {
45
+ const n = this.u32();
46
+ if (n > max)
47
+ throw new Error(`vec length ${n} exceeds ${max}`);
48
+ return Array.from({ length: n }, item);
49
+ }
50
+ get done() {
51
+ return this.pos === this.buf.length;
52
+ }
53
+ }
54
+ // src/header.ts
55
+ import { sha512_256 as sha512_2562 } from "@noble/hashes/sha2.js";
56
+ var NAKAMOTO_BLOCK_VERSION_EPOCH_4 = 1;
57
+ var MAX_SIGNERS = 4000;
58
+ var MAX_PROBLEMATIC_TXS = 1 << 16;
59
+ function parseNakamotoHeader(raw) {
60
+ const r = new Reader(raw);
61
+ const version = r.u8();
62
+ const chainLength = r.u64();
63
+ const burnSpent = r.u64();
64
+ const consensusHash = r.bytes(20);
65
+ const parentBlockId = r.bytes(32);
66
+ const txMerkleRoot = r.bytes(32);
67
+ const stateIndexRoot = r.bytes(32);
68
+ const timestamp = r.u64();
69
+ const minerSignature = r.bytes(65);
70
+ const sigStart = r.pos;
71
+ const signerSignatures = r.vec(() => r.bytes(65), MAX_SIGNERS);
72
+ const sigEnd = r.pos;
73
+ const bits = r.u16();
74
+ if (bits === 0 || bits > MAX_SIGNERS)
75
+ throw new Error(`bad pox_treatment length ${bits}`);
76
+ const bitBytes = Math.ceil(bits / 8);
77
+ if (r.u32() !== bitBytes)
78
+ throw new Error("bad pox_treatment data length");
79
+ r.bytes(bitBytes);
80
+ if ((version & 127) >= NAKAMOTO_BLOCK_VERSION_EPOCH_4) {
81
+ r.vec(() => r.bytes(5), MAX_PROBLEMATIC_TXS);
82
+ }
83
+ const end = r.pos;
84
+ return {
85
+ version,
86
+ chainLength,
87
+ burnSpent,
88
+ consensusHash,
89
+ parentBlockId,
90
+ txMerkleRoot,
91
+ stateIndexRoot,
92
+ timestamp,
93
+ minerSignature,
94
+ signerSignatures,
95
+ signerSighashPreimage: concat([
96
+ raw.subarray(0, sigStart),
97
+ raw.subarray(sigEnd, end)
98
+ ]),
99
+ byteLength: end
100
+ };
101
+ }
102
+ var signerSignatureHash = (h) => sha512_2562(h.signerSighashPreimage);
103
+ var blockId = (h) => hashAll([signerSignatureHash(h), h.consensusHash]);
104
+ // src/marf.ts
105
+ import { sha512_256 as sha512_2563 } from "@noble/hashes/sha2.js";
106
+ var MARF_VALUE_SIZE = 40;
107
+ var NodeId = {
108
+ Empty: 0,
109
+ Leaf: 1,
110
+ Node4: 2,
111
+ Node16: 3,
112
+ Node48: 4,
113
+ Node256: 5
114
+ };
115
+ var NODE_PTR_COUNT = {
116
+ [NodeId.Node4]: 4,
117
+ [NodeId.Node16]: 16,
118
+ [NodeId.Node48]: 48,
119
+ [NodeId.Node256]: 256
120
+ };
121
+ var isBackptr = (id) => (id & 128) !== 0;
122
+ var marfPath = (key) => sha512_2563(new TextEncoder().encode(key));
123
+ function marfValue(value) {
124
+ const v = new Uint8Array(MARF_VALUE_SIZE);
125
+ v.set(sha512_2563(new TextEncoder().encode(value)));
126
+ return v;
127
+ }
128
+ function pathPreimage(path) {
129
+ if (path.length > 32)
130
+ throw new Error("trie path longer than 32 bytes");
131
+ return concat([Uint8Array.of(path.length), path]);
132
+ }
133
+ var leafHash = (path, value) => hashAll([Uint8Array.of(NodeId.Leaf), pathPreimage(path), value]);
134
+ function nodeHash(node, children) {
135
+ const parts = [Uint8Array.of(node.id)];
136
+ for (const p of node.ptrs)
137
+ parts.push(Uint8Array.of(p.id, p.chr), p.backBlock);
138
+ parts.push(pathPreimage(node.path), ...children);
139
+ return hashAll(parts);
140
+ }
141
+ var PROOF_KIND = [
142
+ "Node4",
143
+ "Node16",
144
+ "Node48",
145
+ "Node256",
146
+ "Leaf",
147
+ "Shunt"
148
+ ];
149
+ var KIND_PTRS = {
150
+ Node4: 4,
151
+ Node16: 16,
152
+ Node48: 48,
153
+ Node256: 256
154
+ };
155
+ function decodeProof(bytes) {
156
+ const r = new Reader(bytes);
157
+ const hash = () => r.bytes(32);
158
+ const steps = r.vec(() => {
159
+ const kind = PROOF_KIND[r.u8()];
160
+ if (!kind)
161
+ throw new Error("bad proof step type");
162
+ if (kind === "Shunt")
163
+ return { kind, idx: r.i64(), hashes: r.vec(hash, 1 << 20) };
164
+ const chr = r.u8();
165
+ if (kind === "Leaf")
166
+ return {
167
+ kind,
168
+ chr,
169
+ path: r.bytes(r.u32()),
170
+ value: r.bytes(MARF_VALUE_SIZE)
171
+ };
172
+ const node = {
173
+ id: r.u8(),
174
+ path: r.bytes(r.u32()),
175
+ ptrs: r.vec(() => ({ id: r.u8(), chr: r.u8(), backBlock: r.bytes(32) }), 256)
176
+ };
177
+ const hashes = Array.from({ length: KIND_PTRS[kind] - 1 }, hash);
178
+ return { kind, chr, node, hashes };
179
+ }, 1 << 20);
180
+ if (!r.done)
181
+ throw new Error("trailing bytes after proof");
182
+ return steps;
183
+ }
184
+ var isShunt = (s) => s?.kind === "Shunt";
185
+ var segmentEnd = (p, i) => {
186
+ let j = i + 1;
187
+ while (j < p.length && !isShunt(p[j]))
188
+ j++;
189
+ return j;
190
+ };
191
+ function segmentProofHash(step, childHash) {
192
+ const count = KIND_PTRS[step.kind];
193
+ if (step.node.ptrs.length !== count)
194
+ return null;
195
+ const all = [];
196
+ let ih = 0;
197
+ for (const p of step.node.ptrs) {
198
+ if (p.id !== NodeId.Empty && p.chr === step.chr)
199
+ all.push(childHash);
200
+ else {
201
+ const h = step.hashes[ih++];
202
+ if (!h)
203
+ return null;
204
+ all.push(h);
205
+ }
206
+ }
207
+ return all.length === count ? nodeHash(step.node, all) : null;
208
+ }
209
+ function verifySegment(seg, start) {
210
+ let h = start;
211
+ for (const s of seg) {
212
+ if (s.kind === "Shunt" || !h)
213
+ return null;
214
+ h = s.kind === "Leaf" ? leafHash(s.path, s.value) : segmentProofHash(s, h);
215
+ }
216
+ return h;
217
+ }
218
+ function segmentPathPrefix(seg) {
219
+ const parts = [];
220
+ for (const s of seg) {
221
+ if (s.kind === "Shunt")
222
+ return null;
223
+ if (s.kind === "Leaf")
224
+ parts.push(s.path);
225
+ else
226
+ parts.push(Uint8Array.of(s.chr), s.node.path);
227
+ }
228
+ return concat(parts.reverse());
229
+ }
230
+ function isWellFormed(p, expectedPath) {
231
+ if (p[0]?.kind !== "Leaf")
232
+ return false;
233
+ let i = 0;
234
+ let path = new Uint8Array;
235
+ while (i < p.length) {
236
+ const j = segmentEnd(p, i);
237
+ const prefix = segmentPathPrefix(p.slice(i, j));
238
+ if (!prefix)
239
+ return false;
240
+ if (i === 0) {
241
+ if (!bytesEqual(prefix, expectedPath))
242
+ return false;
243
+ path = prefix;
244
+ } else if (prefix.length > path.length || !bytesEqual(path.subarray(0, prefix.length), prefix))
245
+ return false;
246
+ i = j;
247
+ if (i >= p.length)
248
+ return false;
249
+ let k = i + 1;
250
+ while (k < p.length && isShunt(p[k]))
251
+ k++;
252
+ i = k;
253
+ }
254
+ return true;
255
+ }
256
+ function shuntHash(lead, idx, inserted, hashes) {
257
+ if (idx === 0n)
258
+ return null;
259
+ const all = lead ? [lead] : [];
260
+ let hi = 0;
261
+ for (let i = 0;i < hashes.length + 1; i++) {
262
+ if (idx - 1n === BigInt(i))
263
+ all.push(inserted);
264
+ else {
265
+ const h = hashes[hi++];
266
+ if (!h)
267
+ return null;
268
+ all.push(h);
269
+ }
270
+ }
271
+ return hashAll(all);
272
+ }
273
+ function verifySteps(proof, path, value, root, rootToBlock) {
274
+ if (!isWellFormed(proof, path))
275
+ return false;
276
+ const first = proof[0];
277
+ if (first?.kind !== "Leaf" || !bytesEqual(first.value, value))
278
+ return false;
279
+ let i = 0;
280
+ let j = segmentEnd(proof, i);
281
+ const nodeRoot = verifySegment(proof.slice(i, j), leafHash(first.path, first.value));
282
+ if (!nodeRoot)
283
+ return false;
284
+ i = j;
285
+ const head = proof[i];
286
+ if (!isShunt(head) || head.idx !== 0n)
287
+ return false;
288
+ let trieHash = head.hashes.length === 0 ? nodeRoot : hashAll([nodeRoot, ...head.hashes]);
289
+ i += 1;
290
+ if (i >= proof.length)
291
+ return bytesEqual(root, trieHash);
292
+ let backBlock = rootToBlock.get(hex(trieHash));
293
+ if (!backBlock || isShunt(proof[i]))
294
+ return false;
295
+ while (i < proof.length) {
296
+ j = segmentEnd(proof, i);
297
+ const nextNodeRoot = verifySegment(proof.slice(i, j), backBlock);
298
+ if (!nextNodeRoot)
299
+ return false;
300
+ i = j;
301
+ if (i >= proof.length)
302
+ return false;
303
+ j = i;
304
+ while (j < proof.length) {
305
+ const s = proof[j];
306
+ if (!isShunt(s) || s.idx === 0n)
307
+ break;
308
+ j++;
309
+ }
310
+ j -= 1;
311
+ if (j < i)
312
+ return false;
313
+ let penultimate = trieHash;
314
+ for (const s of proof.slice(i, j)) {
315
+ if (!isShunt(s))
316
+ return false;
317
+ const h = shuntHash(null, s.idx, penultimate, s.hashes);
318
+ if (!h)
319
+ return false;
320
+ penultimate = h;
321
+ }
322
+ const junction = proof[j];
323
+ if (!isShunt(junction))
324
+ return false;
325
+ const next = shuntHash(nextNodeRoot, junction.idx, penultimate, junction.hashes);
326
+ if (!next)
327
+ return false;
328
+ trieHash = next;
329
+ backBlock = rootToBlock.get(hex(trieHash));
330
+ if (!backBlock)
331
+ return false;
332
+ i = j + 1;
333
+ if (bytesEqual(trieHash, root))
334
+ break;
335
+ }
336
+ return bytesEqual(root, trieHash);
337
+ }
338
+ function marfProofAncestors(proof) {
339
+ let steps;
340
+ try {
341
+ steps = decodeProof(proof);
342
+ } catch {
343
+ return [];
344
+ }
345
+ const ids = new Set;
346
+ for (const s of steps) {
347
+ if (s.kind === "Leaf" || s.kind === "Shunt")
348
+ continue;
349
+ const ptr = s.node.ptrs.find((p) => p.id !== NodeId.Empty && p.chr === s.chr);
350
+ if (ptr && isBackptr(ptr.id))
351
+ ids.add(hex(ptr.backBlock));
352
+ }
353
+ return [...ids];
354
+ }
355
+ function verifyMarfProof(input) {
356
+ const rootToBlock = new Map;
357
+ for (const h of input.headers)
358
+ rootToBlock.set(hex(h.stateIndexRoot), blockId(h));
359
+ try {
360
+ const steps = decodeProof(input.proof);
361
+ return verifySteps(steps, input.path, input.value, input.root, rootToBlock);
362
+ } catch {
363
+ return false;
364
+ }
365
+ }
366
+ // src/witness.ts
367
+ var WITNESS_VERSION = 3;
368
+ var PATH_LEN = 32;
369
+ var TRIE_HASH_EMPTY = unhex("c672b8d1ef56ed28ab87c3622c5114069bdd3ad7b8f9737498d0c01ecef0967a");
370
+ var ZERO_BLOCK = new Uint8Array(32);
371
+ function parseWitness(bytes) {
372
+ const r = new Reader(bytes);
373
+ const version = r.u8();
374
+ if (version !== WITNESS_VERSION)
375
+ throw new Error(`unsupported witness version ${version}`);
376
+ const ancestorRoots = Array.from({ length: r.u32() }, () => r.bytes(32));
377
+ const blockTable = Array.from({ length: r.u32() }, () => r.bytes(32));
378
+ const leaves = [];
379
+ const seen = new Set;
380
+ let nodes = 0;
381
+ const readNode = (prefix, expectedId) => {
382
+ nodes++;
383
+ const id = r.u8();
384
+ if (expectedId !== null && id !== expectedId)
385
+ throw new Error(`child id ${id} does not match its pointer id ${expectedId}`);
386
+ const path = r.bytes(r.u8());
387
+ const depth = prefix.length + path.length;
388
+ if (id === NodeId.Leaf) {
389
+ if (depth !== PATH_LEN)
390
+ throw new Error("leaf path does not complete 32 bytes");
391
+ const valueHash = r.bytes(32);
392
+ const full = Uint8Array.from([...prefix, ...path]);
393
+ const key = hex(full);
394
+ if (seen.has(key))
395
+ throw new Error(`duplicate leaf ${key}`);
396
+ seen.add(key);
397
+ leaves.push({ path: full, valueHash });
398
+ return leafHash(path, padValue(valueHash));
399
+ }
400
+ const count = NODE_PTR_COUNT[id];
401
+ if (count === undefined)
402
+ throw new Error(`bad node id ${id}`);
403
+ if (depth >= PATH_LEN)
404
+ throw new Error("interior node at full path depth");
405
+ const n = r.u16();
406
+ if (n !== count)
407
+ throw new Error(`node id ${id} has ${n} ptrs, expected ${count}`);
408
+ const ptrs = [];
409
+ for (let i = 0;i < n; i++) {
410
+ const pid = r.u8();
411
+ if (pid === NodeId.Empty) {
412
+ ptrs.push({ id: pid, chr: 0, backBlock: ZERO_BLOCK });
413
+ continue;
414
+ }
415
+ const chr = r.u8();
416
+ if (!isBackptr(pid)) {
417
+ ptrs.push({ id: pid, chr, backBlock: ZERO_BLOCK });
418
+ continue;
419
+ }
420
+ const block = blockTable[r.u32()];
421
+ if (!block)
422
+ throw new Error("backptr table index out of range");
423
+ ptrs.push({ id: pid, chr, backBlock: block });
424
+ }
425
+ const childPrefix = [...prefix, ...path];
426
+ const children = ptrs.map((p) => {
427
+ if (p.id === NodeId.Empty)
428
+ return TRIE_HASH_EMPTY;
429
+ if (isBackptr(p.id))
430
+ return p.backBlock;
431
+ return readNode([...childPrefix, p.chr], p.id);
432
+ });
433
+ return nodeHash({ id, path, ptrs }, children);
434
+ };
435
+ const nodeRoot = readNode([], null);
436
+ if (!r.done)
437
+ throw new Error("trailing bytes after witness");
438
+ const root = ancestorRoots.length === 0 ? nodeRoot : hashAll([nodeRoot, ...ancestorRoots]);
439
+ return { root, ancestorRoots, leaves, nodes };
440
+ }
441
+ var padValue = (valueHash) => {
442
+ const v = new Uint8Array(MARF_VALUE_SIZE);
443
+ v.set(valueHash);
444
+ return v;
445
+ };
446
+ var leafHolds = (leaf, value) => value.length === MARF_VALUE_SIZE && bytesEqual(padValue(leaf.valueHash), value);
447
+ var INTERNAL_PREFIX = "__MARF_";
448
+ function classifyBlockDiff(input) {
449
+ const writes = indexClaims(input.writes, (v) => marfValue(v));
450
+ const internal = indexClaims(input.internal, (v) => {
451
+ const value = unhex(v);
452
+ if (value.length !== MARF_VALUE_SIZE)
453
+ throw new Error(`internal value must be ${MARF_VALUE_SIZE} bytes`);
454
+ return value;
455
+ });
456
+ for (const { key } of internal.values())
457
+ if (!key.startsWith(INTERNAL_PREFIX))
458
+ throw new Error(`internal key must start with ${INTERNAL_PREFIX}: ${key}`);
459
+ const out = {
460
+ writes: [],
461
+ carried: [],
462
+ internal: [],
463
+ hidden: [],
464
+ missing: [],
465
+ complete: false
466
+ };
467
+ const matched = new Set;
468
+ for (const leaf of input.leaves) {
469
+ const pathHex = hex(leaf.path);
470
+ const write = writes.get(pathHex);
471
+ if (write && leafHolds(leaf, write.value)) {
472
+ out.writes.push({ ...leaf, key: write.key });
473
+ matched.add(pathHex);
474
+ continue;
475
+ }
476
+ const bookkeeping = internal.get(pathHex);
477
+ if (bookkeeping && leafHolds(leaf, bookkeeping.value)) {
478
+ out.internal.push({ ...leaf, key: bookkeeping.key });
479
+ continue;
480
+ }
481
+ const parent = write ? undefined : input.parentValue(pathHex);
482
+ if (parent !== undefined && leafHolds(leaf, unhex(parent)))
483
+ out.carried.push(leaf);
484
+ else
485
+ out.hidden.push(leaf);
486
+ }
487
+ for (const [pathHex, { key }] of writes)
488
+ if (!matched.has(pathHex))
489
+ out.missing.push(key);
490
+ out.complete = out.hidden.length === 0 && out.missing.length === 0;
491
+ return out;
492
+ }
493
+ function indexClaims(claims, toValue) {
494
+ const byPath = new Map;
495
+ for (const [key, value] of claims) {
496
+ const pathHex = hex(marfPath(key));
497
+ if (byPath.has(pathHex))
498
+ throw new Error(`duplicate claimed key ${key}`);
499
+ byPath.set(pathHex, { key, value: toValue(value) });
500
+ }
501
+ return byPath;
502
+ }
503
+ // src/signers.ts
504
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
505
+ import { deserializeCV } from "@secondlayer/stacks/clarity";
506
+ import { c32address, hash160 } from "@secondlayer/stacks/utils";
507
+ var MAINNET_P2PKH = 22;
508
+ var SIGNERS_CONTRACT = "SP000000000000000000002Q6VF78.signers";
509
+ function decodeSignerSet(value) {
510
+ const cv = deserializeCV(typeof value === "string" ? value : hex(value));
511
+ if (cv.type !== "some" || cv.value.type !== "list")
512
+ throw new Error("signer set must be (some (list ...))");
513
+ const set = new Map;
514
+ for (const entry of cv.value.value) {
515
+ if (entry.type !== "tuple")
516
+ throw new Error("signer entry must be a tuple");
517
+ const { signer, weight } = entry.value;
518
+ if (signer?.type !== "address" || weight?.type !== "uint")
519
+ throw new Error("signer entry needs (signer principal) (weight uint)");
520
+ if (set.has(signer.value))
521
+ throw new Error(`duplicate signer ${signer.value}`);
522
+ set.set(signer.value, BigInt(weight.value));
523
+ }
524
+ return set;
525
+ }
526
+ function verifySignerSignatures(header, set) {
527
+ const msg = signerSignatureHash(header);
528
+ let totalWeight = 0n;
529
+ for (const w of set.values())
530
+ totalWeight += w;
531
+ const threshold = (totalWeight * 7n + 9n) / 10n;
532
+ const out = {
533
+ valid: false,
534
+ signedWeight: 0n,
535
+ totalWeight,
536
+ threshold,
537
+ signers: [],
538
+ unknown: [],
539
+ duplicates: [],
540
+ invalid: 0
541
+ };
542
+ const seen = new Set;
543
+ for (const sig of header.signerSignatures) {
544
+ const who = recoverSigner(msg, sig);
545
+ if (!who)
546
+ out.invalid++;
547
+ else if (seen.has(who))
548
+ out.duplicates.push(who);
549
+ else {
550
+ seen.add(who);
551
+ const weight = set.get(who);
552
+ if (weight === undefined)
553
+ out.unknown.push(who);
554
+ else {
555
+ out.signers.push(who);
556
+ out.signedWeight += weight;
557
+ }
558
+ }
559
+ }
560
+ out.valid = totalWeight > 0n && out.signedWeight >= threshold && out.unknown.length === 0 && out.duplicates.length === 0 && out.invalid === 0;
561
+ return out;
562
+ }
563
+ function recoverSigner(msg, sig) {
564
+ try {
565
+ const s = secp256k1.Signature.fromBytes(sig, "recovered");
566
+ if (s.hasHighS())
567
+ return null;
568
+ const pubkey = s.recoverPublicKey(msg).toBytes(true);
569
+ return c32address(MAINNET_P2PKH, hex(hash160(pubkey)));
570
+ } catch {
571
+ return null;
572
+ }
573
+ }
574
+ // src/burn.ts
575
+ import { ripemd160 } from "@noble/hashes/legacy.js";
576
+ import { sha256 } from "@noble/hashes/sha2.js";
577
+ var PREIMAGE_PREFIX = Uint8Array.of(23, 0, 0, 0);
578
+ var BHH_START = 4;
579
+ var BHH_END = 36;
580
+ function verifyConsensusPreimage(consensusHash, preimage) {
581
+ if (consensusHash.length !== 20 || preimage.length < BHH_END)
582
+ return null;
583
+ if (!bytesEqual(preimage.subarray(0, BHH_START), PREIMAGE_PREFIX))
584
+ return null;
585
+ if (!bytesEqual(ripemd160(sha256(preimage)), consensusHash))
586
+ return null;
587
+ return hex(preimage.subarray(BHH_START, BHH_END));
588
+ }
589
+ var MAINNET_FIRST_BURN_HEIGHT = 666050;
590
+ var MAINNET_REWARD_CYCLE_LENGTH = 2100;
591
+ var MAINNET_PREPARE_LENGTH = 100;
592
+ var cycleStart = (cycle, firstBurnHeight = MAINNET_FIRST_BURN_HEIGHT, cycleLength = MAINNET_REWARD_CYCLE_LENGTH) => firstBurnHeight + cycle * cycleLength;
593
+ function rewardCycle(burnHeight, firstBurnHeight = MAINNET_FIRST_BURN_HEIGHT, cycleLength = MAINNET_REWARD_CYCLE_LENGTH) {
594
+ if (burnHeight < firstBurnHeight)
595
+ throw new Error(`burn height ${burnHeight} precedes the first burn block`);
596
+ return Math.floor((burnHeight - firstBurnHeight) / cycleLength);
597
+ }
598
+ // src/keys.ts
599
+ import { serializeCV } from "@secondlayer/stacks/clarity";
600
+ import { c32addressDecode } from "@secondlayer/stacks/utils";
601
+ var StoreType = { DataMap: 0, Variable: 1, FungibleToken: 2 };
602
+ function mapEntryKey(contractId, mapName, key) {
603
+ return `vm::${contractId}::${StoreType.DataMap}::${mapName}::${serializeCV(key)}`;
604
+ }
605
+ function dataVarKey(contractId, varName) {
606
+ return `vm::${contractId}::${StoreType.Variable}::${varName}`;
607
+ }
608
+ function ftBalanceKey(contractId, tokenName, holder) {
609
+ return `vm::${contractId}::${StoreType.FungibleToken}::${tokenName}::${principalJson(holder)}`;
610
+ }
611
+ function principalJson(principal) {
612
+ const dot = principal.indexOf(".");
613
+ const address = dot === -1 ? principal : principal.slice(0, dot);
614
+ const [version, hash160] = c32addressDecode(address);
615
+ const issuer = [version, Array.from(unhex(hash160))];
616
+ if (dot === -1)
617
+ return JSON.stringify({ Standard: issuer });
618
+ return JSON.stringify({
619
+ Contract: { issuer, name: principal.slice(dot + 1) }
620
+ });
621
+ }
622
+ // src/bitcoin/chain.ts
623
+ import { bytesToHex as bytesToHex3, hexToBytes as hexToBytes3 } from "@noble/hashes/utils.js";
624
+
625
+ // src/bitcoin/header.ts
626
+ import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
627
+ import { bytesToHex as bytesToHex2, hexToBytes as hexToBytes2 } from "@noble/hashes/utils.js";
628
+ var HEADER_SIZE = 80;
629
+ function toBytes(raw) {
630
+ return typeof raw === "string" ? hexToBytes2(raw) : raw;
631
+ }
632
+ function reverseBytes(bytes) {
633
+ return Uint8Array.from(bytes).reverse();
634
+ }
635
+ function parseHeader(raw) {
636
+ const bytes = toBytes(raw);
637
+ if (bytes.length !== HEADER_SIZE) {
638
+ throw new RangeError(`block header must be ${HEADER_SIZE} bytes, got ${bytes.length}`);
639
+ }
640
+ const view = new DataView(bytes.buffer, bytes.byteOffset, HEADER_SIZE);
641
+ return {
642
+ version: view.getInt32(0, true),
643
+ prevHash: bytesToHex2(reverseBytes(bytes.subarray(4, 36))),
644
+ merkleRoot: bytesToHex2(reverseBytes(bytes.subarray(36, 68))),
645
+ time: view.getUint32(68, true),
646
+ bits: view.getUint32(72, true),
647
+ nonce: view.getUint32(76, true)
648
+ };
649
+ }
650
+ function headerHash(raw) {
651
+ const bytes = toBytes(raw);
652
+ if (bytes.length !== HEADER_SIZE) {
653
+ throw new RangeError(`block header must be ${HEADER_SIZE} bytes, got ${bytes.length}`);
654
+ }
655
+ return sha2562(sha2562(bytes));
656
+ }
657
+
658
+ // src/bitcoin/pow.ts
659
+ var POW_LIMIT = 0xffffn << 208n;
660
+ var RETARGET_INTERVAL = 2016;
661
+ var TARGET_TIMESPAN = 14 * 24 * 60 * 60;
662
+ var TWO_256 = 1n << 256n;
663
+ function decodeCompact(bits) {
664
+ const size = bits >>> 24;
665
+ let word = bits & 8388607;
666
+ let target;
667
+ if (size <= 3) {
668
+ word >>>= 8 * (3 - size);
669
+ target = BigInt(word);
670
+ } else {
671
+ target = BigInt(word) << BigInt(8 * (size - 3));
672
+ }
673
+ const negative = word !== 0 && (bits & 8388608) !== 0;
674
+ const overflow = word !== 0 && (size > 34 || word > 255 && size > 33 || word > 65535 && size > 32);
675
+ return { target: target & TWO_256 - 1n, negative, overflow };
676
+ }
677
+ function bitsToTarget(bits) {
678
+ const { target, negative, overflow } = decodeCompact(bits);
679
+ if (negative || overflow || target === 0n) {
680
+ throw new RangeError(`bits 0x${bits.toString(16).padStart(8, "0")} do not encode a valid target`);
681
+ }
682
+ return target;
683
+ }
684
+ function targetToBits(target) {
685
+ if (target < 0n)
686
+ throw new RangeError("target must be non-negative");
687
+ let size = target === 0n ? 0 : Math.ceil(target.toString(2).length / 8);
688
+ let compact = size <= 3 ? Number(target << BigInt(8 * (3 - size))) : Number(target >> BigInt(8 * (size - 3)));
689
+ if (compact & 8388608) {
690
+ compact >>>= 8;
691
+ size += 1;
692
+ }
693
+ return (size << 24 | compact) >>> 0;
694
+ }
695
+ function hashToBigInt(hash) {
696
+ let n = 0n;
697
+ for (let i = hash.length - 1;i >= 0; i--) {
698
+ n = n << 8n | BigInt(hash[i]);
699
+ }
700
+ return n;
701
+ }
702
+ function checkProofOfWork(hash, bits) {
703
+ const { target, negative, overflow } = decodeCompact(bits);
704
+ if (negative || overflow || target === 0n || target > POW_LIMIT) {
705
+ return false;
706
+ }
707
+ return hashToBigInt(hash) <= target;
708
+ }
709
+ function headerWork(bits) {
710
+ const target = bitsToTarget(bits);
711
+ return (TWO_256 - 1n - target) / (target + 1n) + 1n;
712
+ }
713
+ function nextRetargetBits(lastBits, firstTime, lastTime) {
714
+ let actual = lastTime - firstTime;
715
+ if (actual < TARGET_TIMESPAN / 4)
716
+ actual = TARGET_TIMESPAN / 4;
717
+ if (actual > TARGET_TIMESPAN * 4)
718
+ actual = TARGET_TIMESPAN * 4;
719
+ let target = bitsToTarget(lastBits) * BigInt(actual) / BigInt(TARGET_TIMESPAN);
720
+ if (target > POW_LIMIT)
721
+ target = POW_LIMIT;
722
+ return targetToBits(target);
723
+ }
724
+
725
+ // src/bitcoin/chain.ts
726
+ var MEDIAN_TIME_SPAN = 11;
727
+ var BIP65_HEIGHT = 388381;
728
+
729
+ class HeaderValidationError extends Error {
730
+ height;
731
+ rule;
732
+ name = "HeaderValidationError";
733
+ constructor(height, rule, detail) {
734
+ super(`header at height ${height} failed ${rule}: ${detail}`);
735
+ this.height = height;
736
+ this.rule = rule;
737
+ }
738
+ }
739
+ var hex8 = (n) => `0x${n.toString(16).padStart(8, "0")}`;
740
+
741
+ class HeaderChain {
742
+ base;
743
+ periodStartTime;
744
+ entries = [];
745
+ heights = new Map;
746
+ work = 0n;
747
+ constructor(base, periodStartTime) {
748
+ this.base = base;
749
+ this.periodStartTime = periodStartTime;
750
+ }
751
+ static fromCheckpoint(checkpoint) {
752
+ const { height } = checkpoint;
753
+ if (!Number.isSafeInteger(height) || height < 0) {
754
+ throw new HeaderValidationError(height, "checkpoint", "height must be a non-negative integer");
755
+ }
756
+ const raw = toRaw(checkpoint.header, height);
757
+ const header = parseHeader(raw);
758
+ try {
759
+ bitsToTarget(header.bits);
760
+ } catch (err) {
761
+ throw new HeaderValidationError(height, "checkpoint", err.message);
762
+ }
763
+ const atPeriodStart = height % RETARGET_INTERVAL === 0;
764
+ let periodStartTime = checkpoint.periodStartTime;
765
+ if (atPeriodStart) {
766
+ if (periodStartTime !== undefined && periodStartTime !== header.time) {
767
+ throw new HeaderValidationError(height, "checkpoint", `periodStartTime ${periodStartTime} disagrees with the period-start checkpoint's own time ${header.time}`);
768
+ }
769
+ periodStartTime = header.time;
770
+ } else if (periodStartTime === undefined) {
771
+ throw new HeaderValidationError(height, "checkpoint", `periodStartTime is required for a checkpoint inside a difficulty period (period starts at ${height - height % RETARGET_INTERVAL})`);
772
+ }
773
+ const chain = new HeaderChain(height, periodStartTime);
774
+ chain.push(raw, header);
775
+ return chain;
776
+ }
777
+ get tip() {
778
+ return this.entries[this.entries.length - 1];
779
+ }
780
+ get totalWork() {
781
+ return this.work;
782
+ }
783
+ get checkpointHeight() {
784
+ return this.base;
785
+ }
786
+ heightOf(hashHex) {
787
+ return this.heights.get(hashHex.toLowerCase());
788
+ }
789
+ headerAt(height) {
790
+ return this.entries[height - this.base];
791
+ }
792
+ append(headers) {
793
+ const before = this.entries.length;
794
+ const workBefore = this.work;
795
+ try {
796
+ for (const item of headers) {
797
+ const height = this.tip.height + 1;
798
+ const raw = toRaw(item, height);
799
+ this.validate(height, raw, parseHeader(raw));
800
+ }
801
+ } catch (err) {
802
+ for (const removed of this.entries.splice(before)) {
803
+ this.heights.delete(removed.hash);
804
+ }
805
+ this.work = workBefore;
806
+ throw err;
807
+ }
808
+ }
809
+ validate(height, raw, header) {
810
+ const prev = this.tip;
811
+ if (header.prevHash !== prev.hash) {
812
+ const known = this.heights.get(header.prevHash);
813
+ throw new HeaderValidationError(height, "prev-link", known === undefined ? `prevHash ${header.prevHash} does not match tip ${prev.hash} at ${prev.height}` : `prevHash points at height ${known}, not the tip at ${prev.height} (forks/reorgs are not supported)`);
814
+ }
815
+ const expectedBits = this.expectedBits(height, prev);
816
+ if (header.bits !== expectedBits) {
817
+ const atRetarget = height % RETARGET_INTERVAL === 0;
818
+ throw new HeaderValidationError(height, atRetarget ? "retarget" : "bits", atRetarget ? `bits ${hex8(header.bits)} != retargeted ${hex8(expectedBits)}` : `bits ${hex8(header.bits)} != previous ${hex8(expectedBits)} (only height % ${RETARGET_INTERVAL} == 0 may change difficulty)`);
819
+ }
820
+ const mtp = this.medianTimePast();
821
+ if (header.time <= mtp) {
822
+ throw new HeaderValidationError(height, "median-time-past", `time ${header.time} <= median of previous ${Math.min(MEDIAN_TIME_SPAN, this.entries.length)} blocks (${mtp})`);
823
+ }
824
+ if (height >= BIP65_HEIGHT && header.version < 4) {
825
+ throw new HeaderValidationError(height, "version", `version ${header.version} < 4 (required from height ${BIP65_HEIGHT})`);
826
+ }
827
+ const hash = headerHash(raw);
828
+ if (!checkProofOfWork(hash, header.bits)) {
829
+ throw new HeaderValidationError(height, "proof-of-work", `hash ${bytesToHex3(reverseBytes(hash))} exceeds target for bits ${hex8(header.bits)}`);
830
+ }
831
+ this.push(raw, header, hash);
832
+ }
833
+ expectedBits(height, prev) {
834
+ if (height % RETARGET_INTERVAL !== 0)
835
+ return prev.header.bits;
836
+ const firstHeight = height - RETARGET_INTERVAL;
837
+ const firstTime = firstHeight < this.base ? this.periodStartTime : this.headerAt(firstHeight).header.time;
838
+ return nextRetargetBits(prev.header.bits, firstTime, prev.header.time);
839
+ }
840
+ medianTimePast() {
841
+ const times = this.entries.slice(-MEDIAN_TIME_SPAN).map((e) => e.header.time).sort((a, b) => a - b);
842
+ return times[Math.floor(times.length / 2)];
843
+ }
844
+ push(raw, header, hash = headerHash(raw)) {
845
+ const height = this.base + this.entries.length;
846
+ const hashHex = bytesToHex3(reverseBytes(hash));
847
+ this.entries.push({ height, hash: hashHex, header, raw });
848
+ this.heights.set(hashHex, height);
849
+ this.work += headerWork(header.bits);
850
+ }
851
+ }
852
+ function toRaw(item, height) {
853
+ let raw;
854
+ try {
855
+ raw = typeof item === "string" ? hexToBytes3(item) : Uint8Array.from(item);
856
+ } catch {
857
+ throw new HeaderValidationError(height, "format", "header is not valid hex");
858
+ }
859
+ if (raw.length !== HEADER_SIZE) {
860
+ throw new HeaderValidationError(height, "format", `header must be ${HEADER_SIZE} bytes, got ${raw.length}`);
861
+ }
862
+ return raw;
863
+ }
864
+ // src/checkpoint.ts
865
+ var MAINNET_CHECKPOINT = {
866
+ stacks: {
867
+ header: [
868
+ "01000000000088a9900000006629951a50e3616a1c1e0b8c05ba66615c7065053804c56e2a68dc605cf905df5a754c57",
869
+ "144e9ddde1191353b05f1a5e6d3c99244aeb006ba4f4db761cb1efecfb92693a8c08fefc766a4cd5ccfcb8f13789a809",
870
+ "e7ee0c2e6b7dfb64868cf1e4e6acc50513202acfab84553454b433248c0ef06cebda3c734c000000006aa28bfd013be3",
871
+ "826a60039f518d7021a4799f2e083dafaaaac7a2d56fda601d674d596d3046b9bbcd52e79fba491e54d92fdab0ca3ab9",
872
+ "9ccf346c6e6726fbd4c8579a02b80000000d0004f2d135dd684fcdf24973ad0e9d4f11ef10890a8159614b1df97d8f31",
873
+ "76532d1ce4ebdcbbf45afcd01d5bb298ef9556b232f877f3586d65303642dabc210059012d5f341c979fd10a1ce9b48f",
874
+ "4a38f68da5ccbb8749c02e164a99668328890ed706accfbec155202f63ff487a26aba793169341c2ff91a43ffed38f0e",
875
+ "941c3dc1012a12bb2148d6cf23dd05e228d29286e3bb87ff717712ecc2c9f0c4297c241ac41f879baeee87a570243a31",
876
+ "cf43e775862e71a7f51cb150bbc31f3a2f62a1772d00f8bdd615ab5b58841c7918d69cf98a030f52ca410c9b91a188ba",
877
+ "038138563ea35f8453075907fc3cdac7e3eff8fa9f61fcb53a1e14a102309fa297e420826bfe015628863a16f1bdc077",
878
+ "90c6eead0cfd7a0aa7222df6d8d41415427b0b4057eb972dc31bd413f1e425ef1bc738cc5c3b8507890bad0940bafdf2",
879
+ "220a6ecadb33ed002f9a476eb20c5e532bafa7b559b285932e1dab1159c93038d8cb0da8d55d25294cc8e6b0972efc08",
880
+ "fdf825407e3215d8d01531f5cddfe993db10be07b38c18fa006999eb6418eac4696bb42ca149f79832442754ea08b0e1",
881
+ "c6237feb522df034a3388c0de90b1a4652218b319f839ff16a132baa90dbbca2876f52b1a5750dc07001cffca3ed6e92",
882
+ "2483e6567f183a78a1c09d16846743f08c5cce93e7e06294c4a722283d31195722b6ce97087c6dd00eedecd1db77be10",
883
+ "9c298c322c53e83ea43201dd0684e8171ad29f6895ed9892d3932daeb0986f32992d6758db194ae9ba8eb256fdff3b61",
884
+ "af0cd448aafe5e4e62e7e75409dfcd3cef3913c792e412849a02550109083fa52598f5fa2907e840050c41db32163838",
885
+ "32aa7cb5e23de14ced47961e356b23e442f2a657ea89fed2845a6b380624dc12347e4d591c80e15016ae35fc005ae002",
886
+ "f1fc5837c21185b88b06cc8611cfb482c021df5329d878e2b4f221341a47e6fe02182c9250d25fe162f42a0bbe9b8ce2",
887
+ "f9a70aa2dcca9e955a61a0fe24016398a0fdf5e2541d3d0838d9f7e24b4093d27660a48cd71bf0728fe5d02e6d374a3e",
888
+ "aa67c91aee87635d7458bb1fe89755021fbe8d34e271fba1d765b065e266008a862c839aeec0d6a9940cfe44e0bc1586",
889
+ "2bd2ac23b3ab6fc59734a8171b44401b395984f0f906b2d15b4a3a24d2136a8ca925485f197197b3aa1bb2c46d02e000",
890
+ "01000000010100000000"
891
+ ].join(""),
892
+ cycle: 143,
893
+ signerSet: [
894
+ "0a0b0000001d0c00000002067369676e65720516f5061b1c62f399ecc0db8cbb21f8d481af079a380677656967687401",
895
+ "000000000000000000000000000002860c00000002067369676e65720516a3bbf2a4133fbd5bd1880982957b7a760347",
896
+ "b2dc0677656967687401000000000000000000000000000000090c00000002067369676e657205164f72f6b5a9f5d863",
897
+ "34b2f0fe701db51a7a246f3d0677656967687401000000000000000000000000000000990c00000002067369676e6572",
898
+ "051653ec8b4f51c8286dad6f4ff81ca2a3409d2f382d0677656967687401000000000000000000000000000000050c00",
899
+ "000002067369676e65720516dba631825ff0f15c7c5946b560070f8f5f5747cb06776569676874010000000000000000",
900
+ "00000000000000240c00000002067369676e6572051600d058b39f4495b412ff7eada763eaa6386eb90a067765696768",
901
+ "7401000000000000000000000000000002860c00000002067369676e65720516321ac33715d684d5e8d0cf427cd98557",
902
+ "6f64c6df06776569676874010000000000000000000000000000000b0c00000002067369676e6572051677a52718ba90",
903
+ "fca67984166b2a084edb9b3bd8f10677656967687401000000000000000000000000000000030c00000002067369676e",
904
+ "65720516a54a20479b3b65a461a427b9161b78e64db58ba70677656967687401000000000000000000000000000001a9",
905
+ "0c00000002067369676e65720516bc1a58c2a058a9e4150db25562e8331ef8a4267c0677656967687401000000000000",
906
+ "000000000000000000060c00000002067369676e657205161091d2da4c9ee17a340171c0d23c4304b54ef43f06776569",
907
+ "67687401000000000000000000000000000000680c00000002067369676e6572051654d01a0973ed139736a1a43032a6",
908
+ "a18d9295ec7206776569676874010000000000000000000000000000008d0c00000002067369676e657205160c49f5b6",
909
+ "c62f1163ddbb21f92aba86f17f7be4880677656967687401000000000000000000000000000000680c00000002067369",
910
+ "676e65720516ad16c79ac56b58f0797f78c5343853674606268006776569676874010000000000000000000000000000",
911
+ "00680c00000002067369676e657205169689e56df00385b6bd81fb6b7769d5b04088acb8067765696768740100000000",
912
+ "0000000000000000000000ad0c00000002067369676e65720516a4f7f81412f4413771a48825cfbe2062a774d4e90677",
913
+ "656967687401000000000000000000000000000003090c00000002067369676e65720516ed73a7398f1c3eb566f3bd7f",
914
+ "4bd43f52adf33d8f0677656967687401000000000000000000000000000000680c00000002067369676e65720516937b",
915
+ "3b43e0d94c9ef8f171892bce949709e1d41f0677656967687401000000000000000000000000000000250c0000000206",
916
+ "7369676e6572051631542aacfd6e04858181c3067b5eb07399ba89df0677656967687401000000000000000000000000",
917
+ "0000005b0c00000002067369676e6572051649f034cd1662a222dbc38a6acd64008ba7e5576606776569676874010000",
918
+ "00000000000000000000000000060c00000002067369676e65720516a20f660dea498c169897a39cb89ce0d98856e572",
919
+ "0677656967687401000000000000000000000000000000090c00000002067369676e6572051652d58897b3678f3a758f",
920
+ "a4b9270744fec0605b210677656967687401000000000000000000000000000000680c00000002067369676e65720516",
921
+ "31733264626dc5fef36eb683a5069d89f63b64cc0677656967687401000000000000000000000000000000090c000000",
922
+ "02067369676e65720516d80db618c2def577604cd8e40a3940b5b639b1ac067765696768740100000000000000000000",
923
+ "00000000000a0c00000002067369676e657205162042a380d946a82866465af8521a273efe6d2a600677656967687401",
924
+ "000000000000000000000000000000490c00000002067369676e6572051635e1b36efc22bc607db92f9100cb08e3473d",
925
+ "602506776569676874010000000000000000000000000000006b0c00000002067369676e657205164843b97054e43dd1",
926
+ "a8571fc1e72949c5bedfb7e906776569676874010000000000000000000000000000004d0c00000002067369676e6572",
927
+ "05164883355670025c19ecae644ffa20e75ce1d730ad0677656967687401000000000000000000000000000000090c00",
928
+ "000002067369676e65720516089ffd0e0ecd61d9e3c8ee42ef94148acf88aa8806776569676874010000000000000000",
929
+ "0000000000000015"
930
+ ].join("")
931
+ },
932
+ bitcoin: {
933
+ height: 967680,
934
+ header: "00000030410ccb1cba623e26f2e02f3fcb0e7f06f66998b68cb5010000000000000000003d16ae2558e8ec7b8dd369ee29bb2733fbd39bcc1058c681970a36a7f1a2bdea1135ae6ac51e0217480c3581"
935
+ }
936
+ };
937
+ // src/source.ts
938
+ class SourceError extends Error {
939
+ url;
940
+ status;
941
+ name = "SourceError";
942
+ constructor(url, status, detail) {
943
+ super(`${url}: ${detail}`);
944
+ this.url = url;
945
+ this.status = status;
946
+ }
947
+ }
948
+ var MAX_ATTEMPTS = 3;
949
+ var MAX_RETRY_SECONDS = 10;
950
+ async function get(url, opts, attempt = 1) {
951
+ const doFetch = opts.fetch ?? fetch;
952
+ let res;
953
+ try {
954
+ res = await doFetch(url, { headers: opts.headers });
955
+ } catch (err) {
956
+ throw new SourceError(url, null, err.message);
957
+ }
958
+ if (res.status === 404)
959
+ return null;
960
+ if (res.status === 503 && attempt < MAX_ATTEMPTS) {
961
+ const header = res.headers.get("retry-after");
962
+ const seconds = header === null ? 1 : Number(header);
963
+ const wait = Number.isFinite(seconds) ? Math.min(Math.max(seconds, 0), MAX_RETRY_SECONDS) : 1;
964
+ await new Promise((r) => setTimeout(r, wait * 1000));
965
+ return get(url, opts, attempt + 1);
966
+ }
967
+ if (!res.ok)
968
+ throw new SourceError(url, res.status, `HTTP ${res.status}`);
969
+ return res;
970
+ }
971
+ async function getJson(url, opts) {
972
+ const res = await get(url, opts);
973
+ return res ? await res.json() : null;
974
+ }
975
+ async function getBytes(url, opts) {
976
+ const res = await get(url, opts);
977
+ return res ? new Uint8Array(await res.arrayBuffer()) : null;
978
+ }
979
+ var required = (url, v) => {
980
+ if (v === null)
981
+ throw new SourceError(url, 404, "not found");
982
+ return v;
983
+ };
984
+ var trimSlash = (s) => s.replace(/\/+$/, "");
985
+ var toMarf = (url, j) => {
986
+ if (j === null)
987
+ return null;
988
+ const proof = j.proof ?? j.marf_proof;
989
+ if (typeof proof !== "string" || typeof j.data !== "string")
990
+ throw new SourceError(url, 200, "MARF answer needs data and proof");
991
+ return { data: j.data, proof: unhex(proof) };
992
+ };
993
+ var VM_WRITE_TYPES = [
994
+ "var_set",
995
+ "map_set",
996
+ "map_insert",
997
+ "map_delete"
998
+ ];
999
+ var PAGE = 1000;
1000
+ async function pages(field, url, http) {
1001
+ const out = [];
1002
+ let cursor = null;
1003
+ for (;; ) {
1004
+ const page = cursor ? `${url}&cursor=${encodeURIComponent(cursor)}` : url;
1005
+ const j = await getJson(page, http);
1006
+ if (j === null)
1007
+ return cursor === null ? null : out;
1008
+ const rows = j[field];
1009
+ if (!Array.isArray(rows))
1010
+ throw new SourceError(page, 200, `answer has no ${field} array`);
1011
+ out.push(...rows);
1012
+ const next = j.next_cursor;
1013
+ if (rows.length < PAGE || typeof next !== "string" || next === cursor)
1014
+ return out;
1015
+ cursor = next;
1016
+ }
1017
+ }
1018
+
1019
+ class SecondlayerProofSource {
1020
+ getBlock;
1021
+ getMarfProof;
1022
+ getWitness;
1023
+ getBurnPreimage;
1024
+ getBitcoinHeaders;
1025
+ getStateWrites;
1026
+ getVmEvents;
1027
+ constructor(opts) {
1028
+ const base = trimSlash(opts.baseUrl);
1029
+ const http = {
1030
+ headers: { authorization: `Bearer ${opts.apiKey}` },
1031
+ fetch: opts.fetch
1032
+ };
1033
+ this.getBlock = async (ref) => {
1034
+ const url = typeof ref === "number" ? `${base}/v1/proofs/block/height/${ref}` : `${base}/v1/proofs/block/${ref}`;
1035
+ return required(url, await getBytes(url, http));
1036
+ };
1037
+ this.getMarfProof = async (pathHex, tipId) => {
1038
+ const url = `${base}/v1/proofs/marf/${pathHex}?tip=${tipId}`;
1039
+ return toMarf(url, await getJson(url, http));
1040
+ };
1041
+ this.getWitness = async (blockId) => {
1042
+ const url = `${base}/v1/proofs/witness/${blockId}`;
1043
+ return required(url, await getBytes(url, http));
1044
+ };
1045
+ this.getBurnPreimage = async (consensusHash) => {
1046
+ const url = `${base}/v1/proofs/burn/${consensusHash}`;
1047
+ const j = required(url, await getJson(url, http));
1048
+ return { preimage: unhex(j.preimage), burnHeight: j.burn_height };
1049
+ };
1050
+ this.getBitcoinHeaders = async (from, count) => {
1051
+ const url = `${base}/v1/proofs/bitcoin-headers?from=${from}&count=${count}`;
1052
+ const j = required(url, await getJson(url, http));
1053
+ if (j.from !== from)
1054
+ throw new SourceError(url, 200, `headers start at ${j.from}`);
1055
+ return j.headers;
1056
+ };
1057
+ this.getStateWrites = (height) => pages("state_writes", `${base}/v1/index/state-writes?block_height=${height}&limit=${PAGE}`, http);
1058
+ this.getVmEvents = async (height) => {
1059
+ const out = [];
1060
+ for (const type of VM_WRITE_TYPES) {
1061
+ const url = `${base}/v1/index/events?event_type=${type}&from_height=${height}&to_height=${height}&limit=${PAGE}`;
1062
+ out.push(...await pages("events", url, http) ?? []);
1063
+ }
1064
+ return out.sort((a, b) => a.event_index - b.event_index);
1065
+ };
1066
+ }
1067
+ }
1068
+
1069
+ class NodeRpcProofSource {
1070
+ getBlock;
1071
+ getMarfProof;
1072
+ constructor(opts) {
1073
+ const base = trimSlash(opts.nodeUrl);
1074
+ const http = { fetch: opts.fetch };
1075
+ this.getBlock = async (ref) => {
1076
+ const url = typeof ref === "number" ? `${base}/v3/blocks/height/${ref}` : `${base}/v3/blocks/${ref}`;
1077
+ return required(url, await getBytes(url, http));
1078
+ };
1079
+ this.getMarfProof = async (pathHex, tipId) => {
1080
+ const url = `${base}/v2/clarity/marf/${pathHex}?tip=${tipId}&proof=1`;
1081
+ return toMarf(url, await getJson(url, http));
1082
+ };
1083
+ }
1084
+ }
1085
+ // src/state.ts
1086
+ var INTERNAL = {
1087
+ self: "__MARF_BLOCK_HEIGHT_SELF",
1088
+ heightToHash: "__MARF_BLOCK_HEIGHT_TO_HASH",
1089
+ hashToHeight: "__MARF_BLOCK_HASH_TO_HEIGHT"
1090
+ };
1091
+ var heightValue = (h) => {
1092
+ const v = new Uint8Array(MARF_VALUE_SIZE);
1093
+ new DataView(v.buffer).setUint32(0, h, true);
1094
+ return hex(v);
1095
+ };
1096
+ var padded = (b32) => `${hex(b32)}${"00".repeat(8)}`;
1097
+ function internalEntries(height, parentBlockId, leaves) {
1098
+ const out = [[INTERNAL.self, heightValue(height)]];
1099
+ const ownKey = `${INTERNAL.heightToHash}::${height}`;
1100
+ const ownPath = marfPath(ownKey);
1101
+ const own = leaves.find((l) => bytesEqual(l.path, ownPath));
1102
+ if (own) {
1103
+ out.push([ownKey, padded(own.valueHash)]);
1104
+ out.push([
1105
+ `${INTERNAL.hashToHeight}::${hex(own.valueHash)}`,
1106
+ heightValue(height)
1107
+ ]);
1108
+ }
1109
+ if (height > 0) {
1110
+ out.push([
1111
+ `${INTERNAL.heightToHash}::${height - 1}`,
1112
+ padded(parentBlockId)
1113
+ ]);
1114
+ out.push([
1115
+ `${INTERNAL.hashToHeight}::${hex(parentBlockId)}`,
1116
+ heightValue(height - 1)
1117
+ ]);
1118
+ }
1119
+ return out;
1120
+ }
1121
+ var utf8 = new TextDecoder("utf-8", { fatal: true });
1122
+ var strip0x = (s) => s.startsWith("0x") ? s.slice(2) : s;
1123
+ var leafHex = (l) => ({
1124
+ path: hex(l.path),
1125
+ valueHash: hex(l.valueHash)
1126
+ });
1127
+ function lastWrites(writes, failures) {
1128
+ const out = new Map;
1129
+ for (const w of [...writes].sort((a, b) => a.ordinal - b.ordinal)) {
1130
+ try {
1131
+ out.set(w.key, {
1132
+ value: utf8.decode(unhex(strip0x(w.value_hex))),
1133
+ txIndex: w.tx_index
1134
+ });
1135
+ } catch {
1136
+ failures.push({
1137
+ step: "names",
1138
+ code: "bad-write",
1139
+ message: `state write ${w.ordinal} value is not UTF-8 hex`,
1140
+ key: w.key,
1141
+ ordinal: w.ordinal
1142
+ });
1143
+ }
1144
+ }
1145
+ return out;
1146
+ }
1147
+ function rowWrite(row) {
1148
+ const need = (v, name) => {
1149
+ if (!v)
1150
+ throw new Error(`${row.event_type} row has no ${name}`);
1151
+ return v;
1152
+ };
1153
+ if (row.event_type === "var_set")
1154
+ return [
1155
+ dataVarKey(row.contract_id, need(row.var_name, "var_name")),
1156
+ strip0x(need(row.raw_value, "raw_value")).toLowerCase()
1157
+ ];
1158
+ const key = `vm::${row.contract_id}::0::${need(row.map, "map")}::${strip0x(need(row.raw_key, "raw_key")).toLowerCase()}`;
1159
+ if (row.event_type === "map_delete")
1160
+ return [key, "09"];
1161
+ return [key, `0a${strip0x(need(row.raw_value, "raw_value")).toLowerCase()}`];
1162
+ }
1163
+ function checkRows(rows, leaves, failures) {
1164
+ const byPath = new Map(leaves.map((l) => [hex(l.path), l]));
1165
+ const named = [];
1166
+ for (const row of [...rows].sort((a, b) => a.event_index - b.event_index)) {
1167
+ try {
1168
+ const [key, value] = rowWrite(row);
1169
+ named.push({ row, key, path: hex(marfPath(key)), value });
1170
+ } catch (err) {
1171
+ failures.push({
1172
+ step: "rows",
1173
+ code: "bad-row",
1174
+ message: err.message,
1175
+ ordinal: row.event_index
1176
+ });
1177
+ }
1178
+ }
1179
+ const last = new Map(named.map((n, i) => [n.path, i]));
1180
+ named.forEach((n, i) => {
1181
+ const leaf = byPath.get(n.path);
1182
+ const at = { key: n.key, path: n.path, ordinal: n.row.event_index };
1183
+ if (!leaf)
1184
+ failures.push({
1185
+ step: "rows",
1186
+ code: "key-not-written",
1187
+ message: `${n.row.event_type} row ${n.row.event_index} names a key the block did not write`,
1188
+ ...at
1189
+ });
1190
+ else if (last.get(n.path) === i && padded(leaf.valueHash) !== hex(marfValue(n.value)))
1191
+ failures.push({
1192
+ step: "rows",
1193
+ code: "value-mismatch",
1194
+ message: `${n.row.event_type} row ${n.row.event_index} value differs from the block's leaf`,
1195
+ ...at
1196
+ });
1197
+ });
1198
+ return named.length;
1199
+ }
1200
+ async function verifyBlockState(input) {
1201
+ const out = { rowsChecked: 0, notes: [], failures: [] };
1202
+ let witness;
1203
+ try {
1204
+ witness = parseWitness(input.witness);
1205
+ } catch (err) {
1206
+ out.failures.push({
1207
+ step: "witness",
1208
+ code: "malformed",
1209
+ message: err.message
1210
+ });
1211
+ return out;
1212
+ }
1213
+ if (!bytesEqual(witness.root, input.stateRoot)) {
1214
+ out.failures.push({
1215
+ step: "witness",
1216
+ code: "root-mismatch",
1217
+ message: `witness root ${hex(witness.root)} != header state_index_root ${hex(input.stateRoot)}`
1218
+ });
1219
+ return out;
1220
+ }
1221
+ const { leaves } = witness;
1222
+ const internal = internalEntries(input.height, input.parentBlockId, leaves);
1223
+ const named = input.writes !== null;
1224
+ const last = lastWrites(input.writes ?? [], out.failures);
1225
+ const writes = [...last].map(([k, w]) => [k, w.value]);
1226
+ const first = classifyBlockDiff({
1227
+ leaves,
1228
+ writes,
1229
+ internal,
1230
+ parentValue: () => {
1231
+ return;
1232
+ }
1233
+ });
1234
+ const inParent = new Map((input.parentLeaves ?? []).map((l) => [hex(l.path), hex(l.valueHash)]));
1235
+ const carried = new Map;
1236
+ for (const leaf of first.hidden) {
1237
+ const pathHex = hex(leaf.path);
1238
+ if (inParent.get(pathHex) === hex(leaf.valueHash) || named && await input.parentHolds?.(leaf))
1239
+ carried.set(pathHex, padded(leaf.valueHash));
1240
+ }
1241
+ const d = classifyBlockDiff({
1242
+ leaves,
1243
+ writes,
1244
+ internal,
1245
+ parentValue: (p) => carried.get(p)
1246
+ });
1247
+ out.diff = {
1248
+ named,
1249
+ writes: named ? d.writes.map((l) => ({
1250
+ ...leafHex(l),
1251
+ key: l.key,
1252
+ txIndex: last.get(l.key)?.txIndex ?? null,
1253
+ named: true
1254
+ })) : d.hidden.map((l) => ({ ...leafHex(l), named: false })),
1255
+ carried: d.carried.map(leafHex),
1256
+ internal: d.internal.map((l) => ({ ...leafHex(l), key: l.key }))
1257
+ };
1258
+ if (!named)
1259
+ out.notes.push(`source has no state_writes for block ${input.height}: ${d.hidden.length} written leaves are proven in the block but unnamed`);
1260
+ else {
1261
+ for (const l of d.hidden)
1262
+ out.failures.push({
1263
+ step: "names",
1264
+ code: "hidden-write",
1265
+ message: `leaf ${hex(l.path)} is not a named write, a carried parent value or MARF bookkeeping`,
1266
+ path: hex(l.path)
1267
+ });
1268
+ for (const key of d.missing)
1269
+ out.failures.push({
1270
+ step: "names",
1271
+ code: "missing-write",
1272
+ message: `state write ${key} has no leaf holding its value`,
1273
+ key,
1274
+ path: hex(marfPath(key))
1275
+ });
1276
+ }
1277
+ if (input.rows)
1278
+ out.rowsChecked = checkRows(input.rows, leaves, out.failures);
1279
+ return out;
1280
+ }
1281
+ // src/verify-block.ts
1282
+ import { uintCV } from "@secondlayer/stacks/clarity";
1283
+ class Broken extends Error {
1284
+ failure;
1285
+ constructor(failure) {
1286
+ super(failure.message);
1287
+ this.failure = failure;
1288
+ }
1289
+ }
1290
+ var fail = (f) => {
1291
+ throw new Broken(f);
1292
+ };
1293
+ async function fetchFor(step, what, call, extra = {}) {
1294
+ try {
1295
+ return await call();
1296
+ } catch (err) {
1297
+ if (err instanceof Broken)
1298
+ throw err;
1299
+ return fail({
1300
+ step,
1301
+ code: "unavailable",
1302
+ message: `${what}: ${err.message}`,
1303
+ ...extra
1304
+ });
1305
+ }
1306
+ }
1307
+ var BITCOIN_BATCH = 2016;
1308
+ async function findAnchor(low, high, window, probe, maxProbes = 32) {
1309
+ let [lo, hi] = [low, high];
1310
+ const target = (window[0] + window[1]) / 2;
1311
+ let bisect = false;
1312
+ for (let i = 0;i < maxProbes; i++) {
1313
+ const span = hi.height - lo.height;
1314
+ if (span <= 1)
1315
+ return null;
1316
+ let h = Math.floor((lo.height + hi.height) / 2);
1317
+ if (!bisect && lo.burn !== undefined && hi.burn !== undefined && hi.burn > lo.burn)
1318
+ h = lo.height + Math.round((target - lo.burn) * span / (hi.burn - lo.burn));
1319
+ h = Math.min(Math.max(h, lo.height + 1), hi.height - 1);
1320
+ const p = await probe(h);
1321
+ if (p.height <= lo.height || p.height >= hi.height)
1322
+ return null;
1323
+ if (p.burn >= window[0] && p.burn < window[1])
1324
+ return p;
1325
+ if (p.burn < window[0])
1326
+ lo = p;
1327
+ else
1328
+ hi = p;
1329
+ bisect = hi.height - lo.height > span / 2;
1330
+ }
1331
+ return null;
1332
+ }
1333
+
1334
+ class BlockVerifier {
1335
+ source;
1336
+ checkpoint;
1337
+ rows;
1338
+ chain;
1339
+ sets = new Map;
1340
+ anchors = new Map;
1341
+ headers = new Map;
1342
+ burns = new Map;
1343
+ preimages = new Map;
1344
+ start;
1345
+ constructor(opts) {
1346
+ this.source = opts.source;
1347
+ this.checkpoint = opts.checkpoint ?? MAINNET_CHECKPOINT;
1348
+ this.rows = opts.rows ?? false;
1349
+ const { stacks } = this.checkpoint;
1350
+ const header = parseNakamotoHeader(unhex(stacks.header));
1351
+ this.start = { header, id: hex(blockId(header)) };
1352
+ this.headers.set(this.start.id, header);
1353
+ this.sets.set(stacks.cycle, decodeSignerSet(stacks.signerSet));
1354
+ this.chain = HeaderChain.fromCheckpoint(this.checkpoint.bitcoin);
1355
+ }
1356
+ async verify(ref) {
1357
+ const out = { ok: false, notes: [], failures: [] };
1358
+ try {
1359
+ await this.run(ref, out);
1360
+ } catch (err) {
1361
+ if (!(err instanceof Broken))
1362
+ throw err;
1363
+ out.failures.unshift(err.failure);
1364
+ }
1365
+ out.ok = out.failures.length === 0;
1366
+ return out;
1367
+ }
1368
+ async run(ref, out) {
1369
+ const src = this.source;
1370
+ const raw = await fetchFor("block", `block ${ref}`, () => src.getBlock(ref));
1371
+ const header = parseHeader2(raw, "block");
1372
+ const id = hex(blockId(header));
1373
+ const height = Number(header.chainLength);
1374
+ if (typeof ref === "string" && id !== ref.replace(/^0x/, "").toLowerCase())
1375
+ fail({
1376
+ step: "block",
1377
+ code: "id-mismatch",
1378
+ message: `source returned block ${id} for ${ref}`
1379
+ });
1380
+ if (typeof ref === "number" && height !== ref)
1381
+ fail({
1382
+ step: "block",
1383
+ code: "height-mismatch",
1384
+ message: `source returned height ${height} for ${ref}`
1385
+ });
1386
+ out.height = height;
1387
+ out.blockId = id;
1388
+ out.stateRoot = hex(header.stateIndexRoot);
1389
+ const burn = await this.bindBurn(header, "burn");
1390
+ const cycle = rewardCycle(burn.burnHeight);
1391
+ out.burnHeight = burn.burnHeight;
1392
+ out.bitcoinBlockHash = burn.hash;
1393
+ out.cycle = cycle;
1394
+ const set = await this.signerSet(cycle, {
1395
+ height,
1396
+ burn: burn.burnHeight
1397
+ });
1398
+ const sig = verifySignerSignatures(header, set);
1399
+ out.signerWeight = sig.signedWeight;
1400
+ out.totalWeight = sig.totalWeight;
1401
+ out.threshold = sig.threshold;
1402
+ if (!sig.valid)
1403
+ fail(signatureFailure(sig, "signatures", cycle));
1404
+ const witness = await fetchFor("witness", `witness ${id}`, () => src.getWitness(id));
1405
+ const { getStateWrites, getVmEvents } = this.rows ? src : {};
1406
+ const writes = getStateWrites ? await fetchFor("names", `state_writes ${height}`, () => getStateWrites.call(src, height)) : null;
1407
+ const rows = getVmEvents ? await fetchFor("rows", `vm_events ${height}`, () => getVmEvents.call(src, height)) : undefined;
1408
+ const parentId = hex(header.parentBlockId);
1409
+ const state = await verifyBlockState({
1410
+ height,
1411
+ parentBlockId: header.parentBlockId,
1412
+ stateRoot: header.stateIndexRoot,
1413
+ witness,
1414
+ writes,
1415
+ rows,
1416
+ parentLeaves: await this.parentLeaves(parentId, out.notes),
1417
+ parentHolds: (leaf) => this.parentHolds(parentId, leaf)
1418
+ });
1419
+ out.diff = state.diff;
1420
+ if (rows)
1421
+ out.rowsChecked = state.rowsChecked;
1422
+ out.notes.push(...state.notes);
1423
+ out.failures.push(...state.failures);
1424
+ }
1425
+ async header(id, step, extra = {}) {
1426
+ const known = this.headers.get(id);
1427
+ if (known)
1428
+ return known;
1429
+ const raw = await fetchFor(step, `block ${id}`, () => this.source.getBlock(id), extra);
1430
+ const header = parseHeader2(raw, step);
1431
+ if (hex(blockId(header)) !== id)
1432
+ fail({
1433
+ step,
1434
+ code: "id-mismatch",
1435
+ message: `source returned another block for ${id}`,
1436
+ blockId: id,
1437
+ ...extra
1438
+ });
1439
+ this.headers.set(id, header);
1440
+ return header;
1441
+ }
1442
+ async proveAt(tip, path, value, proof, step, extra = {}) {
1443
+ const headers = [tip];
1444
+ for (const id of marfProofAncestors(proof))
1445
+ headers.push(await this.header(id, step, extra));
1446
+ return verifyMarfProof({
1447
+ proof,
1448
+ path,
1449
+ value,
1450
+ root: tip.stateIndexRoot,
1451
+ headers
1452
+ });
1453
+ }
1454
+ async parentLeaves(parentId, notes) {
1455
+ try {
1456
+ const parent = await this.header(parentId, "names");
1457
+ const w = parseWitness(await this.source.getWitness(parentId));
1458
+ if (bytesEqual(w.root, parent.stateIndexRoot))
1459
+ return w.leaves;
1460
+ notes.push(`parent witness ${parentId} root does not match its header`);
1461
+ } catch (err) {
1462
+ notes.push(`parent witness ${parentId} unavailable: ${err.message}`);
1463
+ }
1464
+ return;
1465
+ }
1466
+ async parentHolds(parentId, leaf) {
1467
+ const parent = await this.header(parentId, "names");
1468
+ const pathHex = hex(leaf.path);
1469
+ const answer = await fetchFor("names", `MARF ${pathHex} at parent`, () => this.source.getMarfProof(pathHex, parentId));
1470
+ if (!answer)
1471
+ return false;
1472
+ const value = new Uint8Array(MARF_VALUE_SIZE);
1473
+ value.set(leaf.valueHash);
1474
+ return this.proveAt(parent, leaf.path, value, answer.proof, "names");
1475
+ }
1476
+ async bindBurn(header, step, extra = {}) {
1477
+ const ch = hex(header.consensusHash);
1478
+ const known = this.burns.get(ch);
1479
+ if (known)
1480
+ return known;
1481
+ const bp = await fetchFor(step, `burn preimage ${ch}`, () => this.preimage(ch), extra);
1482
+ const hash = verifyConsensusPreimage(header.consensusHash, bp.preimage);
1483
+ if (!hash)
1484
+ return fail({
1485
+ step,
1486
+ code: "preimage-mismatch",
1487
+ message: `preimage does not hash to consensus hash ${ch}`,
1488
+ ...extra
1489
+ });
1490
+ if (bp.burnHeight < this.chain.checkpointHeight)
1491
+ fail({
1492
+ step,
1493
+ code: "before-checkpoint",
1494
+ message: `burn height ${bp.burnHeight} is below the Bitcoin checkpoint ${this.chain.checkpointHeight}`,
1495
+ ...extra
1496
+ });
1497
+ await this.syncBitcoin(bp.burnHeight);
1498
+ const burnHeight = this.chain.heightOf(hash);
1499
+ if (burnHeight === undefined)
1500
+ return fail({
1501
+ step,
1502
+ code: "burn-not-in-chain",
1503
+ message: `burn block ${hash} is not in the verified Bitcoin chain (synced ${this.chain.checkpointHeight}..${this.chain.tip.height})`,
1504
+ ...extra
1505
+ });
1506
+ const bound = { burnHeight, hash };
1507
+ this.burns.set(ch, bound);
1508
+ return bound;
1509
+ }
1510
+ async syncBitcoin(height) {
1511
+ while (this.chain.tip.height < height) {
1512
+ const from = this.chain.tip.height + 1;
1513
+ const count = Math.min(BITCOIN_BATCH, height - from + 1);
1514
+ const headers = await fetchFor("bitcoin", `bitcoin headers ${from}`, () => this.source.getBitcoinHeaders(from, count));
1515
+ if (headers.length === 0)
1516
+ fail({
1517
+ step: "bitcoin",
1518
+ code: "unavailable",
1519
+ message: `source has no Bitcoin headers from ${from}`
1520
+ });
1521
+ try {
1522
+ this.chain.append(headers);
1523
+ } catch (err) {
1524
+ if (!(err instanceof HeaderValidationError))
1525
+ throw err;
1526
+ fail({ step: "bitcoin", code: "invalid-header", message: err.message });
1527
+ }
1528
+ }
1529
+ }
1530
+ async signerSet(cycle, target) {
1531
+ const known = this.sets.get(cycle);
1532
+ if (known)
1533
+ return known;
1534
+ const first = this.checkpoint.stacks.cycle;
1535
+ if (cycle < first)
1536
+ fail({
1537
+ step: "signer-set",
1538
+ code: "before-checkpoint",
1539
+ message: `cycle ${cycle} precedes the checkpoint's cycle ${first}`,
1540
+ cycle
1541
+ });
1542
+ let lo = await this.startPoint();
1543
+ for (let c = first;c < cycle; c++) {
1544
+ const next = this.sets.get(c + 1);
1545
+ if (next) {
1546
+ lo = this.anchors.get(c + 1) ?? lo;
1547
+ continue;
1548
+ }
1549
+ const anchor = await this.locateAnchor(c + 1, lo, target);
1550
+ this.sets.set(c + 1, await this.proveNextSet(c + 1, anchor));
1551
+ this.anchors.set(c + 1, anchor);
1552
+ lo = anchor;
1553
+ }
1554
+ return this.sets.get(cycle);
1555
+ }
1556
+ async startPoint() {
1557
+ const { header } = this.start;
1558
+ const height = Number(header.chainLength);
1559
+ try {
1560
+ return { height, burn: await this.burnHint(header) };
1561
+ } catch {
1562
+ return { height };
1563
+ }
1564
+ }
1565
+ async burnHint(header) {
1566
+ const ch = hex(header.consensusHash);
1567
+ return this.burns.get(ch)?.burnHeight ?? (await this.preimage(ch)).burnHeight;
1568
+ }
1569
+ async preimage(ch) {
1570
+ let bp = this.preimages.get(ch);
1571
+ if (!bp) {
1572
+ bp = await this.source.getBurnPreimage(ch);
1573
+ this.preimages.set(ch, bp);
1574
+ }
1575
+ return bp;
1576
+ }
1577
+ async locateAnchor(cycle, lo, hi) {
1578
+ const start = cycleStart(cycle);
1579
+ const window = [start - MAINNET_PREPARE_LENGTH, start];
1580
+ const probe = async (h) => {
1581
+ const raw = await fetchFor("signer-set", `block at height ${h}`, () => this.source.getBlock(h), { cycle });
1582
+ const header = parseHeader2(raw, "signer-set");
1583
+ const id = hex(blockId(header));
1584
+ this.headers.set(id, header);
1585
+ const burn = await fetchFor("signer-set", `burn hint for block ${id}`, () => this.burnHint(header), { cycle, blockId: id });
1586
+ return { height: Number(header.chainLength), burn, header, id };
1587
+ };
1588
+ const anchor = await findAnchor(lo, hi, window, probe);
1589
+ if (!anchor)
1590
+ return fail({
1591
+ step: "signer-set",
1592
+ code: "anchor-not-found",
1593
+ message: `no block with a burn height in ${window[0]}..${window[1] - 1} (cycle ${cycle} prepare phase) between heights ${lo.height} and ${hi.height}`,
1594
+ cycle
1595
+ });
1596
+ return anchor;
1597
+ }
1598
+ async proveNextSet(cycle, anchor) {
1599
+ const at = { cycle, blockId: anchor.id };
1600
+ const { burnHeight } = await this.bindBurn(anchor.header, "signer-set", at);
1601
+ const start = cycleStart(cycle);
1602
+ if (burnHeight < start - MAINNET_PREPARE_LENGTH || burnHeight >= start)
1603
+ fail({
1604
+ step: "signer-set",
1605
+ code: "anchor-outside-window",
1606
+ message: `anchor burn height ${burnHeight} is outside cycle ${cycle}'s prepare phase ${start - MAINNET_PREPARE_LENGTH}..${start - 1}`,
1607
+ ...at
1608
+ });
1609
+ const signing = rewardCycle(burnHeight);
1610
+ const sig = verifySignerSignatures(anchor.header, this.sets.get(signing));
1611
+ if (!sig.valid)
1612
+ fail({
1613
+ ...signatureFailure(sig, "signer-set", signing),
1614
+ ...at,
1615
+ code: "anchor-unsigned"
1616
+ });
1617
+ const path = marfPath(mapEntryKey(SIGNERS_CONTRACT, "cycle-signer-set", uintCV(cycle)));
1618
+ const answer = await fetchFor("signer-set", `signer set ${cycle} at ${anchor.id}`, () => this.source.getMarfProof(hex(path), anchor.id), at);
1619
+ const value = answer?.data.replace(/^0x/, "") ?? "";
1620
+ const proven = answer !== null && await this.proveAt(anchor.header, path, marfValue(value), answer.proof, "signer-set", at);
1621
+ if (!proven)
1622
+ fail({
1623
+ step: "signer-set",
1624
+ code: "set-proof-invalid",
1625
+ message: `signer set ${cycle} is not proven in anchor ${anchor.id}'s state`,
1626
+ ...at
1627
+ });
1628
+ let set;
1629
+ try {
1630
+ set = decodeSignerSet(value);
1631
+ } catch (err) {
1632
+ return fail({
1633
+ step: "signer-set",
1634
+ code: "set-proof-invalid",
1635
+ message: `signer set ${cycle}: ${err.message}`,
1636
+ ...at
1637
+ });
1638
+ }
1639
+ return set;
1640
+ }
1641
+ }
1642
+ function parseHeader2(raw, step) {
1643
+ try {
1644
+ return parseNakamotoHeader(raw);
1645
+ } catch (err) {
1646
+ return fail({
1647
+ step,
1648
+ code: "invalid-header",
1649
+ message: `block bytes do not parse: ${err.message}`
1650
+ });
1651
+ }
1652
+ }
1653
+ function signatureFailure(sig, step, cycle) {
1654
+ const weight = `${sig.signedWeight}/${sig.totalWeight} signed, threshold ${sig.threshold}`;
1655
+ if (sig.unknown.length)
1656
+ return {
1657
+ step,
1658
+ code: "unknown-signer",
1659
+ message: `${sig.unknown.length} signer(s) not in cycle ${cycle}'s set (${weight})`,
1660
+ cycle
1661
+ };
1662
+ if (sig.duplicates.length)
1663
+ return {
1664
+ step,
1665
+ code: "duplicate-signer",
1666
+ message: `${sig.duplicates.length} duplicate signature(s) (${weight})`,
1667
+ cycle
1668
+ };
1669
+ if (sig.invalid)
1670
+ return {
1671
+ step,
1672
+ code: "bad-signature",
1673
+ message: `${sig.invalid} signature(s) do not recover (${weight})`,
1674
+ cycle
1675
+ };
1676
+ return {
1677
+ step,
1678
+ code: "below-threshold",
1679
+ message: `cycle ${cycle} signers: ${weight}`,
1680
+ cycle
1681
+ };
1682
+ }
1683
+ var verifyBlock = (ref, opts) => new BlockVerifier(opts).verify(ref);
1684
+ export {
1685
+ BlockVerifier,
1686
+ HeaderChain,
1687
+ HeaderValidationError,
1688
+ MAINNET_CHECKPOINT,
1689
+ MAINNET_FIRST_BURN_HEIGHT,
1690
+ MAINNET_PREPARE_LENGTH,
1691
+ MAINNET_REWARD_CYCLE_LENGTH,
1692
+ NodeRpcProofSource,
1693
+ SIGNERS_CONTRACT,
1694
+ SecondlayerProofSource,
1695
+ SourceError,
1696
+ blockId,
1697
+ classifyBlockDiff,
1698
+ cycleStart,
1699
+ dataVarKey,
1700
+ decodeSignerSet,
1701
+ ftBalanceKey,
1702
+ hex,
1703
+ mapEntryKey,
1704
+ marfPath,
1705
+ marfProofAncestors,
1706
+ marfValue,
1707
+ parseNakamotoHeader,
1708
+ parseWitness,
1709
+ rewardCycle,
1710
+ signerSignatureHash,
1711
+ unhex,
1712
+ verifyBlock,
1713
+ verifyBlockState,
1714
+ verifyConsensusPreimage,
1715
+ verifyMarfProof,
1716
+ verifySignerSignatures
1717
+ };
1718
+
1719
+ //# debugId=67FB81E2AAB4645564756E2164756E21
1720
+ //# sourceMappingURL=index.js.map