@forestrie/merklelog 0.1.1 → 0.2.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/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/mmr/algorithms.d.ts +49 -14
- package/dist/mmr/algorithms.d.ts.map +1 -1
- package/dist/mmr/algorithms.js +76 -18
- package/dist/mmr/proof.d.ts.map +1 -1
- package/dist/mmr/proof.js +7 -0
- package/package.json +1 -1
- package/src/index.ts +3 -0
- package/src/mmr/algorithms.ts +117 -23
- package/src/mmr/proof.ts +7 -0
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export { openMassifLeafIndex, MissingIndexError } from "./massifs/leafindex.js";
|
|
|
18
18
|
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
|
-
export { bagPeaks, calculateRoot, verifyInclusion, verifyConsistency, } from "./mmr/algorithms.js";
|
|
21
|
+
export { bagPeaks, calculateRoot, verifyInclusion, verifyConsistency, consistentRoots, indexConsistencyProof, type ConsistencyProof, } from "./mmr/algorithms.js";
|
|
22
22
|
export { createSyncHasher } from "./mmr/algorithms-sync.js";
|
|
23
23
|
export type { Proof, Peak } from "./mmr/types.js";
|
|
24
24
|
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,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,
|
|
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,qBAAqB,EACrB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,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,cAAc,EACd,WAAW,EACX,qBAAqB,EACrB,WAAW,EACX,YAAY,EACZ,uBAAuB,EACvB,YAAY,GACb,MAAM,gBAAgB,CAAC;AACxB,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -20,7 +20,7 @@ export { openMassifLeafIndex, MissingIndexError } from "./massifs/leafindex.js";
|
|
|
20
20
|
// Export mmr module
|
|
21
21
|
export { mmrIndex, massifFirstLeaf, leafMinusSpurSum } from "./mmr/index.js";
|
|
22
22
|
export { heightIndex, height, mmrIndexFromLeafIndex, leafIndex, mmrPosition, mmrSizeFromHeightIndex, leafCount, leafCountFromHeightIndex, } from "./mmr/math.js";
|
|
23
|
-
export { bagPeaks, calculateRoot, verifyInclusion, verifyConsistency, } from "./mmr/algorithms.js";
|
|
23
|
+
export { bagPeaks, calculateRoot, verifyInclusion, verifyConsistency, consistentRoots, indexConsistencyProof, } from "./mmr/algorithms.js";
|
|
24
24
|
export { createSyncHasher } from "./mmr/algorithms-sync.js";
|
|
25
25
|
// MMR proof building (pure; store-agnostic) — plan-2607-15 §4 (go: mmr/proof.go, peaks.go)
|
|
26
26
|
export { inclusionProof, peakMMRIndexes, peaksBitmap, peakIndexForLeafProof, indexHeight, firstMMRSize, massifIndexFromMMRIndex, peakStackMap, } from "./mmr/proof.js";
|
package/dist/mmr/algorithms.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
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 type { Proof, Hasher } from "./types.js";
|
|
14
|
+
import { type NodeGetter } from "./proof.js";
|
|
14
15
|
/**
|
|
15
16
|
* Bags peaks together to compute a single root hash.
|
|
16
17
|
*
|
|
@@ -59,21 +60,55 @@ export declare function calculateRoot(hasher: Hasher, leafHash: Uint8Array, proo
|
|
|
59
60
|
*/
|
|
60
61
|
export declare function verifyInclusion(hasher: Hasher, leafHash: Uint8Array, proof: Proof, root: Uint8Array): Promise<boolean>;
|
|
61
62
|
/**
|
|
62
|
-
*
|
|
63
|
+
* Consistency proof between two MMR states (draft-bryce `consistency-proof`,
|
|
64
|
+
* go-merklelog `ConsistencyProof`): one inclusion path per MMR(A) peak,
|
|
65
|
+
* proven in MMR(B). Verified against trusted accumulators for both sizes.
|
|
66
|
+
*/
|
|
67
|
+
export interface ConsistencyProof {
|
|
68
|
+
mmrSizeA: bigint;
|
|
69
|
+
mmrSizeB: bigint;
|
|
70
|
+
/** One sibling path per MMR(A) peak (ascending), in peak order. */
|
|
71
|
+
paths: Uint8Array[][];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Recover the MMR(B) accumulator prefix committed by the MMR(A) peaks
|
|
75
|
+
* (draft-bryce `consistent_roots`; go-merklelog `ConsistentRoots`).
|
|
63
76
|
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* intended algorithm (verify proof2 extends proof1 via position-committed
|
|
69
|
-
* interior hashing).
|
|
77
|
+
* Each MMR(A) peak is an interior node of MMR(B) at an immovable position;
|
|
78
|
+
* its inclusion path climbs to the covering MMR(B) peak. Consecutive
|
|
79
|
+
* duplicate roots collapse (many old peaks share one new peak). Requires
|
|
80
|
+
* one path per MMR(A) peak (draft: `len(peaks(ifrom)) == len(accumulatorfrom)`).
|
|
70
81
|
*
|
|
71
|
-
* @param
|
|
72
|
-
* @param
|
|
73
|
-
* @param
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
82
|
+
* @param ifrom - last node index of the complete MMR(A) (`mmrSizeA - 1`)
|
|
83
|
+
* @param accumulatorFrom - MMR(A) peak values, descending height order
|
|
84
|
+
* @param paths - inclusion path per peak, proven in MMR(B)
|
|
85
|
+
*/
|
|
86
|
+
export declare function consistentRoots(hasher: Hasher, ifrom: bigint, accumulatorFrom: Uint8Array[], paths: Uint8Array[][]): Promise<Uint8Array[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Verify MMR(A) is a committed prefix of MMR(B)
|
|
89
|
+
* (draft-bryce "Verifying the Receipt of consistency";
|
|
90
|
+
* go-merklelog `VerifyConsistency`).
|
|
91
|
+
*
|
|
92
|
+
* Replaces the plan-0027 always-true stub (FOR-368 Phase 1,
|
|
93
|
+
* plan-2607-29): the previous signature took two inclusion proofs and
|
|
94
|
+
* returned true unconditionally; no caller existed.
|
|
95
|
+
*
|
|
96
|
+
* @param proof - consistency proof `mmrSizeA -> mmrSizeB`
|
|
97
|
+
* @param peaksFrom - TRUSTED MMR(A) accumulator (e.g. a signed checkpoint
|
|
98
|
+
* payload), descending height order
|
|
99
|
+
* @param peaksTo - MMR(B) accumulator to prove against (e.g. an anchored
|
|
100
|
+
* on-chain state), descending height order
|
|
101
|
+
* @returns ok, with the MMR(B) accumulator on success
|
|
102
|
+
*/
|
|
103
|
+
export declare function verifyConsistency(hasher: Hasher, proof: ConsistencyProof, peaksFrom: Uint8Array[], peaksTo: Uint8Array[]): Promise<{
|
|
104
|
+
ok: boolean;
|
|
105
|
+
accumulator: Uint8Array[];
|
|
106
|
+
}>;
|
|
107
|
+
/**
|
|
108
|
+
* Generate a consistency proof `mmrIndexA -> mmrIndexB` from node data
|
|
109
|
+
* (go-merklelog `IndexConsistencyProof`): an inclusion path in MMR(B) for
|
|
110
|
+
* each MMR(A) peak. Node access is by MMR index (massif tiles or any
|
|
111
|
+
* replicated store).
|
|
77
112
|
*/
|
|
78
|
-
export declare function
|
|
113
|
+
export declare function indexConsistencyProof(get: NodeGetter, mmrIndexA: bigint, mmrIndexB: bigint): ConsistencyProof;
|
|
79
114
|
//# sourceMappingURL=algorithms.d.ts.map
|
|
@@ -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;
|
|
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,EAAkC,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAqB7E;;;;;;;;;;;;;;;;;;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;;;;;;;;;;;;GAYG;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;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,gBAAgB,EACvB,SAAS,EAAE,UAAU,EAAE,EACvB,OAAO,EAAE,UAAU,EAAE,GACpB,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,CAAC,CAwBrD;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
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* And the associated IETF draft specification:
|
|
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
|
+
import { inclusionProof, peakMMRIndexes } from "./proof.js";
|
|
13
14
|
import { Uint64 } from "../uint64/index.js";
|
|
14
15
|
import { heightIndex } from "./math.js";
|
|
15
16
|
import { arraysEqual } from "../utils/arrays.js";
|
|
@@ -131,25 +132,82 @@ export async function verifyInclusion(hasher, leafHash, proof, root) {
|
|
|
131
132
|
return arraysEqual(calculatedRoot, root);
|
|
132
133
|
}
|
|
133
134
|
/**
|
|
134
|
-
*
|
|
135
|
+
* Recover the MMR(B) accumulator prefix committed by the MMR(A) peaks
|
|
136
|
+
* (draft-bryce `consistent_roots`; go-merklelog `ConsistentRoots`).
|
|
135
137
|
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* intended algorithm (verify proof2 extends proof1 via position-committed
|
|
141
|
-
* interior hashing).
|
|
138
|
+
* Each MMR(A) peak is an interior node of MMR(B) at an immovable position;
|
|
139
|
+
* its inclusion path climbs to the covering MMR(B) peak. Consecutive
|
|
140
|
+
* duplicate roots collapse (many old peaks share one new peak). Requires
|
|
141
|
+
* one path per MMR(A) peak (draft: `len(peaks(ifrom)) == len(accumulatorfrom)`).
|
|
142
142
|
*
|
|
143
|
-
* @param
|
|
144
|
-
* @param
|
|
145
|
-
* @param
|
|
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
|
|
143
|
+
* @param ifrom - last node index of the complete MMR(A) (`mmrSizeA - 1`)
|
|
144
|
+
* @param accumulatorFrom - MMR(A) peak values, descending height order
|
|
145
|
+
* @param paths - inclusion path per peak, proven in MMR(B)
|
|
149
146
|
*/
|
|
150
|
-
export function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
147
|
+
export async function consistentRoots(hasher, ifrom, accumulatorFrom, paths) {
|
|
148
|
+
const fromPeaks = peakMMRIndexes(ifrom);
|
|
149
|
+
if (fromPeaks.length !== paths.length) {
|
|
150
|
+
throw new Error(`a proof for each accumulator peak is required: ${fromPeaks.length} peaks, ${paths.length} paths`);
|
|
151
|
+
}
|
|
152
|
+
if (accumulatorFrom.length !== fromPeaks.length) {
|
|
153
|
+
throw new Error(`accumulator length mismatch: ${accumulatorFrom.length} values for ${fromPeaks.length} peaks`);
|
|
154
|
+
}
|
|
155
|
+
const roots = [];
|
|
156
|
+
for (let i = 0; i < accumulatorFrom.length; i++) {
|
|
157
|
+
const root = await calculateRoot(hasher, accumulatorFrom[i], { path: paths[i], mmrIndex: fromPeaks[i] }, fromPeaks[i]);
|
|
158
|
+
if (roots.length > 0 && arraysEqual(roots[roots.length - 1], root)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
roots.push(root);
|
|
162
|
+
}
|
|
163
|
+
return roots;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Verify MMR(A) is a committed prefix of MMR(B)
|
|
167
|
+
* (draft-bryce "Verifying the Receipt of consistency";
|
|
168
|
+
* go-merklelog `VerifyConsistency`).
|
|
169
|
+
*
|
|
170
|
+
* Replaces the plan-0027 always-true stub (FOR-368 Phase 1,
|
|
171
|
+
* plan-2607-29): the previous signature took two inclusion proofs and
|
|
172
|
+
* returned true unconditionally; no caller existed.
|
|
173
|
+
*
|
|
174
|
+
* @param proof - consistency proof `mmrSizeA -> mmrSizeB`
|
|
175
|
+
* @param peaksFrom - TRUSTED MMR(A) accumulator (e.g. a signed checkpoint
|
|
176
|
+
* payload), descending height order
|
|
177
|
+
* @param peaksTo - MMR(B) accumulator to prove against (e.g. an anchored
|
|
178
|
+
* on-chain state), descending height order
|
|
179
|
+
* @returns ok, with the MMR(B) accumulator on success
|
|
180
|
+
*/
|
|
181
|
+
export async function verifyConsistency(hasher, proof, peaksFrom, peaksTo) {
|
|
182
|
+
const proven = await consistentRoots(hasher, proof.mmrSizeA - 1n, peaksFrom, proof.paths);
|
|
183
|
+
// Both lists are in descending height order, so every proven root must
|
|
184
|
+
// appear in order within peaksTo (a linear scan; go-merklelog semantics:
|
|
185
|
+
// a proven root matches the current peak or exactly the next one down).
|
|
186
|
+
let ito = 0;
|
|
187
|
+
for (const root of proven) {
|
|
188
|
+
if (ito < peaksTo.length && arraysEqual(peaksTo[ito], root)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
ito += 1;
|
|
192
|
+
if (ito >= peaksTo.length || !arraysEqual(peaksTo[ito], root)) {
|
|
193
|
+
return { ok: false, accumulator: [] };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// The full MMR(B) accumulator is the proven prefix plus any right-peaks;
|
|
197
|
+
// returning peaksTo is safe because consistentRoots enforced one proof
|
|
198
|
+
// per MMR(A) peak (see go-merklelog VerifyConsistency).
|
|
199
|
+
return { ok: true, accumulator: peaksTo };
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Generate a consistency proof `mmrIndexA -> mmrIndexB` from node data
|
|
203
|
+
* (go-merklelog `IndexConsistencyProof`): an inclusion path in MMR(B) for
|
|
204
|
+
* each MMR(A) peak. Node access is by MMR index (massif tiles or any
|
|
205
|
+
* replicated store).
|
|
206
|
+
*/
|
|
207
|
+
export function indexConsistencyProof(get, mmrIndexA, mmrIndexB) {
|
|
208
|
+
const paths = [];
|
|
209
|
+
for (const peak of peakMMRIndexes(mmrIndexA)) {
|
|
210
|
+
paths.push(inclusionProof(get, mmrIndexB, peak));
|
|
211
|
+
}
|
|
212
|
+
return { mmrSizeA: mmrIndexA + 1n, mmrSizeB: mmrIndexB + 1n, paths };
|
|
155
213
|
}
|
package/dist/mmr/proof.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../src/mmr/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"proof.d.ts","sourceRoot":"","sources":["../../src/mmr/proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,EAAE,MAAM,KAAK,UAAU,CAAC;AA2CnD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,UAAU,EACf,YAAY,EAAE,MAAM,EACpB,CAAC,EAAE,MAAM,GACR,UAAU,EAAE,CAyBd;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAcnD;AAaD;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,MAAM,CAIR;AAQD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAczD;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAUrD;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,CAAC,EAAE,MAAM,GACR,MAAM,CAKR;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWrB"}
|
package/dist/mmr/proof.js
CHANGED
|
@@ -35,6 +35,13 @@ function jumpLeftPerfect(pos) {
|
|
|
35
35
|
* (go-merklelog `mmr/indexheight.go` PosHeight)
|
|
36
36
|
*/
|
|
37
37
|
function posHeight(pos) {
|
|
38
|
+
// Positions are 1-based; `pos <= 0` is out of domain and would spin forever
|
|
39
|
+
// (`jumpLeftPerfect(0) === 0`, `allOnes(0) === false`). Reject in bounded
|
|
40
|
+
// time so a malformed size fed to any caller (peakMMRIndexes, indexHeight,
|
|
41
|
+
// inclusionProof, …) fails loudly instead of hanging the verifier (FOR-414).
|
|
42
|
+
if (pos <= 0n) {
|
|
43
|
+
throw new Error(`posHeight: position must be >= 1, got ${pos}`);
|
|
44
|
+
}
|
|
38
45
|
let current = pos;
|
|
39
46
|
while (!allOnes(current)) {
|
|
40
47
|
current = jumpLeftPerfect(current);
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -70,6 +70,9 @@ export {
|
|
|
70
70
|
calculateRoot,
|
|
71
71
|
verifyInclusion,
|
|
72
72
|
verifyConsistency,
|
|
73
|
+
consistentRoots,
|
|
74
|
+
indexConsistencyProof,
|
|
75
|
+
type ConsistencyProof,
|
|
73
76
|
} from "./mmr/algorithms.js";
|
|
74
77
|
export { createSyncHasher } from "./mmr/algorithms-sync.js";
|
|
75
78
|
export type { Proof, Peak } from "./mmr/types.js";
|
package/src/mmr/algorithms.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import type { Proof, Hasher } from "./types.js";
|
|
15
|
+
import { inclusionProof, peakMMRIndexes, type NodeGetter } from "./proof.js";
|
|
15
16
|
import { Uint64 } from "../uint64/index.js";
|
|
16
17
|
import { heightIndex } from "./math.js";
|
|
17
18
|
import { arraysEqual } from "../utils/arrays.js";
|
|
@@ -164,31 +165,124 @@ export async function verifyInclusion(
|
|
|
164
165
|
}
|
|
165
166
|
|
|
166
167
|
/**
|
|
167
|
-
*
|
|
168
|
+
* Consistency proof between two MMR states (draft-bryce `consistency-proof`,
|
|
169
|
+
* go-merklelog `ConsistencyProof`): one inclusion path per MMR(A) peak,
|
|
170
|
+
* proven in MMR(B). Verified against trusted accumulators for both sizes.
|
|
171
|
+
*/
|
|
172
|
+
export interface ConsistencyProof {
|
|
173
|
+
mmrSizeA: bigint;
|
|
174
|
+
mmrSizeB: bigint;
|
|
175
|
+
/** One sibling path per MMR(A) peak (ascending), in peak order. */
|
|
176
|
+
paths: Uint8Array[][];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Recover the MMR(B) accumulator prefix committed by the MMR(A) peaks
|
|
181
|
+
* (draft-bryce `consistent_roots`; go-merklelog `ConsistentRoots`).
|
|
168
182
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
* intended algorithm (verify proof2 extends proof1 via position-committed
|
|
174
|
-
* interior hashing).
|
|
183
|
+
* Each MMR(A) peak is an interior node of MMR(B) at an immovable position;
|
|
184
|
+
* its inclusion path climbs to the covering MMR(B) peak. Consecutive
|
|
185
|
+
* duplicate roots collapse (many old peaks share one new peak). Requires
|
|
186
|
+
* one path per MMR(A) peak (draft: `len(peaks(ifrom)) == len(accumulatorfrom)`).
|
|
175
187
|
*
|
|
176
|
-
* @param
|
|
177
|
-
* @param
|
|
178
|
-
* @param
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
188
|
+
* @param ifrom - last node index of the complete MMR(A) (`mmrSizeA - 1`)
|
|
189
|
+
* @param accumulatorFrom - MMR(A) peak values, descending height order
|
|
190
|
+
* @param paths - inclusion path per peak, proven in MMR(B)
|
|
191
|
+
*/
|
|
192
|
+
export async function consistentRoots(
|
|
193
|
+
hasher: Hasher,
|
|
194
|
+
ifrom: bigint,
|
|
195
|
+
accumulatorFrom: Uint8Array[],
|
|
196
|
+
paths: Uint8Array[][],
|
|
197
|
+
): Promise<Uint8Array[]> {
|
|
198
|
+
const fromPeaks = peakMMRIndexes(ifrom);
|
|
199
|
+
if (fromPeaks.length !== paths.length) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
`a proof for each accumulator peak is required: ${fromPeaks.length} peaks, ${paths.length} paths`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (accumulatorFrom.length !== fromPeaks.length) {
|
|
205
|
+
throw new Error(
|
|
206
|
+
`accumulator length mismatch: ${accumulatorFrom.length} values for ${fromPeaks.length} peaks`,
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
const roots: Uint8Array[] = [];
|
|
210
|
+
for (let i = 0; i < accumulatorFrom.length; i++) {
|
|
211
|
+
const root = await calculateRoot(
|
|
212
|
+
hasher,
|
|
213
|
+
accumulatorFrom[i],
|
|
214
|
+
{ path: paths[i], mmrIndex: fromPeaks[i] },
|
|
215
|
+
fromPeaks[i],
|
|
216
|
+
);
|
|
217
|
+
if (roots.length > 0 && arraysEqual(roots[roots.length - 1], root)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
roots.push(root);
|
|
221
|
+
}
|
|
222
|
+
return roots;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Verify MMR(A) is a committed prefix of MMR(B)
|
|
227
|
+
* (draft-bryce "Verifying the Receipt of consistency";
|
|
228
|
+
* go-merklelog `VerifyConsistency`).
|
|
229
|
+
*
|
|
230
|
+
* Replaces the plan-0027 always-true stub (FOR-368 Phase 1,
|
|
231
|
+
* plan-2607-29): the previous signature took two inclusion proofs and
|
|
232
|
+
* returned true unconditionally; no caller existed.
|
|
233
|
+
*
|
|
234
|
+
* @param proof - consistency proof `mmrSizeA -> mmrSizeB`
|
|
235
|
+
* @param peaksFrom - TRUSTED MMR(A) accumulator (e.g. a signed checkpoint
|
|
236
|
+
* payload), descending height order
|
|
237
|
+
* @param peaksTo - MMR(B) accumulator to prove against (e.g. an anchored
|
|
238
|
+
* on-chain state), descending height order
|
|
239
|
+
* @returns ok, with the MMR(B) accumulator on success
|
|
182
240
|
*/
|
|
183
|
-
export function verifyConsistency(
|
|
241
|
+
export async function verifyConsistency(
|
|
184
242
|
hasher: Hasher,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
243
|
+
proof: ConsistencyProof,
|
|
244
|
+
peaksFrom: Uint8Array[],
|
|
245
|
+
peaksTo: Uint8Array[],
|
|
246
|
+
): Promise<{ ok: boolean; accumulator: Uint8Array[] }> {
|
|
247
|
+
const proven = await consistentRoots(
|
|
248
|
+
hasher,
|
|
249
|
+
proof.mmrSizeA - 1n,
|
|
250
|
+
peaksFrom,
|
|
251
|
+
proof.paths,
|
|
252
|
+
);
|
|
253
|
+
// Both lists are in descending height order, so every proven root must
|
|
254
|
+
// appear in order within peaksTo (a linear scan; go-merklelog semantics:
|
|
255
|
+
// a proven root matches the current peak or exactly the next one down).
|
|
256
|
+
let ito = 0;
|
|
257
|
+
for (const root of proven) {
|
|
258
|
+
if (ito < peaksTo.length && arraysEqual(peaksTo[ito], root)) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
ito += 1;
|
|
262
|
+
if (ito >= peaksTo.length || !arraysEqual(peaksTo[ito], root)) {
|
|
263
|
+
return { ok: false, accumulator: [] };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// The full MMR(B) accumulator is the proven prefix plus any right-peaks;
|
|
267
|
+
// returning peaksTo is safe because consistentRoots enforced one proof
|
|
268
|
+
// per MMR(A) peak (see go-merklelog VerifyConsistency).
|
|
269
|
+
return { ok: true, accumulator: peaksTo };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Generate a consistency proof `mmrIndexA -> mmrIndexB` from node data
|
|
274
|
+
* (go-merklelog `IndexConsistencyProof`): an inclusion path in MMR(B) for
|
|
275
|
+
* each MMR(A) peak. Node access is by MMR index (massif tiles or any
|
|
276
|
+
* replicated store).
|
|
277
|
+
*/
|
|
278
|
+
export function indexConsistencyProof(
|
|
279
|
+
get: NodeGetter,
|
|
280
|
+
mmrIndexA: bigint,
|
|
281
|
+
mmrIndexB: bigint,
|
|
282
|
+
): ConsistencyProof {
|
|
283
|
+
const paths: Uint8Array[][] = [];
|
|
284
|
+
for (const peak of peakMMRIndexes(mmrIndexA)) {
|
|
285
|
+
paths.push(inclusionProof(get, mmrIndexB, peak));
|
|
286
|
+
}
|
|
287
|
+
return { mmrSizeA: mmrIndexA + 1n, mmrSizeB: mmrIndexB + 1n, paths };
|
|
194
288
|
}
|
package/src/mmr/proof.ts
CHANGED
|
@@ -40,6 +40,13 @@ function jumpLeftPerfect(pos: bigint): bigint {
|
|
|
40
40
|
* (go-merklelog `mmr/indexheight.go` PosHeight)
|
|
41
41
|
*/
|
|
42
42
|
function posHeight(pos: bigint): number {
|
|
43
|
+
// Positions are 1-based; `pos <= 0` is out of domain and would spin forever
|
|
44
|
+
// (`jumpLeftPerfect(0) === 0`, `allOnes(0) === false`). Reject in bounded
|
|
45
|
+
// time so a malformed size fed to any caller (peakMMRIndexes, indexHeight,
|
|
46
|
+
// inclusionProof, …) fails loudly instead of hanging the verifier (FOR-414).
|
|
47
|
+
if (pos <= 0n) {
|
|
48
|
+
throw new Error(`posHeight: position must be >= 1, got ${pos}`);
|
|
49
|
+
}
|
|
43
50
|
let current = pos;
|
|
44
51
|
while (!allOnes(current)) {
|
|
45
52
|
current = jumpLeftPerfect(current);
|