@forestrie/merklelog 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.
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@ export type { MassifLeafIndex, LocatedLeaf } from "./massifs/leafindex.js";
19
19
  export { mmrIndex, massifFirstLeaf, leafMinusSpurSum } from "./mmr/index.js";
20
20
  export { heightIndex, height, mmrIndexFromLeafIndex, leafIndex, mmrPosition, mmrSizeFromHeightIndex, leafCount, leafCountFromHeightIndex, } from "./mmr/math.js";
21
21
  export { bagPeaks, calculateRoot, verifyInclusion, verifyConsistency, consistentRoots, consistentRootsForSizes, indexConsistencyProof, type ConsistencyProof, } from "./mmr/algorithms.js";
22
- export { ConsistencyShapeError, IncompleteTreeSize, ConsistencyPeakCountMismatch, ConsistencyPathLengthMismatch, ConsistencyRootMismatch, SizeMustIncrease, } from "./mmr/errors.js";
22
+ export { ConsistencyShapeError, IncompleteTreeSize, ConsistencyPeakCountMismatch, ConsistencyPathLengthMismatch, ConsistencyPathMalformed, ConsistencyRootMismatch, SizeMustIncrease, SizeNotUint64, } from "./mmr/errors.js";
23
23
  export { createSyncHasher } from "./mmr/algorithms-sync.js";
24
24
  export type { Proof, Peak } from "./mmr/types.js";
25
25
  export type { Hasher } from "./mmr/types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EACV,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,SAAS,EACT,KAAK,EACL,KAAK,EACL,OAAO,EACP,wBAAwB,EACxB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,6BAA6B,EAC7B,6BAA6B,EAC7B,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAGvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChF,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EACL,WAAW,EACX,MAAM,EACN,qBAAqB,EACrB,SAAS,EACT,WAAW,EACX,sBAAsB,EACtB,SAAS,EACT,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,QAAQ,EACR,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,4BAA4B,EAC5B,6BAA6B,EAC7B,uBAAuB,EACvB,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,uBAAuB,EACvB,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAC7C,YAAY,EACV,WAAW,EACX,aAAa,EACb,kBAAkB,EAClB,SAAS,GACV,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EACL,SAAS,EACT,KAAK,EACL,KAAK,EACL,OAAO,EACP,wBAAwB,EACxB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,6BAA6B,EAC7B,6BAA6B,EAC7B,oBAAoB,EACpB,uBAAuB,EACvB,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,GACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAGvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG9D,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChF,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAG3E,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC7E,OAAO,EACL,WAAW,EACX,MAAM,EACN,qBAAqB,EACrB,SAAS,EACT,WAAW,EACX,sBAAsB,EACtB,SAAS,EACT,wBAAwB,GACzB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,QAAQ,EACR,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,qBAAqB,EACrB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EACL,qBAAqB,EACrB,kBAAkB,EAClB,4BAA4B,EAC5B,6BAA6B,EAC7B,wBAAwB,EACxB,uBAAuB,EACvB,gBAAgB,EAChB,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAClD,YAAY,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAG7C,OAAO,EACL,cAAc,EACd,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,EACX,YAAY,EACZ,uBAAuB,EACvB,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -23,7 +23,7 @@ export { heightIndex, height, mmrIndexFromLeafIndex, leafIndex, mmrPosition, mmr
23
23
  export { bagPeaks, calculateRoot, verifyInclusion, verifyConsistency, consistentRoots, consistentRootsForSizes, indexConsistencyProof, } from "./mmr/algorithms.js";
24
24
  // Typed proof-shape rejections raised by consistentRootsForSizes (names
25
25
  // mirror the Solidity IUnivocityErrors members) — plan-2609-10 s4.3, FOR-568.
26
- export { ConsistencyShapeError, IncompleteTreeSize, ConsistencyPeakCountMismatch, ConsistencyPathLengthMismatch, ConsistencyRootMismatch, SizeMustIncrease, } from "./mmr/errors.js";
26
+ export { ConsistencyShapeError, IncompleteTreeSize, ConsistencyPeakCountMismatch, ConsistencyPathLengthMismatch, ConsistencyPathMalformed, ConsistencyRootMismatch, SizeMustIncrease, SizeNotUint64, } from "./mmr/errors.js";
27
27
  export { createSyncHasher } from "./mmr/algorithms-sync.js";
28
28
  // MMR proof building (pure; store-agnostic) — plan-2607-15 §4 (go: mmr/proof.go, peaks.go)
29
29
  export { inclusionProof, inclusionProofPath, inclusionProofLocalExtend, peakMMRIndexes, peaksBitmap, peakIndexForLeafProof, mmrSizeForLeafCount, indexHeight, firstMMRSize, massifIndexFromMMRIndex, peakStackMap, } from "./mmr/proof.js";
@@ -129,10 +129,13 @@ export declare function consistentRoots(hasher: Hasher, ifrom: bigint, accumulat
129
129
  * any), and `expectedRight`, the number of MMR(sizeTo) peaks the prover must
130
130
  * supply as right peaks. `roots` followed by those right peaks is the
131
131
  * accumulator of MMR(sizeTo).
132
+ * @throws {SizeNotUint64} if either size is outside `[0, 2^64 - 1]`
132
133
  * @throws {SizeMustIncrease} if `sizeTo <= sizeFrom`
133
134
  * @throws {IncompleteTreeSize} if `sizeTo` is not a complete MMR size
134
135
  * @throws {ConsistencyPeakCountMismatch} if `accumulatorFrom` or `paths` does
135
136
  * not have one entry per origin peak
137
+ * @throws {ConsistencyPathMalformed} if `paths` is not a dense array of
138
+ * `Uint8Array[]`
136
139
  * @throws {ConsistencyPathLengthMismatch} if a path length differs from the
137
140
  * length the two sizes imply
138
141
  * @throws {ConsistencyRootMismatch} if two paths under one target peak produce
@@ -1 +1 @@
1
- {"version":3,"file":"algorithms.d.ts","sourceRoot":"","sources":["../../src/mmr/algorithms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAOL,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AA4BpB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EAAE,GAClB,OAAO,CAAC,UAAU,CAAC,CAwBrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAkCrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,UAAU,EAAE,EAC7B,KAAK,EAAE,UAAU,EAAE,EAAE,GACpB,OAAO,CAAC,UAAU,EAAE,CAAC,CA0BvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,UAAU,EAAE,EAC7B,KAAK,EAAE,UAAU,EAAE,EAAE,GACpB,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CA+EzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,UAAU,EAAE,EACvB,KAAK,EAAE,UAAU,EAAE,EAAE,EACrB,OAAO,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC,CAqBrD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,gBAAgB,CAMlB"}
1
+ {"version":3,"file":"algorithms.d.ts","sourceRoot":"","sources":["../../src/mmr/algorithms.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAOL,KAAK,UAAU,EAChB,MAAM,YAAY,CAAC;AAsCpB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EAAE,GAClB,OAAO,CAAC,UAAU,CAAC,CAwBrB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,KAAK,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAkCrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,UAAU,EACpB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,OAAO,CAAC,CAUlB;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,eAAe,EAAE,UAAU,EAAE,EAC7B,KAAK,EAAE,UAAU,EAAE,EAAE,GACpB,OAAO,CAAC,UAAU,EAAE,CAAC,CA0BvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,eAAe,EAAE,UAAU,EAAE,EAC7B,KAAK,EAAE,UAAU,EAAE,EAAE,GACpB,OAAO,CAAC;IAAE,KAAK,EAAE,UAAU,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAAC,CA2FzD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,UAAU,EAAE,EACvB,KAAK,EAAE,UAAU,EAAE,EAAE,EACrB,OAAO,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC,CAqBrD;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,gBAAgB,CAMlB"}
@@ -11,10 +11,17 @@
11
11
  * https://raw.githubusercontent.com/robinbryce/draft-bryce-cose-receipts-mmr-profile/refs/heads/main/draft-bryce-cose-receipts-mmr-profile.md
12
12
  */
13
13
  import { inclusionProof, peakMMRIndexes, bitLength, popcount, peaksBitmap, mmrSizeForLeafCount, } from "./proof.js";
14
- import { ConsistencyPathLengthMismatch, ConsistencyPeakCountMismatch, ConsistencyRootMismatch, IncompleteTreeSize, SizeMustIncrease, } from "./errors.js";
14
+ import { ConsistencyPathLengthMismatch, ConsistencyPathMalformed, ConsistencyPeakCountMismatch, ConsistencyRootMismatch, IncompleteTreeSize, SizeMustIncrease, SizeNotUint64, } from "./errors.js";
15
15
  import { Uint64 } from "../uint64/index.js";
16
16
  import { heightIndex } from "./math.js";
17
17
  import { arraysEqual } from "../utils/arrays.js";
18
+ /**
19
+ * Upper bound of the uint64 domain the MMR profile's sizes live in: Solidity
20
+ * and Go take every size as `uint64`, and a CBOR unsigned integer caps here
21
+ * too. `bigint` carries no such bound, so {@link consistentRootsForSizes}
22
+ * states it.
23
+ */
24
+ const MAX_UINT64 = (1n << 64n) - 1n;
18
25
  /**
19
26
  * Encodes a Uint64 as 8 bytes big-endian.
20
27
  *
@@ -208,16 +215,27 @@ export async function consistentRoots(hasher, ifrom, accumulatorFrom, paths) {
208
215
  * any), and `expectedRight`, the number of MMR(sizeTo) peaks the prover must
209
216
  * supply as right peaks. `roots` followed by those right peaks is the
210
217
  * accumulator of MMR(sizeTo).
218
+ * @throws {SizeNotUint64} if either size is outside `[0, 2^64 - 1]`
211
219
  * @throws {SizeMustIncrease} if `sizeTo <= sizeFrom`
212
220
  * @throws {IncompleteTreeSize} if `sizeTo` is not a complete MMR size
213
221
  * @throws {ConsistencyPeakCountMismatch} if `accumulatorFrom` or `paths` does
214
222
  * not have one entry per origin peak
223
+ * @throws {ConsistencyPathMalformed} if `paths` is not a dense array of
224
+ * `Uint8Array[]`
215
225
  * @throws {ConsistencyPathLengthMismatch} if a path length differs from the
216
226
  * length the two sizes imply
217
227
  * @throws {ConsistencyRootMismatch} if two paths under one target peak produce
218
228
  * different roots
219
229
  */
220
230
  export async function consistentRootsForSizes(hasher, sizeFrom, sizeTo, accumulatorFrom, paths) {
231
+ // The sizes are uint64 in Solidity and Go; `bigint` carries no such bound,
232
+ // so it is stated here before any arithmetic reads them.
233
+ if (sizeFrom < 0n || sizeFrom > MAX_UINT64) {
234
+ throw new SizeNotUint64("sizeFrom", sizeFrom);
235
+ }
236
+ if (sizeTo < 0n || sizeTo > MAX_UINT64) {
237
+ throw new SizeNotUint64("sizeTo", sizeTo);
238
+ }
221
239
  if (sizeTo <= sizeFrom) {
222
240
  throw new SizeMustIncrease(sizeFrom, sizeTo);
223
241
  }
@@ -234,9 +252,13 @@ export async function consistentRootsForSizes(hasher, sizeFrom, sizeTo, accumula
234
252
  if (accumulatorFrom.length !== n) {
235
253
  throw new ConsistencyPeakCountMismatch(n, accumulatorFrom.length);
236
254
  }
255
+ if (!Array.isArray(paths)) {
256
+ throw new ConsistencyPathMalformed(`not an array (got ${typeName(paths)})`);
257
+ }
237
258
  if (paths.length !== n) {
238
259
  throw new ConsistencyPeakCountMismatch(n, paths.length);
239
260
  }
261
+ assertDensePaths(paths);
240
262
  const nto = popcount(to);
241
263
  if (n === 0) {
242
264
  return { roots: [], expectedRight: nto };
@@ -364,3 +386,34 @@ export function indexConsistencyProof(get, mmrIndexA, mmrIndexB) {
364
386
  }
365
387
  return { mmrSizeA: mmrIndexA + 1n, mmrSizeB: mmrIndexB + 1n, paths };
366
388
  }
389
+ /** Name the value found where a path or a path element was required. */
390
+ function typeName(value) {
391
+ if (value === null)
392
+ return "null";
393
+ if (Array.isArray(value))
394
+ return "an array";
395
+ return typeof value;
396
+ }
397
+ /**
398
+ * Require `paths` to be a DENSE array of `Uint8Array[]`: no holes, and every
399
+ * element an array of `Uint8Array`. A hole and an out-of-type entry both
400
+ * survive the peak COUNT check and would otherwise reach the hashing loop,
401
+ * where they surface as a bare `TypeError` outside the
402
+ * `ConsistencyShapeError` family (review finding C6).
403
+ */
404
+ function assertDensePaths(paths) {
405
+ for (let i = 0; i < paths.length; i++) {
406
+ if (!(i in paths)) {
407
+ throw new ConsistencyPathMalformed("missing (the array is sparse)", i);
408
+ }
409
+ const path = paths[i];
410
+ if (!Array.isArray(path)) {
411
+ throw new ConsistencyPathMalformed(`not an array (got ${typeName(path)})`, i);
412
+ }
413
+ for (let j = 0; j < path.length; j++) {
414
+ if (!(j in path) || !(path[j] instanceof Uint8Array)) {
415
+ throw new ConsistencyPathMalformed(`element ${j} is not a Uint8Array (got ${typeName(path[j])})`, i);
416
+ }
417
+ }
418
+ }
419
+ }
@@ -66,4 +66,35 @@ export declare class SizeMustIncrease extends ConsistencyShapeError {
66
66
  readonly sizeTo: bigint;
67
67
  constructor(sizeFrom: bigint, sizeTo: bigint);
68
68
  }
69
+ /**
70
+ * A size is outside the uint64 domain the MMR profile's sizes live in.
71
+ *
72
+ * Solidity and Go take these sizes as `uint64`, so the domain is part of
73
+ * their signatures; TypeScript's `bigint` has no bound of its own. Without
74
+ * this check a size at or above 2^64 folds here and nowhere else: the
75
+ * position prefix `hash_pospair64` writes is truncated to eight bytes, so
76
+ * the hashes computed above the bound are not the ones the other two
77
+ * implementations would compute, and a size that large is not one a CBOR
78
+ * unsigned integer can carry or the contract can store either.
79
+ */
80
+ export declare class SizeNotUint64 extends ConsistencyShapeError {
81
+ readonly which: "sizeFrom" | "sizeTo";
82
+ readonly size: bigint;
83
+ constructor(which: "sizeFrom" | "sizeTo", size: bigint);
84
+ }
85
+ /**
86
+ * `paths` is not a dense array of `Uint8Array[]`.
87
+ *
88
+ * A hole (a sparse array), a `null`/`undefined` entry, a path that is not an
89
+ * array, or a path element that is not a `Uint8Array` all survive the peak
90
+ * COUNT check and then surface as a bare `TypeError` out of the fold, which
91
+ * escapes the {@link ConsistencyShapeError} family callers are told to
92
+ * switch on. The shape of the proof material is a proof-shape condition like
93
+ * any other, so it is reported as one.
94
+ */
95
+ export declare class ConsistencyPathMalformed extends ConsistencyShapeError {
96
+ readonly detail: string;
97
+ readonly index?: number | undefined;
98
+ constructor(detail: string, index?: number | undefined);
99
+ }
69
100
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/mmr/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,8DAA8D;AAC9D,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,qBAAqB;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIlC;AAED;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,qBAAqB;IAEnE,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAK1B;AAED;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,qBAAqB;IAEpE,QAAQ,CAAC,KAAK,EAAE,MAAM;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAFd,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAK1B;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,qBAAqB;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAAb,KAAK,EAAE,MAAM;CAInC;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,qBAAqB;IAEvD,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAK1B"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/mmr/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,8DAA8D;AAC9D,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;GAOG;AACH,qBAAa,kBAAmB,SAAQ,qBAAqB;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIlC;AAED;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,qBAAqB;IAEnE,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAK1B;AAED;;;GAGG;AACH,qBAAa,6BAA8B,SAAQ,qBAAqB;IAEpE,QAAQ,CAAC,KAAK,EAAE,MAAM;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;gBAFd,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAK1B;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,qBAAqB;IACpD,QAAQ,CAAC,KAAK,EAAE,MAAM;gBAAb,KAAK,EAAE,MAAM;CAInC;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,qBAAqB;IAEvD,QAAQ,CAAC,QAAQ,EAAE,MAAM;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM;gBADd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM;CAK1B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,aAAc,SAAQ,qBAAqB;IAEpD,QAAQ,CAAC,KAAK,EAAE,UAAU,GAAG,QAAQ;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM;gBADZ,KAAK,EAAE,UAAU,GAAG,QAAQ,EAC5B,IAAI,EAAE,MAAM;CAKxB;AAED;;;;;;;;;GASG;AACH,qBAAa,wBAAyB,SAAQ,qBAAqB;IAE/D,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;gBADd,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,YAAA;CAO1B"}
@@ -93,3 +93,44 @@ export class SizeMustIncrease extends ConsistencyShapeError {
93
93
  this.name = "SizeMustIncrease";
94
94
  }
95
95
  }
96
+ /**
97
+ * A size is outside the uint64 domain the MMR profile's sizes live in.
98
+ *
99
+ * Solidity and Go take these sizes as `uint64`, so the domain is part of
100
+ * their signatures; TypeScript's `bigint` has no bound of its own. Without
101
+ * this check a size at or above 2^64 folds here and nowhere else: the
102
+ * position prefix `hash_pospair64` writes is truncated to eight bytes, so
103
+ * the hashes computed above the bound are not the ones the other two
104
+ * implementations would compute, and a size that large is not one a CBOR
105
+ * unsigned integer can carry or the contract can store either.
106
+ */
107
+ export class SizeNotUint64 extends ConsistencyShapeError {
108
+ which;
109
+ size;
110
+ constructor(which, size) {
111
+ super(`${which} (${size}) is outside the uint64 range [0, 2^64 - 1]`);
112
+ this.which = which;
113
+ this.size = size;
114
+ this.name = "SizeNotUint64";
115
+ }
116
+ }
117
+ /**
118
+ * `paths` is not a dense array of `Uint8Array[]`.
119
+ *
120
+ * A hole (a sparse array), a `null`/`undefined` entry, a path that is not an
121
+ * array, or a path element that is not a `Uint8Array` all survive the peak
122
+ * COUNT check and then surface as a bare `TypeError` out of the fold, which
123
+ * escapes the {@link ConsistencyShapeError} family callers are told to
124
+ * switch on. The shape of the proof material is a proof-shape condition like
125
+ * any other, so it is reported as one.
126
+ */
127
+ export class ConsistencyPathMalformed extends ConsistencyShapeError {
128
+ detail;
129
+ index;
130
+ constructor(detail, index) {
131
+ super(index === undefined ? `paths: ${detail}` : `path ${index}: ${detail}`);
132
+ this.detail = detail;
133
+ this.index = index;
134
+ this.name = "ConsistencyPathMalformed";
135
+ }
136
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestrie/merklelog",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "MMR (merkle mountain range) proofs and massif storage format for Forestrie transparency logs",
package/src/index.ts CHANGED
@@ -82,8 +82,10 @@ export {
82
82
  IncompleteTreeSize,
83
83
  ConsistencyPeakCountMismatch,
84
84
  ConsistencyPathLengthMismatch,
85
+ ConsistencyPathMalformed,
85
86
  ConsistencyRootMismatch,
86
87
  SizeMustIncrease,
88
+ SizeNotUint64,
87
89
  } from "./mmr/errors.js";
88
90
  export { createSyncHasher } from "./mmr/algorithms-sync.js";
89
91
  export type { Proof, Peak } from "./mmr/types.js";
@@ -23,15 +23,25 @@ import {
23
23
  } from "./proof.js";
24
24
  import {
25
25
  ConsistencyPathLengthMismatch,
26
+ ConsistencyPathMalformed,
26
27
  ConsistencyPeakCountMismatch,
27
28
  ConsistencyRootMismatch,
28
29
  IncompleteTreeSize,
29
30
  SizeMustIncrease,
31
+ SizeNotUint64,
30
32
  } from "./errors.js";
31
33
  import { Uint64 } from "../uint64/index.js";
32
34
  import { heightIndex } from "./math.js";
33
35
  import { arraysEqual } from "../utils/arrays.js";
34
36
 
37
+ /**
38
+ * Upper bound of the uint64 domain the MMR profile's sizes live in: Solidity
39
+ * and Go take every size as `uint64`, and a CBOR unsigned integer caps here
40
+ * too. `bigint` carries no such bound, so {@link consistentRootsForSizes}
41
+ * states it.
42
+ */
43
+ const MAX_UINT64 = (1n << 64n) - 1n;
44
+
35
45
  /**
36
46
  * Encodes a Uint64 as 8 bytes big-endian.
37
47
  *
@@ -282,10 +292,13 @@ export async function consistentRoots(
282
292
  * any), and `expectedRight`, the number of MMR(sizeTo) peaks the prover must
283
293
  * supply as right peaks. `roots` followed by those right peaks is the
284
294
  * accumulator of MMR(sizeTo).
295
+ * @throws {SizeNotUint64} if either size is outside `[0, 2^64 - 1]`
285
296
  * @throws {SizeMustIncrease} if `sizeTo <= sizeFrom`
286
297
  * @throws {IncompleteTreeSize} if `sizeTo` is not a complete MMR size
287
298
  * @throws {ConsistencyPeakCountMismatch} if `accumulatorFrom` or `paths` does
288
299
  * not have one entry per origin peak
300
+ * @throws {ConsistencyPathMalformed} if `paths` is not a dense array of
301
+ * `Uint8Array[]`
289
302
  * @throws {ConsistencyPathLengthMismatch} if a path length differs from the
290
303
  * length the two sizes imply
291
304
  * @throws {ConsistencyRootMismatch} if two paths under one target peak produce
@@ -298,6 +311,14 @@ export async function consistentRootsForSizes(
298
311
  accumulatorFrom: Uint8Array[],
299
312
  paths: Uint8Array[][],
300
313
  ): Promise<{ roots: Uint8Array[]; expectedRight: number }> {
314
+ // The sizes are uint64 in Solidity and Go; `bigint` carries no such bound,
315
+ // so it is stated here before any arithmetic reads them.
316
+ if (sizeFrom < 0n || sizeFrom > MAX_UINT64) {
317
+ throw new SizeNotUint64("sizeFrom", sizeFrom);
318
+ }
319
+ if (sizeTo < 0n || sizeTo > MAX_UINT64) {
320
+ throw new SizeNotUint64("sizeTo", sizeTo);
321
+ }
301
322
  if (sizeTo <= sizeFrom) {
302
323
  throw new SizeMustIncrease(sizeFrom, sizeTo);
303
324
  }
@@ -314,9 +335,13 @@ export async function consistentRootsForSizes(
314
335
  if (accumulatorFrom.length !== n) {
315
336
  throw new ConsistencyPeakCountMismatch(n, accumulatorFrom.length);
316
337
  }
338
+ if (!Array.isArray(paths)) {
339
+ throw new ConsistencyPathMalformed(`not an array (got ${typeName(paths)})`);
340
+ }
317
341
  if (paths.length !== n) {
318
342
  throw new ConsistencyPeakCountMismatch(n, paths.length);
319
343
  }
344
+ assertDensePaths(paths);
320
345
  const nto = popcount(to);
321
346
  if (n === 0) {
322
347
  return { roots: [], expectedRight: nto };
@@ -469,3 +494,40 @@ export function indexConsistencyProof(
469
494
  }
470
495
  return { mmrSizeA: mmrIndexA + 1n, mmrSizeB: mmrIndexB + 1n, paths };
471
496
  }
497
+
498
+ /** Name the value found where a path or a path element was required. */
499
+ function typeName(value: unknown): string {
500
+ if (value === null) return "null";
501
+ if (Array.isArray(value)) return "an array";
502
+ return typeof value;
503
+ }
504
+
505
+ /**
506
+ * Require `paths` to be a DENSE array of `Uint8Array[]`: no holes, and every
507
+ * element an array of `Uint8Array`. A hole and an out-of-type entry both
508
+ * survive the peak COUNT check and would otherwise reach the hashing loop,
509
+ * where they surface as a bare `TypeError` outside the
510
+ * `ConsistencyShapeError` family (review finding C6).
511
+ */
512
+ function assertDensePaths(paths: Uint8Array[][]): void {
513
+ for (let i = 0; i < paths.length; i++) {
514
+ if (!(i in paths)) {
515
+ throw new ConsistencyPathMalformed("missing (the array is sparse)", i);
516
+ }
517
+ const path = paths[i] as unknown;
518
+ if (!Array.isArray(path)) {
519
+ throw new ConsistencyPathMalformed(
520
+ `not an array (got ${typeName(path)})`,
521
+ i,
522
+ );
523
+ }
524
+ for (let j = 0; j < path.length; j++) {
525
+ if (!(j in path) || !((path[j] as unknown) instanceof Uint8Array)) {
526
+ throw new ConsistencyPathMalformed(
527
+ `element ${j} is not a Uint8Array (got ${typeName(path[j])})`,
528
+ i,
529
+ );
530
+ }
531
+ }
532
+ }
533
+ }
package/src/mmr/errors.ts CHANGED
@@ -91,3 +91,46 @@ export class SizeMustIncrease extends ConsistencyShapeError {
91
91
  this.name = "SizeMustIncrease";
92
92
  }
93
93
  }
94
+
95
+ /**
96
+ * A size is outside the uint64 domain the MMR profile's sizes live in.
97
+ *
98
+ * Solidity and Go take these sizes as `uint64`, so the domain is part of
99
+ * their signatures; TypeScript's `bigint` has no bound of its own. Without
100
+ * this check a size at or above 2^64 folds here and nowhere else: the
101
+ * position prefix `hash_pospair64` writes is truncated to eight bytes, so
102
+ * the hashes computed above the bound are not the ones the other two
103
+ * implementations would compute, and a size that large is not one a CBOR
104
+ * unsigned integer can carry or the contract can store either.
105
+ */
106
+ export class SizeNotUint64 extends ConsistencyShapeError {
107
+ constructor(
108
+ readonly which: "sizeFrom" | "sizeTo",
109
+ readonly size: bigint,
110
+ ) {
111
+ super(`${which} (${size}) is outside the uint64 range [0, 2^64 - 1]`);
112
+ this.name = "SizeNotUint64";
113
+ }
114
+ }
115
+
116
+ /**
117
+ * `paths` is not a dense array of `Uint8Array[]`.
118
+ *
119
+ * A hole (a sparse array), a `null`/`undefined` entry, a path that is not an
120
+ * array, or a path element that is not a `Uint8Array` all survive the peak
121
+ * COUNT check and then surface as a bare `TypeError` out of the fold, which
122
+ * escapes the {@link ConsistencyShapeError} family callers are told to
123
+ * switch on. The shape of the proof material is a proof-shape condition like
124
+ * any other, so it is reported as one.
125
+ */
126
+ export class ConsistencyPathMalformed extends ConsistencyShapeError {
127
+ constructor(
128
+ readonly detail: string,
129
+ readonly index?: number,
130
+ ) {
131
+ super(
132
+ index === undefined ? `paths: ${detail}` : `path ${index}: ${detail}`,
133
+ );
134
+ this.name = "ConsistencyPathMalformed";
135
+ }
136
+ }