@forestrie/receipt-verify 0.4.0 → 0.5.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.
@@ -14,22 +14,15 @@
14
14
  * at an externally attested tree size (e.g. Univocity `logState` on Base
15
15
  * Sepolia) and compare the computed peak with the published accumulator.
16
16
  */
17
+ import { openMassifNodeStore, type MassifNodeStore } from "@forestrie/merklelog";
17
18
  import { type CoseSign1 } from "./parse-receipt.js";
18
- export type MassifNodeStore = {
19
- /** 32-byte node at MMR index `i` (log data or ancestor peak stack). */
20
- get(i: bigint): Uint8Array;
21
- massifHeight: number;
22
- massifIndex: bigint;
23
- firstIndex: bigint;
24
- /** Last MMR index with log data in this massif blob. */
25
- lastIndex: bigint;
26
- };
27
19
  /**
28
- * Open a v2 massif blob for MMR node reads. Nodes below `firstIndex` resolve
29
- * through the ancestor peak stack; nodes above `lastIndex` are not present in
30
- * this blob and throw.
20
+ * Re-exported for compatibility. The MMR proof math and massif node store now
21
+ * live in `@forestrie/merklelog` (plan-2607-15 §4, phase 2); receipt-verify
22
+ * keeps only the COSE-shaped layer below.
31
23
  */
32
- export declare function openMassifNodeStore(massifBytes: Uint8Array): MassifNodeStore;
24
+ export { openMassifNodeStore };
25
+ export type { MassifNodeStore };
33
26
  export type ParsedCheckpoint = {
34
27
  coseSign1: CoseSign1;
35
28
  unprotected: Map<number, unknown>;
@@ -73,6 +66,4 @@ export declare function computeAccumulatorPeak(opts: {
73
66
  mmrIndex: bigint;
74
67
  mmrSize: bigint;
75
68
  }): Promise<ComputedAccumulatorPeak>;
76
- /** MMR indices of the peaks of the tree ending at `mmrIndex` (inclusive). */
77
- export declare function peakMMRIndexes(mmrIndex: bigint): bigint[];
78
69
  //# sourceMappingURL=build-receipt-offline.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-receipt-offline.d.ts","sourceRoot":"","sources":["../src/build-receipt-offline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAW5B,MAAM,MAAM,eAAe,GAAG;IAC5B,uEAAuE;IACvE,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,UAAU,GAAG,eAAe,CA8C5E;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,oEAAoE;IACpE,YAAY,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,wEAAwE;IACxE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,UAAU,GAAG,IAAI,CAAC;CACnC,CAAC;AAEF,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,eAAe,EAAE,UAAU,GAAG,gBAAgB,CAqB7E;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,UAAU,CAAC;IACxB,eAAe,EAAE,UAAU,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,UAAU,CA2EZ;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,mEAAmE;IACnE,IAAI,EAAE,UAAU,CAAC;IACjB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,WAAW,EAAE,UAAU,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAwBnC;AA2ID,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAczD"}
1
+ {"version":3,"file":"build-receipt-offline.d.ts","sourceRoot":"","sources":["../src/build-receipt-offline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAIL,mBAAmB,EAEnB,KAAK,eAAe,EAErB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAQ5B;;;;GAIG;AACH,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAC/B,YAAY,EAAE,eAAe,EAAE,CAAC;AAEhC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,oEAAoE;IACpE,YAAY,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC/B,wEAAwE;IACxE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,cAAc,EAAE,UAAU,GAAG,IAAI,CAAC;CACnC,CAAC;AAEF,wDAAwD;AACxD,wBAAgB,eAAe,CAAC,eAAe,EAAE,UAAU,GAAG,gBAAgB,CAqB7E;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,UAAU,CAAC;IACxB,eAAe,EAAE,UAAU,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,wBAAwB,GAC9B,UAAU,CA2EZ;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,mEAAmE;IACnE,IAAI,EAAE,UAAU,CAAC;IACjB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,SAAS,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE;IACjD,WAAW,EAAE,UAAU,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAwBnC"}
@@ -15,59 +15,19 @@
15
15
  * Sepolia) and compare the computed peak with the published accumulator.
16
16
  */
17
17
  import { decode as decodeCbor, encode as encodeCbor } from "cbor-x";
18
- import { calculateRoot, Massif, peakStackEnd, } from "@forestrie/merklelog";
18
+ import { calculateRoot, inclusionProof, massifIndexFromMMRIndex, openMassifNodeStore, peakIndexForLeafProof, } from "@forestrie/merklelog";
19
19
  import { requireCoseSign1, toHeaderMap, unwrapCoseSign1Tag, } from "./parse-receipt.js";
20
20
  import { SubtleHasher } from "./subtle-hasher.js";
21
21
  const VDS_COSE_RECEIPT_PROOFS_TAG = 396;
22
22
  const SEAL_PEAK_RECEIPTS_LABEL = -65931;
23
23
  const DELEGATION_CERT_LABEL = 1000;
24
24
  const VDP_CONSISTENCY_PROOF_KEY = -2;
25
- const VALUE_BYTES = 32n;
26
- const MAX_MMR_HEIGHT = 64n;
27
25
  /**
28
- * Open a v2 massif blob for MMR node reads. Nodes below `firstIndex` resolve
29
- * through the ancestor peak stack; nodes above `lastIndex` are not present in
30
- * this blob and throw.
26
+ * Re-exported for compatibility. The MMR proof math and massif node store now
27
+ * live in `@forestrie/merklelog` (plan-2607-15 §4, phase 2); receipt-verify
28
+ * keeps only the COSE-shaped layer below.
31
29
  */
32
- export function openMassifNodeStore(massifBytes) {
33
- const massif = new Massif(massifBytes);
34
- const start = massif.getStart();
35
- const massifHeight = start.massifHeight;
36
- if (!Number.isInteger(massifHeight) ||
37
- massifHeight < 1 ||
38
- massifHeight > 64) {
39
- throw new Error(`massif header has invalid height ${massifHeight}`);
40
- }
41
- const massifIndex = BigInt(start.massifIndex);
42
- const firstIndex = start.firstIndex;
43
- const logStart = peakStackEnd(massifHeight);
44
- const peakStackStart = logStart - MAX_MMR_HEIGHT * VALUE_BYTES;
45
- const blobLen = BigInt(massifBytes.byteLength);
46
- if (blobLen < logStart) {
47
- throw new Error("massif blob too short for v2 layout");
48
- }
49
- const logNodeCount = (blobLen - logStart) / VALUE_BYTES;
50
- const lastIndex = firstIndex + logNodeCount - 1n;
51
- const stackMap = peakStackMapForMassif(massifHeight, firstIndex);
52
- const get = (i) => {
53
- if (i >= firstIndex) {
54
- if (i > lastIndex) {
55
- throw new Error(`mmr index ${i.toString(10)} is beyond this massif's log data ` +
56
- `(last ${lastIndex.toString(10)}); local content does not cover ` +
57
- `the requested tree size`);
58
- }
59
- const off = logStart + (i - firstIndex) * VALUE_BYTES;
60
- return slice32(massifBytes, off, "log-data");
61
- }
62
- const peakIdx = stackMap.get(i);
63
- if (peakIdx === undefined) {
64
- throw new Error(`missing ancestor peak for mmr index ${i.toString(10)}`);
65
- }
66
- const off = peakStackStart + BigInt(peakIdx) * VALUE_BYTES;
67
- return slice32(massifBytes, off, "peak-stack");
68
- };
69
- return { get, massifHeight, massifIndex, firstIndex, lastIndex };
70
- }
30
+ export { openMassifNodeStore };
71
31
  /** Parse a format-v3 checkpoint (`.sth`) COSE Sign1. */
72
32
  export function parseCheckpoint(checkpointBytes) {
73
33
  const decoded = decodeCbor(checkpointBytes);
@@ -179,13 +139,6 @@ function cborBytes(value) {
179
139
  ? encoded
180
140
  : new Uint8Array(encoded);
181
141
  }
182
- function slice32(buf, offset, label) {
183
- if (offset < 0n || offset + 32n > BigInt(buf.byteLength)) {
184
- throw new Error(`out of range read for ${label}: off=${offset.toString(10)}`);
185
- }
186
- const start = Number(offset);
187
- return buf.slice(start, start + 32);
188
- }
189
142
  /**
190
143
  * Sealed mmr size from a format-v3 checkpoint: tree-size-2 of the consistency
191
144
  * proof (`bstr .cbor [tree-size-1, tree-size-2, paths, right-peaks]`) under
@@ -210,144 +163,3 @@ function sealedSizeFromCheckpoint(unprotected) {
210
163
  }
211
164
  return null;
212
165
  }
213
- // --- MMR index math (ported from go-merklelog/mmr, mirrors resolve-receipt) ---
214
- function bitLength(num) {
215
- if (num === 0n)
216
- return 0;
217
- return num.toString(2).length;
218
- }
219
- function allOnes(num) {
220
- return num > 0n && (num & (num + 1n)) === 0n;
221
- }
222
- function jumpLeftPerfect(pos) {
223
- const bl = bitLength(pos);
224
- if (bl === 0)
225
- return pos;
226
- const msb = 1n << BigInt(bl - 1);
227
- return pos - (msb - 1n);
228
- }
229
- function posHeight(pos) {
230
- let current = pos;
231
- while (!allOnes(current)) {
232
- current = jumpLeftPerfect(current);
233
- }
234
- return bitLength(current) - 1;
235
- }
236
- function indexHeight(i) {
237
- return posHeight(i + 1n);
238
- }
239
- function inclusionProof(getNode, mmrLastIndex, i) {
240
- if (i > mmrLastIndex) {
241
- throw new Error("index out of range");
242
- }
243
- let g = BigInt(indexHeight(i));
244
- const proof = [];
245
- while (true) {
246
- const siblingOffset = 2n << g;
247
- let iSibling;
248
- if (BigInt(indexHeight(i + 1n)) > g) {
249
- iSibling = i - siblingOffset + 1n;
250
- i += 1n;
251
- }
252
- else {
253
- iSibling = i + siblingOffset - 1n;
254
- i += siblingOffset;
255
- }
256
- if (iSibling > mmrLastIndex) {
257
- return proof;
258
- }
259
- proof.push(getNode(iSibling));
260
- g += 1n;
261
- }
262
- }
263
- function peaksBitmap(mmrSize) {
264
- if (mmrSize === 0n)
265
- return 0n;
266
- let pos = mmrSize;
267
- let peakSize = (1n << BigInt(bitLength(mmrSize))) - 1n;
268
- let peakMap = 0n;
269
- while (peakSize > 0n) {
270
- peakMap <<= 1n;
271
- if (pos >= peakSize) {
272
- pos -= peakSize;
273
- peakMap |= 1n;
274
- }
275
- peakSize >>= 1n;
276
- }
277
- return peakMap;
278
- }
279
- function popcount64(x) {
280
- let count = 0;
281
- let v = x;
282
- while (v > 0n) {
283
- if ((v & 1n) === 1n)
284
- count += 1;
285
- v >>= 1n;
286
- }
287
- return count;
288
- }
289
- function peakIndexForLeafProof(mmrSize, proofLen) {
290
- const leafCount = peaksBitmap(mmrSize);
291
- const peaksMask = (1n << BigInt(proofLen + 1)) - 1n;
292
- return popcount64(leafCount) - popcount64(leafCount & peaksMask);
293
- }
294
- function topPeak(i) {
295
- const bl = bitLength(i + 2n);
296
- return (1n << BigInt(bl - 1)) - 2n;
297
- }
298
- /** MMR indices of the peaks of the tree ending at `mmrIndex` (inclusive). */
299
- export function peakMMRIndexes(mmrIndex) {
300
- let mmrSize = mmrIndex + 1n;
301
- if (posHeight(mmrSize + 1n) > posHeight(mmrSize)) {
302
- return [];
303
- }
304
- let peak = 0n;
305
- const out = [];
306
- while (mmrSize !== 0n) {
307
- const peakSize = topPeak(mmrSize - 1n) + 1n;
308
- peak = peak + peakSize;
309
- out.push(peak - 1n);
310
- mmrSize -= peakSize;
311
- }
312
- return out;
313
- }
314
- function peakStackMapForMassif(massifHeight, firstIndex) {
315
- const map = new Map();
316
- const iPeaks = peakMMRIndexes(firstIndex);
317
- for (let i = 0; i < iPeaks.length; i++) {
318
- const ip = iPeaks[i];
319
- if (indexHeight(ip) < massifHeight - 1) {
320
- continue;
321
- }
322
- map.set(ip, i);
323
- }
324
- return map;
325
- }
326
- function mmrIndexFromLeafIndex(leafIndex) {
327
- let sum = 0n;
328
- let current = leafIndex;
329
- while (current > 0n) {
330
- const h = BigInt(bitLength(current));
331
- sum += (1n << h) - 1n;
332
- const half = 1n << (h - 1n);
333
- current -= half;
334
- }
335
- return sum;
336
- }
337
- function firstMMRSize(mmrIndex) {
338
- let i = mmrIndex;
339
- let h0 = indexHeight(i);
340
- let h1 = indexHeight(i + 1n);
341
- while (h0 < h1) {
342
- i += 1n;
343
- h0 = h1;
344
- h1 = indexHeight(i + 1n);
345
- }
346
- return i + 1n;
347
- }
348
- function massifIndexFromMMRIndex(massifHeight, mmrIndex) {
349
- const size = firstMMRSize(mmrIndex);
350
- const leafIndex = peaksBitmap(size) - 1n;
351
- const massifMaxLeaves = 1n << BigInt(massifHeight - 1);
352
- return leafIndex / massifMaxLeaves;
353
- }
package/dist/index.d.ts CHANGED
@@ -4,7 +4,12 @@ export type { Grant } from "@forestrie/encoding";
4
4
  export type { VerifyGrantReceiptOfflineInput } from "./verify-grant-receipt-offline.js";
5
5
  export { parseReceipt } from "./parse-receipt.js";
6
6
  export type { BuildReceiptOfflineInput, ComputedAccumulatorPeak, MassifNodeStore, ParsedCheckpoint, } from "./build-receipt-offline.js";
7
- export { buildReceiptOffline, computeAccumulatorPeak, openMassifNodeStore, parseCheckpoint, peakMMRIndexes, } from "./build-receipt-offline.js";
7
+ export { buildReceiptOffline, computeAccumulatorPeak, openMassifNodeStore, parseCheckpoint, } from "./build-receipt-offline.js";
8
+ /**
9
+ * MMR proof math now lives in @forestrie/merklelog (plan-2607-15 §4 hoist);
10
+ * re-exported here to preserve the receipt-verify public surface.
11
+ */
12
+ export { peakMMRIndexes } from "@forestrie/merklelog";
8
13
  export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
9
14
  export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
10
15
  export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,oFAAoF;AACpF,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,EACf,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,cAAc,GACf,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE3E,0EAA0E;AAC1E,OAAO,EACL,cAAc,EACd,iDAAiD,GAClD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC7E,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,oFAAoF;AACpF,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACxF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,EACf,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,GAChB,MAAM,4BAA4B,CAAC;AACpC;;;GAGG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AAC9E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAE3E,0EAA0E;AAC1E,OAAO,EACL,cAAc,EACd,iDAAiD,GAClD,MAAM,2CAA2C,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,0BAA0B,EAAE,MAAM,eAAe,CAAC;AAC7E,YAAY,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,10 @@
1
1
  export { parseReceipt } from "./parse-receipt.js";
2
- export { buildReceiptOffline, computeAccumulatorPeak, openMassifNodeStore, parseCheckpoint, peakMMRIndexes, } from "./build-receipt-offline.js";
2
+ export { buildReceiptOffline, computeAccumulatorPeak, openMassifNodeStore, parseCheckpoint, } from "./build-receipt-offline.js";
3
+ /**
4
+ * MMR proof math now lives in @forestrie/merklelog (plan-2607-15 §4 hoist);
5
+ * re-exported here to preserve the receipt-verify public surface.
6
+ */
7
+ export { peakMMRIndexes } from "@forestrie/merklelog";
3
8
  export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
4
9
  export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
5
10
  export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestrie/receipt-verify",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Offline SCITT grant receipt verification (ADR-0045)",
@@ -29,8 +29,8 @@
29
29
  "dependencies": {
30
30
  "@noble/hashes": "^1.7.1",
31
31
  "cbor-x": "^1.5.9",
32
- "@forestrie/encoding": "0.2.0",
33
- "@forestrie/merklelog": "0.0.3"
32
+ "@forestrie/merklelog": "0.1.0",
33
+ "@forestrie/encoding": "0.2.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "esbuild": "^0.24.0",
@@ -18,8 +18,11 @@
18
18
  import { decode as decodeCbor, encode as encodeCbor } from "cbor-x";
19
19
  import {
20
20
  calculateRoot,
21
- Massif,
22
- peakStackEnd,
21
+ inclusionProof,
22
+ massifIndexFromMMRIndex,
23
+ openMassifNodeStore,
24
+ peakIndexForLeafProof,
25
+ type MassifNodeStore,
23
26
  type Proof,
24
27
  } from "@forestrie/merklelog";
25
28
  import {
@@ -35,71 +38,13 @@ const SEAL_PEAK_RECEIPTS_LABEL = -65931;
35
38
  const DELEGATION_CERT_LABEL = 1000;
36
39
  const VDP_CONSISTENCY_PROOF_KEY = -2;
37
40
 
38
- const VALUE_BYTES = 32n;
39
- const MAX_MMR_HEIGHT = 64n;
40
-
41
- export type MassifNodeStore = {
42
- /** 32-byte node at MMR index `i` (log data or ancestor peak stack). */
43
- get(i: bigint): Uint8Array;
44
- massifHeight: number;
45
- massifIndex: bigint;
46
- firstIndex: bigint;
47
- /** Last MMR index with log data in this massif blob. */
48
- lastIndex: bigint;
49
- };
50
-
51
41
  /**
52
- * Open a v2 massif blob for MMR node reads. Nodes below `firstIndex` resolve
53
- * through the ancestor peak stack; nodes above `lastIndex` are not present in
54
- * this blob and throw.
42
+ * Re-exported for compatibility. The MMR proof math and massif node store now
43
+ * live in `@forestrie/merklelog` (plan-2607-15 §4, phase 2); receipt-verify
44
+ * keeps only the COSE-shaped layer below.
55
45
  */
56
- export function openMassifNodeStore(massifBytes: Uint8Array): MassifNodeStore {
57
- const massif = new Massif(massifBytes);
58
- const start = massif.getStart();
59
- const massifHeight = start.massifHeight;
60
- if (
61
- !Number.isInteger(massifHeight) ||
62
- massifHeight < 1 ||
63
- massifHeight > 64
64
- ) {
65
- throw new Error(`massif header has invalid height ${massifHeight}`);
66
- }
67
- const massifIndex = BigInt(start.massifIndex);
68
- const firstIndex = start.firstIndex;
69
-
70
- const logStart = peakStackEnd(massifHeight);
71
- const peakStackStart = logStart - MAX_MMR_HEIGHT * VALUE_BYTES;
72
- const blobLen = BigInt(massifBytes.byteLength);
73
- if (blobLen < logStart) {
74
- throw new Error("massif blob too short for v2 layout");
75
- }
76
- const logNodeCount = (blobLen - logStart) / VALUE_BYTES;
77
- const lastIndex = firstIndex + logNodeCount - 1n;
78
-
79
- const stackMap = peakStackMapForMassif(massifHeight, firstIndex);
80
-
81
- const get = (i: bigint): Uint8Array => {
82
- if (i >= firstIndex) {
83
- if (i > lastIndex) {
84
- throw new Error(
85
- `mmr index ${i.toString(10)} is beyond this massif's log data ` +
86
- `(last ${lastIndex.toString(10)}); local content does not cover ` +
87
- `the requested tree size`,
88
- );
89
- }
90
- const off = logStart + (i - firstIndex) * VALUE_BYTES;
91
- return slice32(massifBytes, off, "log-data");
92
- }
93
- const peakIdx = stackMap.get(i);
94
- if (peakIdx === undefined) {
95
- throw new Error(`missing ancestor peak for mmr index ${i.toString(10)}`);
96
- }
97
- const off = peakStackStart + BigInt(peakIdx) * VALUE_BYTES;
98
- return slice32(massifBytes, off, "peak-stack");
99
- };
100
-
101
- return { get, massifHeight, massifIndex, firstIndex, lastIndex };
102
- }
46
+ export { openMassifNodeStore };
47
+ export type { MassifNodeStore };
103
48
 
104
49
  export type ParsedCheckpoint = {
105
50
  coseSign1: CoseSign1;
@@ -281,16 +226,6 @@ function cborBytes(value: unknown): Uint8Array {
281
226
  : new Uint8Array(encoded as ArrayLike<number>);
282
227
  }
283
228
 
284
- function slice32(buf: Uint8Array, offset: bigint, label: string): Uint8Array {
285
- if (offset < 0n || offset + 32n > BigInt(buf.byteLength)) {
286
- throw new Error(
287
- `out of range read for ${label}: off=${offset.toString(10)}`,
288
- );
289
- }
290
- const start = Number(offset);
291
- return buf.slice(start, start + 32);
292
- }
293
-
294
229
  /**
295
230
  * Sealed mmr size from a format-v3 checkpoint: tree-size-2 of the consistency
296
231
  * proof (`bstr .cbor [tree-size-1, tree-size-2, paths, right-peaks]`) under
@@ -313,165 +248,3 @@ function sealedSizeFromCheckpoint(
313
248
  }
314
249
  return null;
315
250
  }
316
-
317
- // --- MMR index math (ported from go-merklelog/mmr, mirrors resolve-receipt) ---
318
-
319
- function bitLength(num: bigint): number {
320
- if (num === 0n) return 0;
321
- return num.toString(2).length;
322
- }
323
-
324
- function allOnes(num: bigint): boolean {
325
- return num > 0n && (num & (num + 1n)) === 0n;
326
- }
327
-
328
- function jumpLeftPerfect(pos: bigint): bigint {
329
- const bl = bitLength(pos);
330
- if (bl === 0) return pos;
331
- const msb = 1n << BigInt(bl - 1);
332
- return pos - (msb - 1n);
333
- }
334
-
335
- function posHeight(pos: bigint): number {
336
- let current = pos;
337
- while (!allOnes(current)) {
338
- current = jumpLeftPerfect(current);
339
- }
340
- return bitLength(current) - 1;
341
- }
342
-
343
- function indexHeight(i: bigint): number {
344
- return posHeight(i + 1n);
345
- }
346
-
347
- function inclusionProof(
348
- getNode: (i: bigint) => Uint8Array,
349
- mmrLastIndex: bigint,
350
- i: bigint,
351
- ): Uint8Array[] {
352
- if (i > mmrLastIndex) {
353
- throw new Error("index out of range");
354
- }
355
- let g = BigInt(indexHeight(i));
356
- const proof: Uint8Array[] = [];
357
- while (true) {
358
- const siblingOffset = 2n << g;
359
- let iSibling: bigint;
360
- if (BigInt(indexHeight(i + 1n)) > g) {
361
- iSibling = i - siblingOffset + 1n;
362
- i += 1n;
363
- } else {
364
- iSibling = i + siblingOffset - 1n;
365
- i += siblingOffset;
366
- }
367
- if (iSibling > mmrLastIndex) {
368
- return proof;
369
- }
370
- proof.push(getNode(iSibling));
371
- g += 1n;
372
- }
373
- }
374
-
375
- function peaksBitmap(mmrSize: bigint): bigint {
376
- if (mmrSize === 0n) return 0n;
377
- let pos = mmrSize;
378
- let peakSize = (1n << BigInt(bitLength(mmrSize))) - 1n;
379
- let peakMap = 0n;
380
- while (peakSize > 0n) {
381
- peakMap <<= 1n;
382
- if (pos >= peakSize) {
383
- pos -= peakSize;
384
- peakMap |= 1n;
385
- }
386
- peakSize >>= 1n;
387
- }
388
- return peakMap;
389
- }
390
-
391
- function popcount64(x: bigint): number {
392
- let count = 0;
393
- let v = x;
394
- while (v > 0n) {
395
- if ((v & 1n) === 1n) count += 1;
396
- v >>= 1n;
397
- }
398
- return count;
399
- }
400
-
401
- function peakIndexForLeafProof(mmrSize: bigint, proofLen: number): number {
402
- const leafCount = peaksBitmap(mmrSize);
403
- const peaksMask = (1n << BigInt(proofLen + 1)) - 1n;
404
- return popcount64(leafCount) - popcount64(leafCount & peaksMask);
405
- }
406
-
407
- function topPeak(i: bigint): bigint {
408
- const bl = bitLength(i + 2n);
409
- return (1n << BigInt(bl - 1)) - 2n;
410
- }
411
-
412
- /** MMR indices of the peaks of the tree ending at `mmrIndex` (inclusive). */
413
- export function peakMMRIndexes(mmrIndex: bigint): bigint[] {
414
- let mmrSize = mmrIndex + 1n;
415
- if (posHeight(mmrSize + 1n) > posHeight(mmrSize)) {
416
- return [];
417
- }
418
- let peak = 0n;
419
- const out: bigint[] = [];
420
- while (mmrSize !== 0n) {
421
- const peakSize = topPeak(mmrSize - 1n) + 1n;
422
- peak = peak + peakSize;
423
- out.push(peak - 1n);
424
- mmrSize -= peakSize;
425
- }
426
- return out;
427
- }
428
-
429
- function peakStackMapForMassif(
430
- massifHeight: number,
431
- firstIndex: bigint,
432
- ): Map<bigint, number> {
433
- const map = new Map<bigint, number>();
434
- const iPeaks = peakMMRIndexes(firstIndex);
435
- for (let i = 0; i < iPeaks.length; i++) {
436
- const ip = iPeaks[i]!;
437
- if (indexHeight(ip) < massifHeight - 1) {
438
- continue;
439
- }
440
- map.set(ip, i);
441
- }
442
- return map;
443
- }
444
-
445
- function mmrIndexFromLeafIndex(leafIndex: bigint): bigint {
446
- let sum = 0n;
447
- let current = leafIndex;
448
- while (current > 0n) {
449
- const h = BigInt(bitLength(current));
450
- sum += (1n << h) - 1n;
451
- const half = 1n << (h - 1n);
452
- current -= half;
453
- }
454
- return sum;
455
- }
456
-
457
- function firstMMRSize(mmrIndex: bigint): bigint {
458
- let i = mmrIndex;
459
- let h0 = indexHeight(i);
460
- let h1 = indexHeight(i + 1n);
461
- while (h0 < h1) {
462
- i += 1n;
463
- h0 = h1;
464
- h1 = indexHeight(i + 1n);
465
- }
466
- return i + 1n;
467
- }
468
-
469
- function massifIndexFromMMRIndex(
470
- massifHeight: number,
471
- mmrIndex: bigint,
472
- ): bigint {
473
- const size = firstMMRSize(mmrIndex);
474
- const leafIndex = peaksBitmap(size) - 1n;
475
- const massifMaxLeaves = 1n << BigInt(massifHeight - 1);
476
- return leafIndex / massifMaxLeaves;
477
- }
package/src/index.ts CHANGED
@@ -17,8 +17,12 @@ export {
17
17
  computeAccumulatorPeak,
18
18
  openMassifNodeStore,
19
19
  parseCheckpoint,
20
- peakMMRIndexes,
21
20
  } from "./build-receipt-offline.js";
21
+ /**
22
+ * MMR proof math now lives in @forestrie/merklelog (plan-2607-15 §4 hoist);
23
+ * re-exported here to preserve the receipt-verify public surface.
24
+ */
25
+ export { peakMMRIndexes } from "@forestrie/merklelog";
22
26
  export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
23
27
  export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
24
28
  export { decodeForestrieGrantCose } from "./decode-forestrie-grant-cose.js";