@forestrie/merklelog 0.0.1
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/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/massifs/indexformat.d.ts +146 -0
- package/dist/massifs/indexformat.d.ts.map +1 -0
- package/dist/massifs/indexformat.js +191 -0
- package/dist/massifs/logformat.d.ts +49 -0
- package/dist/massifs/logformat.d.ts.map +1 -0
- package/dist/massifs/logformat.js +49 -0
- package/dist/massifs/massif.d.ts +86 -0
- package/dist/massifs/massif.d.ts.map +1 -0
- package/dist/massifs/massif.js +143 -0
- package/dist/massifs/massiffull.d.ts +14 -0
- package/dist/massifs/massiffull.d.ts.map +1 -0
- package/dist/massifs/massiffull.js +21 -0
- package/dist/massifs/massiflogentries.d.ts +23 -0
- package/dist/massifs/massiflogentries.d.ts.map +1 -0
- package/dist/massifs/massiflogentries.js +31 -0
- package/dist/massifs/massifstart.d.ts +63 -0
- package/dist/massifs/massifstart.d.ts.map +1 -0
- package/dist/massifs/massifstart.js +39 -0
- package/dist/massifs/mmrindex.d.ts +15 -0
- package/dist/massifs/mmrindex.d.ts.map +1 -0
- package/dist/massifs/mmrindex.js +24 -0
- package/dist/massifs/peakstackend.d.ts +21 -0
- package/dist/massifs/peakstackend.d.ts.map +1 -0
- package/dist/massifs/peakstackend.js +29 -0
- package/dist/massifs/types.d.ts +20 -0
- package/dist/massifs/types.d.ts.map +1 -0
- package/dist/massifs/types.js +23 -0
- package/dist/massifs/urkleindex.d.ts +116 -0
- package/dist/massifs/urkleindex.d.ts.map +1 -0
- package/dist/massifs/urkleindex.js +169 -0
- package/dist/massifs/v2storagepaths.d.ts +17 -0
- package/dist/massifs/v2storagepaths.d.ts.map +1 -0
- package/dist/massifs/v2storagepaths.js +46 -0
- package/dist/mmr/algorithms-sync.d.ts +17 -0
- package/dist/mmr/algorithms-sync.d.ts.map +1 -0
- package/dist/mmr/algorithms-sync.js +42 -0
- package/dist/mmr/algorithms.d.ts +79 -0
- package/dist/mmr/algorithms.d.ts.map +1 -0
- package/dist/mmr/algorithms.js +155 -0
- package/dist/mmr/index.d.ts +37 -0
- package/dist/mmr/index.d.ts.map +1 -0
- package/dist/mmr/index.js +64 -0
- package/dist/mmr/math.d.ts +67 -0
- package/dist/mmr/math.d.ts.map +1 -0
- package/dist/mmr/math.js +172 -0
- package/dist/mmr/types.d.ts +37 -0
- package/dist/mmr/types.d.ts.map +1 -0
- package/dist/mmr/types.js +4 -0
- package/dist/uint64/index.d.ts +90 -0
- package/dist/uint64/index.d.ts.map +1 -0
- package/dist/uint64/index.js +149 -0
- package/dist/utils/arrays.d.ts +15 -0
- package/dist/utils/arrays.d.ts.map +1 -0
- package/dist/utils/arrays.js +24 -0
- package/package.json +41 -0
- package/src/index.ts +68 -0
- package/src/massifs/indexformat.ts +217 -0
- package/src/massifs/logformat.ts +54 -0
- package/src/massifs/massif.ts +182 -0
- package/src/massifs/massiffull.ts +28 -0
- package/src/massifs/massiflogentries.ts +40 -0
- package/src/massifs/massifstart.ts +67 -0
- package/src/massifs/mmrindex.ts +32 -0
- package/src/massifs/peakstackend.ts +34 -0
- package/src/massifs/types.ts +55 -0
- package/src/massifs/urkleindex.ts +273 -0
- package/src/massifs/v2storagepaths.ts +61 -0
- package/src/mmr/algorithms-sync.ts +46 -0
- package/src/mmr/algorithms.ts +194 -0
- package/src/mmr/index.ts +75 -0
- package/src/mmr/math.ts +190 -0
- package/src/mmr/types.ts +39 -0
- package/src/uint64/index.ts +164 -0
- package/src/utils/arrays.ts +25 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Urkle index helpers for efficient leaf data access
|
|
3
|
+
*
|
|
4
|
+
* Provides helpers for computing field indices and efficiently enumerating
|
|
5
|
+
* urkle leaf data components.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Leaf components that can be read via the enumerator.
|
|
9
|
+
*/
|
|
10
|
+
export type LeafComponent = "idtimestamp" | "valueBytes" | "extra1" | "extra2" | "extra3";
|
|
11
|
+
/**
|
|
12
|
+
* Returns the field index (for use with Massif.fieldref) of the start of the
|
|
13
|
+
* urkle leaf table data region.
|
|
14
|
+
*
|
|
15
|
+
* The field index is the offset in units of ValueBytes (32 bytes), which can be
|
|
16
|
+
* passed directly to Massif.fieldref() to get a view of the data.
|
|
17
|
+
*
|
|
18
|
+
* V2 layout before leaf table:
|
|
19
|
+
* StartHeader (256 bytes = 8 fields)
|
|
20
|
+
* IndexHeader (32 bytes = 1 field)
|
|
21
|
+
* BloomBitsets (4 * ceil(mBits/8))
|
|
22
|
+
* FrontierState (544 bytes)
|
|
23
|
+
*
|
|
24
|
+
* @param massifHeight - One-based massif height
|
|
25
|
+
* @returns Field index (in units of 32-byte fields) where the leaf table starts
|
|
26
|
+
*/
|
|
27
|
+
export declare function urkleLeafTableStartFieldIndex(massifHeight: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the byte offset within a massif buffer where the urkle leaf table
|
|
30
|
+
* starts. This is useful for direct byte-level access.
|
|
31
|
+
*
|
|
32
|
+
* @param massifHeight - One-based massif height
|
|
33
|
+
* @returns Byte offset where the leaf table starts
|
|
34
|
+
*/
|
|
35
|
+
export declare function urkleLeafTableStartByteOffset(massifHeight: number): number;
|
|
36
|
+
/**
|
|
37
|
+
* LeafEnumeratorSpec specifies which leaf components to enumerate.
|
|
38
|
+
*
|
|
39
|
+
* Each component that is true will be read and returned during enumeration.
|
|
40
|
+
*/
|
|
41
|
+
export interface LeafEnumeratorSpec {
|
|
42
|
+
idtimestamp?: boolean;
|
|
43
|
+
valueBytes?: boolean;
|
|
44
|
+
extra1?: boolean;
|
|
45
|
+
extra2?: boolean;
|
|
46
|
+
extra3?: boolean;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* LeafEntry represents a single leaf's data components.
|
|
50
|
+
*
|
|
51
|
+
* Only the components specified in the enumerator spec will be populated.
|
|
52
|
+
* Each component provides a DataView for efficient typed access.
|
|
53
|
+
*/
|
|
54
|
+
export interface LeafEntry {
|
|
55
|
+
/** Leaf ordinal (0-based index) */
|
|
56
|
+
ordinal: number;
|
|
57
|
+
/** ID timestamp as bigint (if idtimestamp was requested) */
|
|
58
|
+
idtimestamp?: bigint;
|
|
59
|
+
/** Committed value bytes as Uint8Array view (if valueBytes was requested) */
|
|
60
|
+
valueBytes?: Uint8Array;
|
|
61
|
+
/** Extra1 field as Uint8Array view (24 bytes, if extra1 was requested) */
|
|
62
|
+
extra1?: Uint8Array;
|
|
63
|
+
/** Extra2 field as Uint8Array view (32 bytes, if extra2 was requested) */
|
|
64
|
+
extra2?: Uint8Array;
|
|
65
|
+
/** Extra3 field as Uint8Array view (32 bytes, if extra3 was requested) */
|
|
66
|
+
extra3?: Uint8Array;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Creates an efficient leaf enumerator for iterating through urkle leaf data.
|
|
70
|
+
*
|
|
71
|
+
* The enumerator returns a generator function that yields LeafEntry objects
|
|
72
|
+
* for each leaf in the massif. Only the components specified in the spec
|
|
73
|
+
* are read and returned, minimizing overhead.
|
|
74
|
+
*
|
|
75
|
+
* The enumerator uses DataView for efficient typed access without copying.
|
|
76
|
+
*
|
|
77
|
+
* @param buffer - The massif buffer (Uint8Array)
|
|
78
|
+
* @param massifHeight - One-based massif height
|
|
79
|
+
* @param leafCount - Number of leaves to enumerate (from start position)
|
|
80
|
+
* @param spec - Specification of which components to read
|
|
81
|
+
* @param start - Starting leaf ordinal (0-based, defaults to 0)
|
|
82
|
+
* @returns Generator that yields LeafEntry objects
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const enumerate = createLeafEnumerator(
|
|
87
|
+
* massif.buffer,
|
|
88
|
+
* massifHeight,
|
|
89
|
+
* actualLeafCount,
|
|
90
|
+
* { idtimestamp: true, valueBytes: true },
|
|
91
|
+
* 0 // start from first leaf
|
|
92
|
+
* );
|
|
93
|
+
*
|
|
94
|
+
* for (const leaf of enumerate()) {
|
|
95
|
+
* console.log(leaf.ordinal, leaf.idtimestamp, leaf.valueBytes);
|
|
96
|
+
* }
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function createLeafEnumerator(buffer: Uint8Array, massifHeight: number, leafCount: number, spec: LeafEnumeratorSpec, start?: number): () => Generator<LeafEntry, void, unknown>;
|
|
100
|
+
/**
|
|
101
|
+
* Returns the byte offset of a specific leaf record's component.
|
|
102
|
+
*
|
|
103
|
+
* @param massifHeight - One-based massif height
|
|
104
|
+
* @param leafOrdinal - Zero-based leaf ordinal
|
|
105
|
+
* @param component - Which component to locate
|
|
106
|
+
* @returns Byte offset of the component within the massif buffer
|
|
107
|
+
*/
|
|
108
|
+
export declare function leafComponentByteOffset(massifHeight: number, leafOrdinal: number, component: LeafComponent): number;
|
|
109
|
+
/**
|
|
110
|
+
* Returns the size in bytes of a leaf component.
|
|
111
|
+
*
|
|
112
|
+
* @param component - Which component
|
|
113
|
+
* @returns Size in bytes
|
|
114
|
+
*/
|
|
115
|
+
export declare function leafComponentSize(component: LeafComponent): number;
|
|
116
|
+
//# sourceMappingURL=urkleindex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"urkleindex.d.ts","sourceRoot":"","sources":["../../src/massifs/urkleindex.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,aAAa,GACb,YAAY,GACZ,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAC;AAwBb;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAiB1E;AAED;;;;;;GAMG;AACH,wBAAgB,6BAA6B,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAa1E;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,kBAAkB,EACxB,KAAK,GAAE,MAAU,GAChB,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,CAoE3C;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,aAAa,GACvB,MAAM,CAIR;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM,CAElE"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Urkle index helpers for efficient leaf data access
|
|
3
|
+
*
|
|
4
|
+
* Provides helpers for computing field indices and efficiently enumerating
|
|
5
|
+
* urkle leaf data components.
|
|
6
|
+
*/
|
|
7
|
+
import { LogFormat } from "./logformat.js";
|
|
8
|
+
import { Urkle, Bloom, leafCountForMassifHeight, bloomMBits, bloomBitsetBytes, } from "./indexformat.js";
|
|
9
|
+
/**
|
|
10
|
+
* Byte offsets within a leaf record for each component.
|
|
11
|
+
*/
|
|
12
|
+
const LEAF_COMPONENT_OFFSETS = {
|
|
13
|
+
idtimestamp: 0,
|
|
14
|
+
valueBytes: Urkle.LeafValueOffset, // 8
|
|
15
|
+
extra1: Urkle.LeafExtra1Offset, // 40
|
|
16
|
+
extra2: Urkle.LeafExtra2Offset, // 64
|
|
17
|
+
extra3: Urkle.LeafExtra3Offset, // 96
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Byte sizes for each leaf component.
|
|
21
|
+
*/
|
|
22
|
+
const LEAF_COMPONENT_SIZES = {
|
|
23
|
+
idtimestamp: Urkle.LeafKeyBytes, // 8
|
|
24
|
+
valueBytes: Urkle.LeafValueBytes, // 32
|
|
25
|
+
extra1: Urkle.LeafExtra1Bytes, // 24
|
|
26
|
+
extra2: Urkle.LeafExtraBytes, // 32
|
|
27
|
+
extra3: Urkle.LeafExtraBytes, // 32
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Returns the field index (for use with Massif.fieldref) of the start of the
|
|
31
|
+
* urkle leaf table data region.
|
|
32
|
+
*
|
|
33
|
+
* The field index is the offset in units of ValueBytes (32 bytes), which can be
|
|
34
|
+
* passed directly to Massif.fieldref() to get a view of the data.
|
|
35
|
+
*
|
|
36
|
+
* V2 layout before leaf table:
|
|
37
|
+
* StartHeader (256 bytes = 8 fields)
|
|
38
|
+
* IndexHeader (32 bytes = 1 field)
|
|
39
|
+
* BloomBitsets (4 * ceil(mBits/8))
|
|
40
|
+
* FrontierState (544 bytes)
|
|
41
|
+
*
|
|
42
|
+
* @param massifHeight - One-based massif height
|
|
43
|
+
* @returns Field index (in units of 32-byte fields) where the leaf table starts
|
|
44
|
+
*/
|
|
45
|
+
export function urkleLeafTableStartFieldIndex(massifHeight) {
|
|
46
|
+
const leafCount = leafCountForMassifHeight(massifHeight);
|
|
47
|
+
const mBits = bloomMBits(leafCount);
|
|
48
|
+
const bloomBitsetsOnly = BigInt(Bloom.Filters) * bloomBitsetBytes(mBits > 0n ? mBits : 0n);
|
|
49
|
+
// Byte offset calculation
|
|
50
|
+
const startHeaderBytes = BigInt(LogFormat.StartHeaderSize); // 256
|
|
51
|
+
const indexHeaderBytes = BigInt(LogFormat.IndexHeaderBytes); // 32
|
|
52
|
+
const frontierBytes = BigInt(Urkle.FrontierStateV1Bytes); // 544
|
|
53
|
+
const byteOffset = startHeaderBytes + indexHeaderBytes + bloomBitsetsOnly + frontierBytes;
|
|
54
|
+
// Convert to field index (each field is 32 bytes)
|
|
55
|
+
// Note: this should be an exact division for valid massif heights
|
|
56
|
+
return Number(byteOffset / BigInt(LogFormat.ValueBytes));
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns the byte offset within a massif buffer where the urkle leaf table
|
|
60
|
+
* starts. This is useful for direct byte-level access.
|
|
61
|
+
*
|
|
62
|
+
* @param massifHeight - One-based massif height
|
|
63
|
+
* @returns Byte offset where the leaf table starts
|
|
64
|
+
*/
|
|
65
|
+
export function urkleLeafTableStartByteOffset(massifHeight) {
|
|
66
|
+
const leafCount = leafCountForMassifHeight(massifHeight);
|
|
67
|
+
const mBits = bloomMBits(leafCount);
|
|
68
|
+
const bloomBitsetsOnly = BigInt(Bloom.Filters) * bloomBitsetBytes(mBits > 0n ? mBits : 0n);
|
|
69
|
+
const startHeaderBytes = BigInt(LogFormat.StartHeaderSize);
|
|
70
|
+
const indexHeaderBytes = BigInt(LogFormat.IndexHeaderBytes);
|
|
71
|
+
const frontierBytes = BigInt(Urkle.FrontierStateV1Bytes);
|
|
72
|
+
return Number(startHeaderBytes + indexHeaderBytes + bloomBitsetsOnly + frontierBytes);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Creates an efficient leaf enumerator for iterating through urkle leaf data.
|
|
76
|
+
*
|
|
77
|
+
* The enumerator returns a generator function that yields LeafEntry objects
|
|
78
|
+
* for each leaf in the massif. Only the components specified in the spec
|
|
79
|
+
* are read and returned, minimizing overhead.
|
|
80
|
+
*
|
|
81
|
+
* The enumerator uses DataView for efficient typed access without copying.
|
|
82
|
+
*
|
|
83
|
+
* @param buffer - The massif buffer (Uint8Array)
|
|
84
|
+
* @param massifHeight - One-based massif height
|
|
85
|
+
* @param leafCount - Number of leaves to enumerate (from start position)
|
|
86
|
+
* @param spec - Specification of which components to read
|
|
87
|
+
* @param start - Starting leaf ordinal (0-based, defaults to 0)
|
|
88
|
+
* @returns Generator that yields LeafEntry objects
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```typescript
|
|
92
|
+
* const enumerate = createLeafEnumerator(
|
|
93
|
+
* massif.buffer,
|
|
94
|
+
* massifHeight,
|
|
95
|
+
* actualLeafCount,
|
|
96
|
+
* { idtimestamp: true, valueBytes: true },
|
|
97
|
+
* 0 // start from first leaf
|
|
98
|
+
* );
|
|
99
|
+
*
|
|
100
|
+
* for (const leaf of enumerate()) {
|
|
101
|
+
* console.log(leaf.ordinal, leaf.idtimestamp, leaf.valueBytes);
|
|
102
|
+
* }
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export function createLeafEnumerator(buffer, massifHeight, leafCount, spec, start = 0) {
|
|
106
|
+
const leafTableStart = urkleLeafTableStartByteOffset(massifHeight);
|
|
107
|
+
const recordBytes = Urkle.LeafRecordBytes;
|
|
108
|
+
// Pre-compute which components we need to read
|
|
109
|
+
const readIdtimestamp = spec.idtimestamp ?? false;
|
|
110
|
+
const readValueBytes = spec.valueBytes ?? false;
|
|
111
|
+
const readExtra1 = spec.extra1 ?? false;
|
|
112
|
+
const readExtra2 = spec.extra2 ?? false;
|
|
113
|
+
const readExtra3 = spec.extra3 ?? false;
|
|
114
|
+
// Create DataView for the buffer (reused for all reads)
|
|
115
|
+
const dataView = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
|
|
116
|
+
return function* () {
|
|
117
|
+
for (let i = 0; i < leafCount; i++) {
|
|
118
|
+
const ordinal = start + i;
|
|
119
|
+
const recordOffset = leafTableStart + ordinal * recordBytes;
|
|
120
|
+
const entry = { ordinal };
|
|
121
|
+
if (readIdtimestamp) {
|
|
122
|
+
entry.idtimestamp = dataView.getBigUint64(recordOffset, false);
|
|
123
|
+
}
|
|
124
|
+
if (readValueBytes) {
|
|
125
|
+
entry.valueBytes = buffer.subarray(recordOffset + LEAF_COMPONENT_OFFSETS.valueBytes, recordOffset +
|
|
126
|
+
LEAF_COMPONENT_OFFSETS.valueBytes +
|
|
127
|
+
LEAF_COMPONENT_SIZES.valueBytes);
|
|
128
|
+
}
|
|
129
|
+
if (readExtra1) {
|
|
130
|
+
entry.extra1 = buffer.subarray(recordOffset + LEAF_COMPONENT_OFFSETS.extra1, recordOffset +
|
|
131
|
+
LEAF_COMPONENT_OFFSETS.extra1 +
|
|
132
|
+
LEAF_COMPONENT_SIZES.extra1);
|
|
133
|
+
}
|
|
134
|
+
if (readExtra2) {
|
|
135
|
+
entry.extra2 = buffer.subarray(recordOffset + LEAF_COMPONENT_OFFSETS.extra2, recordOffset +
|
|
136
|
+
LEAF_COMPONENT_OFFSETS.extra2 +
|
|
137
|
+
LEAF_COMPONENT_SIZES.extra2);
|
|
138
|
+
}
|
|
139
|
+
if (readExtra3) {
|
|
140
|
+
entry.extra3 = buffer.subarray(recordOffset + LEAF_COMPONENT_OFFSETS.extra3, recordOffset +
|
|
141
|
+
LEAF_COMPONENT_OFFSETS.extra3 +
|
|
142
|
+
LEAF_COMPONENT_SIZES.extra3);
|
|
143
|
+
}
|
|
144
|
+
yield entry;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Returns the byte offset of a specific leaf record's component.
|
|
150
|
+
*
|
|
151
|
+
* @param massifHeight - One-based massif height
|
|
152
|
+
* @param leafOrdinal - Zero-based leaf ordinal
|
|
153
|
+
* @param component - Which component to locate
|
|
154
|
+
* @returns Byte offset of the component within the massif buffer
|
|
155
|
+
*/
|
|
156
|
+
export function leafComponentByteOffset(massifHeight, leafOrdinal, component) {
|
|
157
|
+
const leafTableStart = urkleLeafTableStartByteOffset(massifHeight);
|
|
158
|
+
const recordOffset = leafTableStart + leafOrdinal * Urkle.LeafRecordBytes;
|
|
159
|
+
return recordOffset + LEAF_COMPONENT_OFFSETS[component];
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Returns the size in bytes of a leaf component.
|
|
163
|
+
*
|
|
164
|
+
* @param component - Which component
|
|
165
|
+
* @returns Size in bytes
|
|
166
|
+
*/
|
|
167
|
+
export function leafComponentSize(component) {
|
|
168
|
+
return LEAF_COMPONENT_SIZES[component];
|
|
169
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse an R2 object path to extract log ID, massif height, and massif index.
|
|
3
|
+
*
|
|
4
|
+
* - v2/merklelog/massifs/{massifHeight}/{logId}/{massifIndex}.log
|
|
5
|
+
* - v2/merklelog/checkpoints/{massifHeight}/{logId}/{massifIndex}.sth
|
|
6
|
+
*
|
|
7
|
+
* @param path - The object path path from the R2 notification
|
|
8
|
+
* @returns Parsed components including logId, massifHeight, and massifIndex
|
|
9
|
+
* @throws Error if the path doesn't match expected format or if parsing fails
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseV2StorageObjectPath(path: string): {
|
|
12
|
+
logId: string;
|
|
13
|
+
massifHeight: number;
|
|
14
|
+
massifIndex: number;
|
|
15
|
+
type: string;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=v2storagepaths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"v2storagepaths.d.ts","sourceRoot":"","sources":["../../src/massifs/v2storagepaths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM;;;;;EAkDpD"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse an R2 object path to extract log ID, massif height, and massif index.
|
|
3
|
+
*
|
|
4
|
+
* - v2/merklelog/massifs/{massifHeight}/{logId}/{massifIndex}.log
|
|
5
|
+
* - v2/merklelog/checkpoints/{massifHeight}/{logId}/{massifIndex}.sth
|
|
6
|
+
*
|
|
7
|
+
* @param path - The object path path from the R2 notification
|
|
8
|
+
* @returns Parsed components including logId, massifHeight, and massifIndex
|
|
9
|
+
* @throws Error if the path doesn't match expected format or if parsing fails
|
|
10
|
+
*/
|
|
11
|
+
export function parseV2StorageObjectPath(path) {
|
|
12
|
+
const parts = path.split("/");
|
|
13
|
+
if (parts.length < 6 || parts[0] !== "v2" || parts[1] !== "merklelog")
|
|
14
|
+
throw new Error(`Unrecognized path format: ${path}`);
|
|
15
|
+
// Check for new v2 format: v2/merklelog/massifs/{massifHeight}/{logId}/{index}.log
|
|
16
|
+
// or v2/merklelog/checkpoints/{massifHeight}/{logId}/{index}.sth
|
|
17
|
+
const typePart = parts[2]; // "massifs" or "checkpoints"
|
|
18
|
+
const massifHeightStr = parts[3];
|
|
19
|
+
const logId = parts[4];
|
|
20
|
+
const filename = parts[5];
|
|
21
|
+
// Validate extension
|
|
22
|
+
const expectedExt = typePart === "massifs" ? ".log" : ".sth";
|
|
23
|
+
if (!filename.endsWith(expectedExt)) {
|
|
24
|
+
throw new Error(`Expected ${expectedExt} extension for ${typePart}, got: ${filename}`);
|
|
25
|
+
}
|
|
26
|
+
// Parse massif index (hex, 16 digits)
|
|
27
|
+
const massifIndexStr = filename.slice(0, -expectedExt.length);
|
|
28
|
+
if (massifIndexStr.length !== 16) {
|
|
29
|
+
throw new Error(`Massif index must be 16 hex digits, got ${massifIndexStr.length}: ${massifIndexStr}`);
|
|
30
|
+
}
|
|
31
|
+
const massifIndex = Number.parseInt(massifIndexStr, 16);
|
|
32
|
+
if (!Number.isFinite(massifIndex)) {
|
|
33
|
+
throw new Error(`Failed to parse massif index as hex number: ${massifIndexStr}`);
|
|
34
|
+
}
|
|
35
|
+
// Parse massifHeight
|
|
36
|
+
const massifHeight = Number.parseInt(massifHeightStr, 10);
|
|
37
|
+
if (!Number.isFinite(massifHeight)) {
|
|
38
|
+
throw new Error(`Failed to parse massif height as number: ${massifHeightStr}`);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
logId,
|
|
42
|
+
massifHeight,
|
|
43
|
+
massifIndex,
|
|
44
|
+
type: typePart,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience factory for a sync-backed Hasher (Node.js).
|
|
3
|
+
*
|
|
4
|
+
* createSyncHasher() returns a Hasher that uses node:crypto (SHA-256).
|
|
5
|
+
* digest() returns Promise.resolve(sync digest), so the hasher is suitable
|
|
6
|
+
* for callers who know they are in Node and want a sync-backed implementation.
|
|
7
|
+
*/
|
|
8
|
+
import type { Hasher } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Creates a Hasher that uses Node's crypto.createHash('sha256').
|
|
11
|
+
* digest() returns Promise.resolve(sync digest), so it is safe to use with
|
|
12
|
+
* when you need a sync-backed hasher (e.g. in Node).
|
|
13
|
+
*
|
|
14
|
+
* Only available in Node.js; throws if node:crypto is not available.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createSyncHasher(): Promise<Hasher>;
|
|
17
|
+
//# sourceMappingURL=algorithms-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"algorithms-sync.d.ts","sourceRoot":"","sources":["../../src/mmr/algorithms-sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAazC;;;;;;GAMG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAiBxD"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convenience factory for a sync-backed Hasher (Node.js).
|
|
3
|
+
*
|
|
4
|
+
* createSyncHasher() returns a Hasher that uses node:crypto (SHA-256).
|
|
5
|
+
* digest() returns Promise.resolve(sync digest), so the hasher is suitable
|
|
6
|
+
* for callers who know they are in Node and want a sync-backed implementation.
|
|
7
|
+
*/
|
|
8
|
+
function concatChunks(chunks) {
|
|
9
|
+
const totalLength = chunks.reduce((s, c) => s + c.length, 0);
|
|
10
|
+
const out = new Uint8Array(totalLength);
|
|
11
|
+
let offset = 0;
|
|
12
|
+
for (const c of chunks) {
|
|
13
|
+
out.set(c, offset);
|
|
14
|
+
offset += c.length;
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a Hasher that uses Node's crypto.createHash('sha256').
|
|
20
|
+
* digest() returns Promise.resolve(sync digest), so it is safe to use with
|
|
21
|
+
* when you need a sync-backed hasher (e.g. in Node).
|
|
22
|
+
*
|
|
23
|
+
* Only available in Node.js; throws if node:crypto is not available.
|
|
24
|
+
*/
|
|
25
|
+
export async function createSyncHasher() {
|
|
26
|
+
const { createHash } = await import("node:crypto");
|
|
27
|
+
const chunks = [];
|
|
28
|
+
return {
|
|
29
|
+
reset() {
|
|
30
|
+
chunks.length = 0;
|
|
31
|
+
},
|
|
32
|
+
update(data) {
|
|
33
|
+
chunks.push(data);
|
|
34
|
+
},
|
|
35
|
+
digest() {
|
|
36
|
+
const combined = concatChunks(chunks);
|
|
37
|
+
const buf = Buffer.from(combined);
|
|
38
|
+
const out = createHash("sha256").update(buf).digest();
|
|
39
|
+
return Promise.resolve(new Uint8Array(out));
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MMR Algorithm Implementations
|
|
3
|
+
*
|
|
4
|
+
* Core algorithms for Merkle Mountain Range operations including
|
|
5
|
+
* peak bagging, inclusion proofs, and consistency proofs.
|
|
6
|
+
*
|
|
7
|
+
* This implementation is based on the authoritative Python reference:
|
|
8
|
+
* https://raw.githubusercontent.com/robinbryce/merkle-mountain-range-proofs/refs/heads/main/algorithms.py
|
|
9
|
+
*
|
|
10
|
+
* And the associated IETF draft specification:
|
|
11
|
+
* https://raw.githubusercontent.com/robinbryce/draft-bryce-cose-receipts-mmr-profile/refs/heads/main/draft-bryce-cose-receipts-mmr-profile.md
|
|
12
|
+
*/
|
|
13
|
+
import type { Proof, Hasher } from "./types.js";
|
|
14
|
+
/**
|
|
15
|
+
* Bags peaks together to compute a single root hash.
|
|
16
|
+
*
|
|
17
|
+
* The root is defined as the 'bagging' of all peaks, starting with the highest.
|
|
18
|
+
* This creates a binary merkle tree from the peaks to obtain a single tree root.
|
|
19
|
+
*
|
|
20
|
+
* WARNING — NOT used by the receipt verification path, and NOT spec-aligned for
|
|
21
|
+
* MMRIVER receipts. The MMR profile
|
|
22
|
+
* (draft-bryce-cose-receipts-mmr-profile) proves inclusion to a single peak
|
|
23
|
+
* (the accumulator is the list of peaks); it does not bag peaks. This helper
|
|
24
|
+
* also hashes `H(right || left)` WITHOUT the 1-based position prefix that
|
|
25
|
+
* `calculateRoot` commits, so it is inconsistent with go-merklelog interior
|
|
26
|
+
* hashing. Retained only for any legacy bagging caller; do not introduce new
|
|
27
|
+
* consumers without first reconciling with the spec.
|
|
28
|
+
*
|
|
29
|
+
* @param hasher - Cryptographic hasher instance
|
|
30
|
+
* @param peaks - Array of peak hashes (highest to lowest)
|
|
31
|
+
* @returns The bagged root hash
|
|
32
|
+
*/
|
|
33
|
+
export declare function bagPeaks(hasher: Hasher, peaks: Uint8Array[]): Promise<Uint8Array>;
|
|
34
|
+
/**
|
|
35
|
+
* Calculates the root hash from a leaf hash and inclusion proof
|
|
36
|
+
*
|
|
37
|
+
* Mirrors the reference `included_root` (algorithms.py): each interior node is
|
|
38
|
+
* `H(pos_BE8 || left || right)` where `pos` is the 1-based node position.
|
|
39
|
+
*
|
|
40
|
+
* @param hasher - Cryptographic hasher instance
|
|
41
|
+
* @param leafHash - Hash of the leaf being proven
|
|
42
|
+
* @param proof - Inclusion proof path
|
|
43
|
+
* @param leafIndex - The zero-based MMR index of the node being proven. Despite
|
|
44
|
+
* the name, this is treated as an MMR index (it seeds `currentPos = index + 1`
|
|
45
|
+
* and `heightIndex(index)`). For leaf 0 the leaf index and MMR index coincide;
|
|
46
|
+
* for any other leaf, callers MUST pass the MMR index (see `proof.mmrIndex`),
|
|
47
|
+
* not the leaf index.
|
|
48
|
+
* @returns The calculated root hash
|
|
49
|
+
*/
|
|
50
|
+
export declare function calculateRoot(hasher: Hasher, leafHash: Uint8Array, proof: Proof, leafIndex: bigint): Promise<Uint8Array>;
|
|
51
|
+
/**
|
|
52
|
+
* Verifies an inclusion proof
|
|
53
|
+
*
|
|
54
|
+
* @param hasher - Cryptographic hasher instance
|
|
55
|
+
* @param leafHash - Hash of the leaf being proven
|
|
56
|
+
* @param proof - Inclusion proof
|
|
57
|
+
* @param root - Expected root hash
|
|
58
|
+
* @returns True if the proof is valid
|
|
59
|
+
*/
|
|
60
|
+
export declare function verifyInclusion(hasher: Hasher, leafHash: Uint8Array, proof: Proof, root: Uint8Array): Promise<boolean>;
|
|
61
|
+
/**
|
|
62
|
+
* Verifies a consistency proof between two MMR states.
|
|
63
|
+
*
|
|
64
|
+
* WARNING — NOT IMPLEMENTED. This is a stub: it compares each root against
|
|
65
|
+
* itself and therefore ALWAYS returns true. It performs no real consistency
|
|
66
|
+
* check and MUST NOT be relied upon for security. No worker path currently
|
|
67
|
+
* calls it. See the reference `verify_consistency` (algorithms.py) for the
|
|
68
|
+
* intended algorithm (verify proof2 extends proof1 via position-committed
|
|
69
|
+
* interior hashing).
|
|
70
|
+
*
|
|
71
|
+
* @param hasher - Cryptographic hasher instance
|
|
72
|
+
* @param proof1 - Proof for the first state
|
|
73
|
+
* @param proof2 - Proof for the second state
|
|
74
|
+
* @param root1 - Root hash of the first state
|
|
75
|
+
* @param root2 - Root hash of the second state
|
|
76
|
+
* @returns Always true (stub) — do not use for verification
|
|
77
|
+
*/
|
|
78
|
+
export declare function verifyConsistency(hasher: Hasher, proof1: Proof, proof2: Proof, root1: Uint8Array, root2: Uint8Array): boolean;
|
|
79
|
+
//# sourceMappingURL=algorithms.d.ts.map
|
|
@@ -0,0 +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;AAqBhD;;;;;;;;;;;;;;;;;;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;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,KAAK,EACb,MAAM,EAAE,KAAK,EACb,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE,UAAU,GAChB,OAAO,CAKT"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MMR Algorithm Implementations
|
|
3
|
+
*
|
|
4
|
+
* Core algorithms for Merkle Mountain Range operations including
|
|
5
|
+
* peak bagging, inclusion proofs, and consistency proofs.
|
|
6
|
+
*
|
|
7
|
+
* This implementation is based on the authoritative Python reference:
|
|
8
|
+
* https://raw.githubusercontent.com/robinbryce/merkle-mountain-range-proofs/refs/heads/main/algorithms.py
|
|
9
|
+
*
|
|
10
|
+
* And the associated IETF draft specification:
|
|
11
|
+
* https://raw.githubusercontent.com/robinbryce/draft-bryce-cose-receipts-mmr-profile/refs/heads/main/draft-bryce-cose-receipts-mmr-profile.md
|
|
12
|
+
*/
|
|
13
|
+
import { Uint64 } from "../uint64/index.js";
|
|
14
|
+
import { heightIndex } from "./math.js";
|
|
15
|
+
import { arraysEqual } from "../utils/arrays.js";
|
|
16
|
+
/**
|
|
17
|
+
* Encodes a Uint64 as 8 bytes big-endian.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors go-merklelog `HashWriteUint64` and the reference
|
|
20
|
+
* `pos.to_bytes(8, byteorder="big")` used by `hash_pospair64`.
|
|
21
|
+
*/
|
|
22
|
+
function u64BigEndian(value) {
|
|
23
|
+
const out = new Uint8Array(8);
|
|
24
|
+
let v = value.toBigInt() & 0xffffffffffffffffn;
|
|
25
|
+
for (let i = 7; i >= 0; i--) {
|
|
26
|
+
out[i] = Number(v & 0xffn);
|
|
27
|
+
v >>= 8n;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Bags peaks together to compute a single root hash.
|
|
33
|
+
*
|
|
34
|
+
* The root is defined as the 'bagging' of all peaks, starting with the highest.
|
|
35
|
+
* This creates a binary merkle tree from the peaks to obtain a single tree root.
|
|
36
|
+
*
|
|
37
|
+
* WARNING — NOT used by the receipt verification path, and NOT spec-aligned for
|
|
38
|
+
* MMRIVER receipts. The MMR profile
|
|
39
|
+
* (draft-bryce-cose-receipts-mmr-profile) proves inclusion to a single peak
|
|
40
|
+
* (the accumulator is the list of peaks); it does not bag peaks. This helper
|
|
41
|
+
* also hashes `H(right || left)` WITHOUT the 1-based position prefix that
|
|
42
|
+
* `calculateRoot` commits, so it is inconsistent with go-merklelog interior
|
|
43
|
+
* hashing. Retained only for any legacy bagging caller; do not introduce new
|
|
44
|
+
* consumers without first reconciling with the spec.
|
|
45
|
+
*
|
|
46
|
+
* @param hasher - Cryptographic hasher instance
|
|
47
|
+
* @param peaks - Array of peak hashes (highest to lowest)
|
|
48
|
+
* @returns The bagged root hash
|
|
49
|
+
*/
|
|
50
|
+
export async function bagPeaks(hasher, peaks) {
|
|
51
|
+
if (peaks.length === 0) {
|
|
52
|
+
throw new Error("Cannot bag empty peaks array");
|
|
53
|
+
}
|
|
54
|
+
if (peaks.length === 1) {
|
|
55
|
+
return peaks[0];
|
|
56
|
+
}
|
|
57
|
+
const peakHashes = [...peaks];
|
|
58
|
+
while (peakHashes.length > 1) {
|
|
59
|
+
const right = peakHashes.pop();
|
|
60
|
+
const left = peakHashes.pop();
|
|
61
|
+
hasher.reset();
|
|
62
|
+
hasher.update(right);
|
|
63
|
+
hasher.update(left);
|
|
64
|
+
const combined = await hasher.digest();
|
|
65
|
+
peakHashes.push(combined);
|
|
66
|
+
}
|
|
67
|
+
return peakHashes[0];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Calculates the root hash from a leaf hash and inclusion proof
|
|
71
|
+
*
|
|
72
|
+
* Mirrors the reference `included_root` (algorithms.py): each interior node is
|
|
73
|
+
* `H(pos_BE8 || left || right)` where `pos` is the 1-based node position.
|
|
74
|
+
*
|
|
75
|
+
* @param hasher - Cryptographic hasher instance
|
|
76
|
+
* @param leafHash - Hash of the leaf being proven
|
|
77
|
+
* @param proof - Inclusion proof path
|
|
78
|
+
* @param leafIndex - The zero-based MMR index of the node being proven. Despite
|
|
79
|
+
* the name, this is treated as an MMR index (it seeds `currentPos = index + 1`
|
|
80
|
+
* and `heightIndex(index)`). For leaf 0 the leaf index and MMR index coincide;
|
|
81
|
+
* for any other leaf, callers MUST pass the MMR index (see `proof.mmrIndex`),
|
|
82
|
+
* not the leaf index.
|
|
83
|
+
* @returns The calculated root hash
|
|
84
|
+
*/
|
|
85
|
+
export async function calculateRoot(hasher, leafHash, proof, leafIndex) {
|
|
86
|
+
let currentHash = leafHash;
|
|
87
|
+
const mmrIndex = new Uint64(leafIndex);
|
|
88
|
+
let currentHeight = heightIndex(mmrIndex);
|
|
89
|
+
let currentPos = mmrIndex.add(new Uint64(1));
|
|
90
|
+
for (const siblingHash of proof.path) {
|
|
91
|
+
hasher.reset();
|
|
92
|
+
const nextHeight = heightIndex(new Uint64(currentPos.toBigInt()));
|
|
93
|
+
const isRightChild = nextHeight > currentHeight;
|
|
94
|
+
// Advance currentPos to the parent node's 1-based position, then commit it
|
|
95
|
+
// as the hash prefix: interior nodes are H(pos || left || right) per the
|
|
96
|
+
// MMR profile (draft-bryce-cose-receipts-mmr-profile `included_root` /
|
|
97
|
+
// `hash_pospair64`, and go-merklelog `HashPosPair64`). Omitting `pos`
|
|
98
|
+
// produces the wrong peak for any leaf above a single-leaf tree.
|
|
99
|
+
if (isRightChild) {
|
|
100
|
+
currentPos = currentPos.add(new Uint64(1));
|
|
101
|
+
hasher.update(u64BigEndian(currentPos));
|
|
102
|
+
hasher.update(siblingHash);
|
|
103
|
+
hasher.update(currentHash);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
currentPos = currentPos.add(new Uint64(2).shl(currentHeight));
|
|
107
|
+
hasher.update(u64BigEndian(currentPos));
|
|
108
|
+
hasher.update(currentHash);
|
|
109
|
+
hasher.update(siblingHash);
|
|
110
|
+
}
|
|
111
|
+
currentHash = await hasher.digest();
|
|
112
|
+
currentHeight += 1;
|
|
113
|
+
}
|
|
114
|
+
return currentHash;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Verifies an inclusion proof
|
|
118
|
+
*
|
|
119
|
+
* @param hasher - Cryptographic hasher instance
|
|
120
|
+
* @param leafHash - Hash of the leaf being proven
|
|
121
|
+
* @param proof - Inclusion proof
|
|
122
|
+
* @param root - Expected root hash
|
|
123
|
+
* @returns True if the proof is valid
|
|
124
|
+
*/
|
|
125
|
+
export async function verifyInclusion(hasher, leafHash, proof, root) {
|
|
126
|
+
if (proof.leafIndex === undefined && proof.mmrIndex === undefined) {
|
|
127
|
+
throw new Error("Proof must have either leafIndex or mmrIndex");
|
|
128
|
+
}
|
|
129
|
+
const leafIdx = proof.leafIndex !== undefined ? proof.leafIndex : proof.mmrIndex;
|
|
130
|
+
const calculatedRoot = await calculateRoot(hasher, leafHash, proof, leafIdx);
|
|
131
|
+
return arraysEqual(calculatedRoot, root);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Verifies a consistency proof between two MMR states.
|
|
135
|
+
*
|
|
136
|
+
* WARNING — NOT IMPLEMENTED. This is a stub: it compares each root against
|
|
137
|
+
* itself and therefore ALWAYS returns true. It performs no real consistency
|
|
138
|
+
* check and MUST NOT be relied upon for security. No worker path currently
|
|
139
|
+
* calls it. See the reference `verify_consistency` (algorithms.py) for the
|
|
140
|
+
* intended algorithm (verify proof2 extends proof1 via position-committed
|
|
141
|
+
* interior hashing).
|
|
142
|
+
*
|
|
143
|
+
* @param hasher - Cryptographic hasher instance
|
|
144
|
+
* @param proof1 - Proof for the first state
|
|
145
|
+
* @param proof2 - Proof for the second state
|
|
146
|
+
* @param root1 - Root hash of the first state
|
|
147
|
+
* @param root2 - Root hash of the second state
|
|
148
|
+
* @returns Always true (stub) — do not use for verification
|
|
149
|
+
*/
|
|
150
|
+
export function verifyConsistency(hasher, proof1, proof2, root1, root2) {
|
|
151
|
+
// TODO(plan-0027): Implement full consistency check (verify proof2 extends
|
|
152
|
+
// proof1) with position-committed interior hashing. Until then this returns
|
|
153
|
+
// true unconditionally and must not be used for any trust decision.
|
|
154
|
+
return arraysEqual(root1, root1) && arraysEqual(root2, root2);
|
|
155
|
+
}
|