@forestrie/merklelog 0.4.0 → 0.6.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 +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/massifs/v2storagepaths.d.ts +43 -3
- package/dist/massifs/v2storagepaths.d.ts.map +1 -1
- package/dist/massifs/v2storagepaths.js +104 -17
- package/dist/mmr/algorithms.d.ts +3 -0
- package/dist/mmr/algorithms.d.ts.map +1 -1
- package/dist/mmr/algorithms.js +54 -1
- package/dist/mmr/errors.d.ts +31 -0
- package/dist/mmr/errors.d.ts.map +1 -1
- package/dist/mmr/errors.js +41 -0
- package/package.json +1 -1
- package/src/index.ts +10 -1
- package/src/massifs/v2storagepaths.ts +133 -18
- package/src/mmr/algorithms.ts +62 -0
- package/src/mmr/errors.ts +43 -0
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { Massif } from "./massifs/massif.js";
|
|
|
11
11
|
export type { MassifStart, LeafComponent, LeafEnumeratorSpec, LeafEntry, } from "./massifs/types.js";
|
|
12
12
|
export { MassifStartFmt } from "./massifs/types.js";
|
|
13
13
|
export { LogFormat, Urkle, Bloom, IndexV2, leafCountForMassifHeight, leafTableBytes, nodeCountMax, nodeStoreBytes, bloomMBits, bloomBitsetBytes, bloomRegionBytes, indexDataBytesV2, peakStackEnd, massifLogEntries, urkleLeafTableStartFieldIndex, urkleLeafTableStartByteOffset, createLeafEnumerator, leafComponentByteOffset, leafComponentSize, computeLastMMRIndex, isMassifFull, } from "./massifs/types.js";
|
|
14
|
-
export { parseV2StorageObjectPath } from "./massifs/v2storagepaths.js";
|
|
14
|
+
export { formatMassifObjectIndex, MASSIF_OBJECT_INDEX_DIGITS, MAX_MASSIF_INDEX, parseV2StorageObjectPath, v2CheckpointObjectKey, v2MassifObjectKey, } from "./massifs/v2storagepaths.js";
|
|
15
15
|
export { openMassifNodeStore } from "./massifs/nodestore.js";
|
|
16
16
|
export type { MassifNodeStore } from "./massifs/nodestore.js";
|
|
17
17
|
export { openMassifLeafIndex, MissingIndexError } from "./massifs/leafindex.js";
|
|
@@ -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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,EACL,uBAAuB,EACvB,0BAA0B,EAC1B,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB,EACrB,iBAAiB,GAClB,MAAM,6BAA6B,CAAC;AAGrC,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
|
@@ -12,7 +12,7 @@ export { Uint64 } from "./uint64/index.js";
|
|
|
12
12
|
export { Massif } from "./massifs/massif.js";
|
|
13
13
|
export { MassifStartFmt } from "./massifs/types.js";
|
|
14
14
|
export { LogFormat, Urkle, Bloom, IndexV2, leafCountForMassifHeight, leafTableBytes, nodeCountMax, nodeStoreBytes, bloomMBits, bloomBitsetBytes, bloomRegionBytes, indexDataBytesV2, peakStackEnd, massifLogEntries, urkleLeafTableStartFieldIndex, urkleLeafTableStartByteOffset, createLeafEnumerator, leafComponentByteOffset, leafComponentSize, computeLastMMRIndex, isMassifFull, } from "./massifs/types.js";
|
|
15
|
-
export { parseV2StorageObjectPath } from "./massifs/v2storagepaths.js";
|
|
15
|
+
export { formatMassifObjectIndex, MASSIF_OBJECT_INDEX_DIGITS, MAX_MASSIF_INDEX, parseV2StorageObjectPath, v2CheckpointObjectKey, v2MassifObjectKey, } from "./massifs/v2storagepaths.js";
|
|
16
16
|
// Massif blob node access for MMR proof building (plan-2607-15 §4)
|
|
17
17
|
export { openMassifNodeStore } from "./massifs/nodestore.js";
|
|
18
18
|
// Content-hash leaf lookup over the massif index region (FOR-373)
|
|
@@ -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";
|
|
@@ -1,17 +1,57 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Merklelog v2 R2 object layout:
|
|
3
3
|
*
|
|
4
4
|
* - v2/merklelog/massifs/{massifHeight}/{logId}/{massifIndex}.log
|
|
5
5
|
* - v2/merklelog/checkpoints/{massifHeight}/{logId}/{massifIndex}.sth
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* `{massifIndex}` is a zero-padded, 16-digit DECIMAL integer. The Go writer
|
|
8
|
+
* owns this convention: go-merklelog
|
|
9
|
+
* [massifs/storage/const.go](https://github.com/forestrie/go-merklelog/blob/main/massifs/storage/const.go)
|
|
10
|
+
* (`V1MMRBlobNameFmt = "%016d.log"`, `V1MMRSignedTreeHeadBlobNameFmt =
|
|
11
|
+
* "%016d.sth"`) and its reader
|
|
12
|
+
* [objectindexfrompath.go](https://github.com/forestrie/go-merklelog/blob/main/massifs/storage/objectindexfrompath.go)
|
|
13
|
+
* (`strconv.Atoi`). canopy-api builds its R2 keys through the functions
|
|
14
|
+
* here (FOR-621: an earlier reader parsed hex, which agrees with the writer
|
|
15
|
+
* only for indices 0-9). Code outside this package that spells a key by hand
|
|
16
|
+
* (delegation-coordinator `seal-hint.ts`, test fixtures, demos) uses only
|
|
17
|
+
* index 0, which reads the same in any radix; anything that needs another
|
|
18
|
+
* index must use these helpers.
|
|
19
|
+
*/
|
|
20
|
+
/** Width of the massif index segment in an object key. */
|
|
21
|
+
export declare const MASSIF_OBJECT_INDEX_DIGITS = 16;
|
|
22
|
+
/**
|
|
23
|
+
* Largest massif index the Go side can represent. go-merklelog
|
|
24
|
+
* `FmtMassifPath(prefix, massifIndex uint32)` and arbor's publisher
|
|
25
|
+
* (`strconv.ParseUint(base, 10, 32)`) use `uint32`; a larger index would be
|
|
26
|
+
* silently truncated by `ObjectIndexFromPath`, so it is rejected here.
|
|
27
|
+
*/
|
|
28
|
+
export declare const MAX_MASSIF_INDEX = 4294967295n;
|
|
29
|
+
/**
|
|
30
|
+
* Parse an R2 object path to extract log ID, massif height, and massif index.
|
|
31
|
+
*
|
|
32
|
+
* @param path - The object path from the R2 notification
|
|
8
33
|
* @returns Parsed components including logId, massifHeight, and massifIndex
|
|
9
34
|
* @throws Error if the path doesn't match expected format or if parsing fails
|
|
10
35
|
*/
|
|
11
36
|
export declare function parseV2StorageObjectPath(path: string): {
|
|
12
37
|
logId: string;
|
|
13
38
|
massifHeight: number;
|
|
14
|
-
massifIndex:
|
|
39
|
+
massifIndex: bigint;
|
|
15
40
|
type: string;
|
|
16
41
|
};
|
|
42
|
+
/**
|
|
43
|
+
* Build the massif `.log` object key for a log.
|
|
44
|
+
*/
|
|
45
|
+
export declare function v2MassifObjectKey(massifHeight: number, logId: string, massifIndex: bigint | number): string;
|
|
46
|
+
/**
|
|
47
|
+
* Build the signed checkpoint `.sth` object key for a log.
|
|
48
|
+
*/
|
|
49
|
+
export declare function v2CheckpointObjectKey(massifHeight: number, logId: string, massifIndex: bigint | number): string;
|
|
50
|
+
/**
|
|
51
|
+
* Format a massif index as the 16-digit decimal key segment (Go `%016d`).
|
|
52
|
+
*
|
|
53
|
+
* @throws Error if the index is negative, not an integer, not a safe integer
|
|
54
|
+
* (number input), or exceeds {@link MAX_MASSIF_INDEX}.
|
|
55
|
+
*/
|
|
56
|
+
export declare function formatMassifObjectIndex(massifIndex: bigint | number): string;
|
|
17
57
|
//# sourceMappingURL=v2storagepaths.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"v2storagepaths.d.ts","sourceRoot":"","sources":["../../src/massifs/v2storagepaths.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"v2storagepaths.d.ts","sourceRoot":"","sources":["../../src/massifs/v2storagepaths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,0DAA0D;AAC1D,eAAO,MAAM,0BAA0B,KAAK,CAAC;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAiB5C;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM;;;;;EAyDpD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,MAAM,GAC3B,MAAM,CAGR;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,MAAM,GAC3B,MAAM,CAGR;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAW5E"}
|
|
@@ -1,42 +1,84 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Merklelog v2 R2 object layout:
|
|
3
3
|
*
|
|
4
4
|
* - v2/merklelog/massifs/{massifHeight}/{logId}/{massifIndex}.log
|
|
5
5
|
* - v2/merklelog/checkpoints/{massifHeight}/{logId}/{massifIndex}.sth
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* `{massifIndex}` is a zero-padded, 16-digit DECIMAL integer. The Go writer
|
|
8
|
+
* owns this convention: go-merklelog
|
|
9
|
+
* [massifs/storage/const.go](https://github.com/forestrie/go-merklelog/blob/main/massifs/storage/const.go)
|
|
10
|
+
* (`V1MMRBlobNameFmt = "%016d.log"`, `V1MMRSignedTreeHeadBlobNameFmt =
|
|
11
|
+
* "%016d.sth"`) and its reader
|
|
12
|
+
* [objectindexfrompath.go](https://github.com/forestrie/go-merklelog/blob/main/massifs/storage/objectindexfrompath.go)
|
|
13
|
+
* (`strconv.Atoi`). canopy-api builds its R2 keys through the functions
|
|
14
|
+
* here (FOR-621: an earlier reader parsed hex, which agrees with the writer
|
|
15
|
+
* only for indices 0-9). Code outside this package that spells a key by hand
|
|
16
|
+
* (delegation-coordinator `seal-hint.ts`, test fixtures, demos) uses only
|
|
17
|
+
* index 0, which reads the same in any radix; anything that needs another
|
|
18
|
+
* index must use these helpers.
|
|
19
|
+
*/
|
|
20
|
+
/** Width of the massif index segment in an object key. */
|
|
21
|
+
export const MASSIF_OBJECT_INDEX_DIGITS = 16;
|
|
22
|
+
/**
|
|
23
|
+
* Largest massif index the Go side can represent. go-merklelog
|
|
24
|
+
* `FmtMassifPath(prefix, massifIndex uint32)` and arbor's publisher
|
|
25
|
+
* (`strconv.ParseUint(base, 10, 32)`) use `uint32`; a larger index would be
|
|
26
|
+
* silently truncated by `ObjectIndexFromPath`, so it is rejected here.
|
|
27
|
+
*/
|
|
28
|
+
export const MAX_MASSIF_INDEX = 0xffffffffn;
|
|
29
|
+
/**
|
|
30
|
+
* Massif height bounds. Go writes height as `%d` of a `uint8` and arbor's
|
|
31
|
+
* publisher `ParseCheckpointKey` rejects 0, so valid heights are 1..255 with
|
|
32
|
+
* no leading zeros.
|
|
33
|
+
*/
|
|
34
|
+
const MIN_MASSIF_HEIGHT = 1;
|
|
35
|
+
const MAX_MASSIF_HEIGHT = 255;
|
|
36
|
+
const MASSIF_OBJECT_INDEX_RE = /^[0-9]{16}$/;
|
|
37
|
+
const MASSIF_HEIGHT_RE = /^[1-9][0-9]{0,2}$/;
|
|
38
|
+
const OBJECT_EXTENSIONS = {
|
|
39
|
+
massifs: ".log",
|
|
40
|
+
checkpoints: ".sth",
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Parse an R2 object path to extract log ID, massif height, and massif index.
|
|
44
|
+
*
|
|
45
|
+
* @param path - The object path from the R2 notification
|
|
8
46
|
* @returns Parsed components including logId, massifHeight, and massifIndex
|
|
9
47
|
* @throws Error if the path doesn't match expected format or if parsing fails
|
|
10
48
|
*/
|
|
11
49
|
export function parseV2StorageObjectPath(path) {
|
|
12
50
|
const parts = path.split("/");
|
|
13
|
-
if (parts.length
|
|
51
|
+
if (parts.length !== 6 || parts[0] !== "v2" || parts[1] !== "merklelog")
|
|
14
52
|
throw new Error(`Unrecognized path format: ${path}`);
|
|
15
|
-
|
|
16
|
-
// or v2/merklelog/checkpoints/{massifHeight}/{logId}/{index}.sth
|
|
17
|
-
const typePart = parts[2]; // "massifs" or "checkpoints"
|
|
53
|
+
const typePart = parts[2];
|
|
18
54
|
const massifHeightStr = parts[3];
|
|
19
55
|
const logId = parts[4];
|
|
20
56
|
const filename = parts[5];
|
|
21
|
-
|
|
22
|
-
|
|
57
|
+
const expectedExt = Object.hasOwn(OBJECT_EXTENSIONS, typePart)
|
|
58
|
+
? OBJECT_EXTENSIONS[typePart]
|
|
59
|
+
: undefined;
|
|
60
|
+
if (expectedExt === undefined) {
|
|
61
|
+
throw new Error(`Object type must be massifs or checkpoints, got: ${typePart}`);
|
|
62
|
+
}
|
|
63
|
+
if (logId.length === 0) {
|
|
64
|
+
throw new Error(`Log id segment is empty: ${path}`);
|
|
65
|
+
}
|
|
23
66
|
if (!filename.endsWith(expectedExt)) {
|
|
24
67
|
throw new Error(`Expected ${expectedExt} extension for ${typePart}, got: ${filename}`);
|
|
25
68
|
}
|
|
26
|
-
// Parse massif index (hex, 16 digits)
|
|
27
69
|
const massifIndexStr = filename.slice(0, -expectedExt.length);
|
|
28
|
-
if (massifIndexStr
|
|
29
|
-
throw new Error(`Massif index must be 16
|
|
70
|
+
if (!MASSIF_OBJECT_INDEX_RE.test(massifIndexStr)) {
|
|
71
|
+
throw new Error(`Massif index must be 16 decimal digits, got: ${massifIndexStr}`);
|
|
30
72
|
}
|
|
31
|
-
const massifIndex =
|
|
32
|
-
if (
|
|
33
|
-
throw new Error(`
|
|
73
|
+
const massifIndex = BigInt(massifIndexStr);
|
|
74
|
+
if (massifIndex > MAX_MASSIF_INDEX) {
|
|
75
|
+
throw new Error(`Massif index must be 0..${MAX_MASSIF_INDEX}, got ${massifIndex}`);
|
|
34
76
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (!Number.isFinite(massifHeight)) {
|
|
77
|
+
if (!MASSIF_HEIGHT_RE.test(massifHeightStr) ||
|
|
78
|
+
Number(massifHeightStr) > MAX_MASSIF_HEIGHT) {
|
|
38
79
|
throw new Error(`Failed to parse massif height as number: ${massifHeightStr}`);
|
|
39
80
|
}
|
|
81
|
+
const massifHeight = Number(massifHeightStr);
|
|
40
82
|
return {
|
|
41
83
|
logId,
|
|
42
84
|
massifHeight,
|
|
@@ -44,3 +86,48 @@ export function parseV2StorageObjectPath(path) {
|
|
|
44
86
|
type: typePart,
|
|
45
87
|
};
|
|
46
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Build the massif `.log` object key for a log.
|
|
91
|
+
*/
|
|
92
|
+
export function v2MassifObjectKey(massifHeight, logId, massifIndex) {
|
|
93
|
+
assertKeyHeightAndLogId(massifHeight, logId);
|
|
94
|
+
return `v2/merklelog/massifs/${massifHeight}/${logId}/${formatMassifObjectIndex(massifIndex)}.log`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Build the signed checkpoint `.sth` object key for a log.
|
|
98
|
+
*/
|
|
99
|
+
export function v2CheckpointObjectKey(massifHeight, logId, massifIndex) {
|
|
100
|
+
assertKeyHeightAndLogId(massifHeight, logId);
|
|
101
|
+
return `v2/merklelog/checkpoints/${massifHeight}/${logId}/${formatMassifObjectIndex(massifIndex)}.sth`;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Format a massif index as the 16-digit decimal key segment (Go `%016d`).
|
|
105
|
+
*
|
|
106
|
+
* @throws Error if the index is negative, not an integer, not a safe integer
|
|
107
|
+
* (number input), or exceeds {@link MAX_MASSIF_INDEX}.
|
|
108
|
+
*/
|
|
109
|
+
export function formatMassifObjectIndex(massifIndex) {
|
|
110
|
+
if (typeof massifIndex === "number" && !Number.isSafeInteger(massifIndex)) {
|
|
111
|
+
throw new Error(`Massif index must be a safe integer, got ${massifIndex}`);
|
|
112
|
+
}
|
|
113
|
+
const index = BigInt(massifIndex);
|
|
114
|
+
if (index < 0n || index > MAX_MASSIF_INDEX) {
|
|
115
|
+
throw new Error(`Massif index must be 0..${MAX_MASSIF_INDEX}, got ${index}`);
|
|
116
|
+
}
|
|
117
|
+
return index.toString(10).padStart(MASSIF_OBJECT_INDEX_DIGITS, "0");
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Reject a massif height or log id that would build a key Go cannot read:
|
|
121
|
+
* height outside 1..255 or non-integer, or a log id that is empty or would
|
|
122
|
+
* add path segments.
|
|
123
|
+
*/
|
|
124
|
+
function assertKeyHeightAndLogId(massifHeight, logId) {
|
|
125
|
+
if (!Number.isInteger(massifHeight) ||
|
|
126
|
+
massifHeight < MIN_MASSIF_HEIGHT ||
|
|
127
|
+
massifHeight > MAX_MASSIF_HEIGHT) {
|
|
128
|
+
throw new Error(`Massif height must be an integer ${MIN_MASSIF_HEIGHT}..${MAX_MASSIF_HEIGHT}, got ${massifHeight}`);
|
|
129
|
+
}
|
|
130
|
+
if (logId.length === 0 || logId.includes("/")) {
|
|
131
|
+
throw new Error(`Log id must be a non-empty path segment, got: ${JSON.stringify(logId)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/dist/mmr/algorithms.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/mmr/algorithms.js
CHANGED
|
@@ -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
|
+
}
|
package/dist/mmr/errors.d.ts
CHANGED
|
@@ -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
|
package/dist/mmr/errors.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/mmr/errors.js
CHANGED
|
@@ -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
package/src/index.ts
CHANGED
|
@@ -43,7 +43,14 @@ export {
|
|
|
43
43
|
isMassifFull,
|
|
44
44
|
} from "./massifs/types.js";
|
|
45
45
|
|
|
46
|
-
export {
|
|
46
|
+
export {
|
|
47
|
+
formatMassifObjectIndex,
|
|
48
|
+
MASSIF_OBJECT_INDEX_DIGITS,
|
|
49
|
+
MAX_MASSIF_INDEX,
|
|
50
|
+
parseV2StorageObjectPath,
|
|
51
|
+
v2CheckpointObjectKey,
|
|
52
|
+
v2MassifObjectKey,
|
|
53
|
+
} from "./massifs/v2storagepaths.js";
|
|
47
54
|
|
|
48
55
|
// Massif blob node access for MMR proof building (plan-2607-15 §4)
|
|
49
56
|
export { openMassifNodeStore } from "./massifs/nodestore.js";
|
|
@@ -82,8 +89,10 @@ export {
|
|
|
82
89
|
IncompleteTreeSize,
|
|
83
90
|
ConsistencyPeakCountMismatch,
|
|
84
91
|
ConsistencyPathLengthMismatch,
|
|
92
|
+
ConsistencyPathMalformed,
|
|
85
93
|
ConsistencyRootMismatch,
|
|
86
94
|
SizeMustIncrease,
|
|
95
|
+
SizeNotUint64,
|
|
87
96
|
} from "./mmr/errors.js";
|
|
88
97
|
export { createSyncHasher } from "./mmr/algorithms-sync.js";
|
|
89
98
|
export type { Proof, Peak } from "./mmr/types.js";
|
|
@@ -1,56 +1,106 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Merklelog v2 R2 object layout:
|
|
3
3
|
*
|
|
4
4
|
* - v2/merklelog/massifs/{massifHeight}/{logId}/{massifIndex}.log
|
|
5
5
|
* - v2/merklelog/checkpoints/{massifHeight}/{logId}/{massifIndex}.sth
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* `{massifIndex}` is a zero-padded, 16-digit DECIMAL integer. The Go writer
|
|
8
|
+
* owns this convention: go-merklelog
|
|
9
|
+
* [massifs/storage/const.go](https://github.com/forestrie/go-merklelog/blob/main/massifs/storage/const.go)
|
|
10
|
+
* (`V1MMRBlobNameFmt = "%016d.log"`, `V1MMRSignedTreeHeadBlobNameFmt =
|
|
11
|
+
* "%016d.sth"`) and its reader
|
|
12
|
+
* [objectindexfrompath.go](https://github.com/forestrie/go-merklelog/blob/main/massifs/storage/objectindexfrompath.go)
|
|
13
|
+
* (`strconv.Atoi`). canopy-api builds its R2 keys through the functions
|
|
14
|
+
* here (FOR-621: an earlier reader parsed hex, which agrees with the writer
|
|
15
|
+
* only for indices 0-9). Code outside this package that spells a key by hand
|
|
16
|
+
* (delegation-coordinator `seal-hint.ts`, test fixtures, demos) uses only
|
|
17
|
+
* index 0, which reads the same in any radix; anything that needs another
|
|
18
|
+
* index must use these helpers.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/** Width of the massif index segment in an object key. */
|
|
22
|
+
export const MASSIF_OBJECT_INDEX_DIGITS = 16;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Largest massif index the Go side can represent. go-merklelog
|
|
26
|
+
* `FmtMassifPath(prefix, massifIndex uint32)` and arbor's publisher
|
|
27
|
+
* (`strconv.ParseUint(base, 10, 32)`) use `uint32`; a larger index would be
|
|
28
|
+
* silently truncated by `ObjectIndexFromPath`, so it is rejected here.
|
|
29
|
+
*/
|
|
30
|
+
export const MAX_MASSIF_INDEX = 0xffffffffn;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Massif height bounds. Go writes height as `%d` of a `uint8` and arbor's
|
|
34
|
+
* publisher `ParseCheckpointKey` rejects 0, so valid heights are 1..255 with
|
|
35
|
+
* no leading zeros.
|
|
36
|
+
*/
|
|
37
|
+
const MIN_MASSIF_HEIGHT = 1;
|
|
38
|
+
const MAX_MASSIF_HEIGHT = 255;
|
|
39
|
+
|
|
40
|
+
const MASSIF_OBJECT_INDEX_RE = /^[0-9]{16}$/;
|
|
41
|
+
const MASSIF_HEIGHT_RE = /^[1-9][0-9]{0,2}$/;
|
|
42
|
+
const OBJECT_EXTENSIONS: Readonly<Record<string, string>> = {
|
|
43
|
+
massifs: ".log",
|
|
44
|
+
checkpoints: ".sth",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse an R2 object path to extract log ID, massif height, and massif index.
|
|
49
|
+
*
|
|
50
|
+
* @param path - The object path from the R2 notification
|
|
8
51
|
* @returns Parsed components including logId, massifHeight, and massifIndex
|
|
9
52
|
* @throws Error if the path doesn't match expected format or if parsing fails
|
|
10
53
|
*/
|
|
11
54
|
export function parseV2StorageObjectPath(path: string) {
|
|
12
55
|
const parts = path.split("/");
|
|
13
56
|
|
|
14
|
-
if (parts.length
|
|
57
|
+
if (parts.length !== 6 || parts[0] !== "v2" || parts[1] !== "merklelog")
|
|
15
58
|
throw new Error(`Unrecognized path format: ${path}`);
|
|
16
59
|
|
|
17
|
-
|
|
18
|
-
// or v2/merklelog/checkpoints/{massifHeight}/{logId}/{index}.sth
|
|
19
|
-
const typePart = parts[2]; // "massifs" or "checkpoints"
|
|
60
|
+
const typePart = parts[2];
|
|
20
61
|
const massifHeightStr = parts[3];
|
|
21
62
|
const logId = parts[4];
|
|
22
63
|
const filename = parts[5];
|
|
23
64
|
|
|
24
|
-
|
|
25
|
-
|
|
65
|
+
const expectedExt = Object.hasOwn(OBJECT_EXTENSIONS, typePart)
|
|
66
|
+
? OBJECT_EXTENSIONS[typePart]
|
|
67
|
+
: undefined;
|
|
68
|
+
if (expectedExt === undefined) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Object type must be massifs or checkpoints, got: ${typePart}`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (logId.length === 0) {
|
|
74
|
+
throw new Error(`Log id segment is empty: ${path}`);
|
|
75
|
+
}
|
|
26
76
|
if (!filename.endsWith(expectedExt)) {
|
|
27
77
|
throw new Error(
|
|
28
78
|
`Expected ${expectedExt} extension for ${typePart}, got: ${filename}`,
|
|
29
79
|
);
|
|
30
80
|
}
|
|
31
81
|
|
|
32
|
-
// Parse massif index (hex, 16 digits)
|
|
33
82
|
const massifIndexStr = filename.slice(0, -expectedExt.length);
|
|
34
|
-
if (massifIndexStr
|
|
83
|
+
if (!MASSIF_OBJECT_INDEX_RE.test(massifIndexStr)) {
|
|
35
84
|
throw new Error(
|
|
36
|
-
`Massif index must be 16
|
|
85
|
+
`Massif index must be 16 decimal digits, got: ${massifIndexStr}`,
|
|
37
86
|
);
|
|
38
87
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (!Number.isFinite(massifIndex)) {
|
|
88
|
+
const massifIndex = BigInt(massifIndexStr);
|
|
89
|
+
if (massifIndex > MAX_MASSIF_INDEX) {
|
|
42
90
|
throw new Error(
|
|
43
|
-
`
|
|
91
|
+
`Massif index must be 0..${MAX_MASSIF_INDEX}, got ${massifIndex}`,
|
|
44
92
|
);
|
|
45
93
|
}
|
|
46
94
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
95
|
+
if (
|
|
96
|
+
!MASSIF_HEIGHT_RE.test(massifHeightStr) ||
|
|
97
|
+
Number(massifHeightStr) > MAX_MASSIF_HEIGHT
|
|
98
|
+
) {
|
|
50
99
|
throw new Error(
|
|
51
100
|
`Failed to parse massif height as number: ${massifHeightStr}`,
|
|
52
101
|
);
|
|
53
102
|
}
|
|
103
|
+
const massifHeight = Number(massifHeightStr);
|
|
54
104
|
|
|
55
105
|
return {
|
|
56
106
|
logId,
|
|
@@ -59,3 +109,68 @@ export function parseV2StorageObjectPath(path: string) {
|
|
|
59
109
|
type: typePart,
|
|
60
110
|
};
|
|
61
111
|
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Build the massif `.log` object key for a log.
|
|
115
|
+
*/
|
|
116
|
+
export function v2MassifObjectKey(
|
|
117
|
+
massifHeight: number,
|
|
118
|
+
logId: string,
|
|
119
|
+
massifIndex: bigint | number,
|
|
120
|
+
): string {
|
|
121
|
+
assertKeyHeightAndLogId(massifHeight, logId);
|
|
122
|
+
return `v2/merklelog/massifs/${massifHeight}/${logId}/${formatMassifObjectIndex(massifIndex)}.log`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build the signed checkpoint `.sth` object key for a log.
|
|
127
|
+
*/
|
|
128
|
+
export function v2CheckpointObjectKey(
|
|
129
|
+
massifHeight: number,
|
|
130
|
+
logId: string,
|
|
131
|
+
massifIndex: bigint | number,
|
|
132
|
+
): string {
|
|
133
|
+
assertKeyHeightAndLogId(massifHeight, logId);
|
|
134
|
+
return `v2/merklelog/checkpoints/${massifHeight}/${logId}/${formatMassifObjectIndex(massifIndex)}.sth`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Format a massif index as the 16-digit decimal key segment (Go `%016d`).
|
|
139
|
+
*
|
|
140
|
+
* @throws Error if the index is negative, not an integer, not a safe integer
|
|
141
|
+
* (number input), or exceeds {@link MAX_MASSIF_INDEX}.
|
|
142
|
+
*/
|
|
143
|
+
export function formatMassifObjectIndex(massifIndex: bigint | number): string {
|
|
144
|
+
if (typeof massifIndex === "number" && !Number.isSafeInteger(massifIndex)) {
|
|
145
|
+
throw new Error(`Massif index must be a safe integer, got ${massifIndex}`);
|
|
146
|
+
}
|
|
147
|
+
const index = BigInt(massifIndex);
|
|
148
|
+
if (index < 0n || index > MAX_MASSIF_INDEX) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Massif index must be 0..${MAX_MASSIF_INDEX}, got ${index}`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
return index.toString(10).padStart(MASSIF_OBJECT_INDEX_DIGITS, "0");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Reject a massif height or log id that would build a key Go cannot read:
|
|
158
|
+
* height outside 1..255 or non-integer, or a log id that is empty or would
|
|
159
|
+
* add path segments.
|
|
160
|
+
*/
|
|
161
|
+
function assertKeyHeightAndLogId(massifHeight: number, logId: string): void {
|
|
162
|
+
if (
|
|
163
|
+
!Number.isInteger(massifHeight) ||
|
|
164
|
+
massifHeight < MIN_MASSIF_HEIGHT ||
|
|
165
|
+
massifHeight > MAX_MASSIF_HEIGHT
|
|
166
|
+
) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Massif height must be an integer ${MIN_MASSIF_HEIGHT}..${MAX_MASSIF_HEIGHT}, got ${massifHeight}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (logId.length === 0 || logId.includes("/")) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Log id must be a non-empty path segment, got: ${JSON.stringify(logId)}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
package/src/mmr/algorithms.ts
CHANGED
|
@@ -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
|
+
}
|