@forestrie/receipt-verify 2.1.0 → 2.2.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/checkpoint-chain.d.ts +25 -5
- package/dist/checkpoint-chain.d.ts.map +1 -1
- package/dist/checkpoint-chain.js +71 -7
- package/dist/freshen-receipt.d.ts +8 -4
- package/dist/freshen-receipt.d.ts.map +1 -1
- package/dist/freshen-receipt.js +18 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/checkpoint-chain.ts +72 -7
- package/src/freshen-receipt.ts +23 -4
- package/src/index.ts +1 -0
|
@@ -39,6 +39,21 @@ export declare class CheckpointSignedSizeMismatchError extends Error {
|
|
|
39
39
|
export declare class CheckpointHighSSignatureError extends Error {
|
|
40
40
|
constructor(message: string);
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* The checkpoint's protected header carries no integer `alg` (label 1).
|
|
44
|
+
*
|
|
45
|
+
* The univocity contract rejects such a header outright — the structural
|
|
46
|
+
* walk finds the size, and the `alg` requirement in the same call raises
|
|
47
|
+
* `ClaimNotFound(1)` or `UnexpectedMajorType`. Off-chain the header used to
|
|
48
|
+
* read as "no algorithm stated", which both turned OFF the high-s rejection
|
|
49
|
+
* below (gated on the algorithm being ES256) and still yielded a signed size
|
|
50
|
+
* to fold from — so a checkpoint the chain will never anchor verified here
|
|
51
|
+
* under weaker rules than a well-formed one (review finding S-1). Reported
|
|
52
|
+
* as `"proof_malformed"` by {@link verifyCheckpointChain}.
|
|
53
|
+
*/
|
|
54
|
+
export declare class CheckpointProtectedHeaderAlgError extends Error {
|
|
55
|
+
constructor(message: string);
|
|
56
|
+
}
|
|
42
57
|
/**
|
|
43
58
|
* Decode the embedded consistency proof (`vdp` 396 key -2) and require its
|
|
44
59
|
* declared `tree-size-2` to equal the checkpoint's SIGNED `tree-size-2` from
|
|
@@ -61,6 +76,8 @@ export declare class CheckpointHighSSignatureError extends Error {
|
|
|
61
76
|
* differs from the declared proof's tree-size-2
|
|
62
77
|
* @throws {CheckpointHighSSignatureError} when the checkpoint is ES256-signed
|
|
63
78
|
* with a high-s (malleable) signature
|
|
79
|
+
* @throws {CheckpointProtectedHeaderAlgError} when the protected header
|
|
80
|
+
* carries no integer `alg` (label 1)
|
|
64
81
|
*/
|
|
65
82
|
export declare function checkpointConsistencyProof(checkpointBytes: Uint8Array): CheckpointConsistencyProof;
|
|
66
83
|
/**
|
|
@@ -136,11 +153,14 @@ export type CheckpointChainResult = {
|
|
|
136
153
|
* as the fold's starting accumulator (a suffix chain rooted in an
|
|
137
154
|
* already-trusted accumulator supplies both).
|
|
138
155
|
* - A supplied `trustedBase` must describe a state an MMR can be in: its
|
|
139
|
-
* `size` a complete MMR size,
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* count no MMR has
|
|
156
|
+
* `size` a complete MMR size, its `accumulator` holding one peak per peak
|
|
157
|
+
* of that size, and every one of those peaks a 32-byte node value.
|
|
158
|
+
* `peaksBitmap` rounds an incomplete size DOWN to the largest MMR below
|
|
159
|
+
* it, so without the completeness check a size of 5 folds the 4 -> N shape
|
|
160
|
+
* while every link reports a base of 5 — a node count no MMR has; and
|
|
161
|
+
* without the byte-length check an origin peak of any length is copied
|
|
162
|
+
* through the empty-path branch into the detached payload. All three are
|
|
163
|
+
* `proof_malformed`.
|
|
144
164
|
* - The first link's declared `tree-size-1` must equal that trusted
|
|
145
165
|
* starting size, and every subsequent link's must equal the previous
|
|
146
166
|
* link's sealed `tree-size-2`; either disagreement is `size_mismatch`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint-chain.d.ts","sourceRoot":"","sources":["../src/checkpoint-chain.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"checkpoint-chain.d.ts","sourceRoot":"","sources":["../src/checkpoint-chain.ts"],"names":[],"mappings":"AAyDA;;;;qBAIqB;AACrB,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;+EAC2E;IAC3E,eAAe,EAAE,MAAM,CAAC;IACxB,sEAAsE;IACtE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;IACtB,uEAAuE;IACvE,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;gBAC1C,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,iCAAkC,SAAQ,KAAK;gBAC9C,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,0BAA0B,CACxC,eAAe,EAAE,UAAU,GAC1B,0BAA0B,CA6C5B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,0BAA0B,EACjC,eAAe,EAAE,UAAU,EAAE,EAC7B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,EAAE,CAAC,CAoBvB;AAED;+DAC+D;AAC/D,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,UAAU,CAQxE;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB;mBACe;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,2EAA2E;IAC3E,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAAC,WAAW,EAAE,UAAU,EAAE,CAAA;CAAE,GACrE;IACE,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EACF,aAAa,GACb,WAAW;IACb;;;;;OAKG;OACD,qBAAqB,GACrB,iBAAiB;IACnB;;;;;;;;OAQG;OACD,eAAe,CAAC;IACpB,yCAAyC;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,eAAe,EAAE,CACf,eAAe,EAAE,UAAU,EAC3B,eAAe,EAAE,UAAU,KACxB,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB;+EAC2E;IAC3E,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC;CAC3D,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAuJjC"}
|
package/dist/checkpoint-chain.js
CHANGED
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
* the key, so no reason string chosen from it may mean anything more than
|
|
39
39
|
* "these two sizes differ".
|
|
40
40
|
*/
|
|
41
|
-
import { COSE_ALG_ES256,
|
|
41
|
+
import { COSE_ALG_ES256, ProtectedHeaderAlgError, isLowS, readProtectedAlg, readProtectedTreeSize2, } from "@forestrie/encoding";
|
|
42
42
|
import { consistentRootsForSizes, mmrSizeForLeafCount, peakMMRIndexes, peaksBitmap, } from "@forestrie/merklelog";
|
|
43
43
|
import { SubtleHasher } from "./subtle-hasher.js";
|
|
44
44
|
import { parseCheckpoint } from "./build-receipt-offline.js";
|
|
@@ -74,6 +74,24 @@ export class CheckpointHighSSignatureError extends Error {
|
|
|
74
74
|
this.name = "CheckpointHighSSignatureError";
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* The checkpoint's protected header carries no integer `alg` (label 1).
|
|
79
|
+
*
|
|
80
|
+
* The univocity contract rejects such a header outright — the structural
|
|
81
|
+
* walk finds the size, and the `alg` requirement in the same call raises
|
|
82
|
+
* `ClaimNotFound(1)` or `UnexpectedMajorType`. Off-chain the header used to
|
|
83
|
+
* read as "no algorithm stated", which both turned OFF the high-s rejection
|
|
84
|
+
* below (gated on the algorithm being ES256) and still yielded a signed size
|
|
85
|
+
* to fold from — so a checkpoint the chain will never anchor verified here
|
|
86
|
+
* under weaker rules than a well-formed one (review finding S-1). Reported
|
|
87
|
+
* as `"proof_malformed"` by {@link verifyCheckpointChain}.
|
|
88
|
+
*/
|
|
89
|
+
export class CheckpointProtectedHeaderAlgError extends Error {
|
|
90
|
+
constructor(message) {
|
|
91
|
+
super(message);
|
|
92
|
+
this.name = "CheckpointProtectedHeaderAlgError";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
77
95
|
/**
|
|
78
96
|
* Decode the embedded consistency proof (`vdp` 396 key -2) and require its
|
|
79
97
|
* declared `tree-size-2` to equal the checkpoint's SIGNED `tree-size-2` from
|
|
@@ -96,10 +114,24 @@ export class CheckpointHighSSignatureError extends Error {
|
|
|
96
114
|
* differs from the declared proof's tree-size-2
|
|
97
115
|
* @throws {CheckpointHighSSignatureError} when the checkpoint is ES256-signed
|
|
98
116
|
* with a high-s (malleable) signature
|
|
117
|
+
* @throws {CheckpointProtectedHeaderAlgError} when the protected header
|
|
118
|
+
* carries no integer `alg` (label 1)
|
|
99
119
|
*/
|
|
100
120
|
export function checkpointConsistencyProof(checkpointBytes) {
|
|
101
121
|
const { coseSign1, unprotected } = parseCheckpoint(checkpointBytes);
|
|
102
|
-
|
|
122
|
+
// Strict: a header with no integer alg is one the contract rejects, and
|
|
123
|
+
// reading it leniently would switch the high-s rejection below off while
|
|
124
|
+
// the signed size was still taken from it (S-1).
|
|
125
|
+
let alg;
|
|
126
|
+
try {
|
|
127
|
+
alg = readProtectedAlg(coseSign1[0]);
|
|
128
|
+
}
|
|
129
|
+
catch (err) {
|
|
130
|
+
if (err instanceof ProtectedHeaderAlgError) {
|
|
131
|
+
throw new CheckpointProtectedHeaderAlgError(`checkpoint protected header carries no integer alg (label 1): ${err.message}`);
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
103
135
|
const signature = coseSign1[3];
|
|
104
136
|
if (alg === COSE_ALG_ES256 && signature.length === 64 && !isLowS(signature)) {
|
|
105
137
|
throw new CheckpointHighSSignatureError("checkpoint ES256 signature is not low-s canonical (s > n/2); rejected " +
|
|
@@ -176,11 +208,14 @@ export function accumulatorPayload(accumulator) {
|
|
|
176
208
|
* as the fold's starting accumulator (a suffix chain rooted in an
|
|
177
209
|
* already-trusted accumulator supplies both).
|
|
178
210
|
* - A supplied `trustedBase` must describe a state an MMR can be in: its
|
|
179
|
-
* `size` a complete MMR size,
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* count no MMR has
|
|
211
|
+
* `size` a complete MMR size, its `accumulator` holding one peak per peak
|
|
212
|
+
* of that size, and every one of those peaks a 32-byte node value.
|
|
213
|
+
* `peaksBitmap` rounds an incomplete size DOWN to the largest MMR below
|
|
214
|
+
* it, so without the completeness check a size of 5 folds the 4 -> N shape
|
|
215
|
+
* while every link reports a base of 5 — a node count no MMR has; and
|
|
216
|
+
* without the byte-length check an origin peak of any length is copied
|
|
217
|
+
* through the empty-path branch into the detached payload. All three are
|
|
218
|
+
* `proof_malformed`.
|
|
184
219
|
* - The first link's declared `tree-size-1` must equal that trusted
|
|
185
220
|
* starting size, and every subsequent link's must equal the previous
|
|
186
221
|
* link's sealed `tree-size-2`; either disagreement is `size_mismatch`.
|
|
@@ -240,6 +275,25 @@ export async function verifyCheckpointChain(opts) {
|
|
|
240
275
|
links,
|
|
241
276
|
};
|
|
242
277
|
}
|
|
278
|
+
// …and every peak must be a 32-byte node value, the check arbor's
|
|
279
|
+
// producer applies to both path elements and right-peaks (`toNode32`).
|
|
280
|
+
// The count alone does not reach it: on the empty-path branch an origin
|
|
281
|
+
// peak is copied into the result verbatim, so a 0/31/33/64-byte peak
|
|
282
|
+
// reaches `accumulatorPayload` and shortens or lengthens the detached
|
|
283
|
+
// payload, with only the signature left to reject it (review finding
|
|
284
|
+
// I2, canopy C6).
|
|
285
|
+
for (let i = 0; i < trustedBase.accumulator.length; i++) {
|
|
286
|
+
const peak = trustedBase.accumulator[i];
|
|
287
|
+
if (!(peak instanceof Uint8Array) || peak.length !== 32) {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
reason: "proof_malformed",
|
|
291
|
+
at: 0,
|
|
292
|
+
detail: `trusted base accumulator peak ${i} is not a 32-byte node value (${describePeak(peak)})`,
|
|
293
|
+
links,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
243
297
|
}
|
|
244
298
|
let accumulator = trustedBase?.accumulator ?? [];
|
|
245
299
|
let expectedBase = trustedBase?.size ?? 0n;
|
|
@@ -322,3 +376,13 @@ export async function verifyCheckpointChain(opts) {
|
|
|
322
376
|
}
|
|
323
377
|
return { ok: true, links, accumulator };
|
|
324
378
|
}
|
|
379
|
+
/** Name what was found where a 32-byte accumulator peak was required. */
|
|
380
|
+
function describePeak(peak) {
|
|
381
|
+
if (peak instanceof Uint8Array)
|
|
382
|
+
return `${peak.length} bytes`;
|
|
383
|
+
if (peak === null)
|
|
384
|
+
return "null";
|
|
385
|
+
if (Array.isArray(peak))
|
|
386
|
+
return "an array";
|
|
387
|
+
return typeof peak;
|
|
388
|
+
}
|
|
@@ -16,7 +16,9 @@ export type FreshenReceiptInput = {
|
|
|
16
16
|
leafValue: Uint8Array;
|
|
17
17
|
/** Consistency-proof chain covering [0 or the trusted base] → the latest
|
|
18
18
|
* sealed size, in ascending contiguous order (the raw per-checkpoint
|
|
19
|
-
* proofs, with `paths`).
|
|
19
|
+
* proofs, with `paths`). Every link's `signedTreeSize2` must equal its
|
|
20
|
+
* `treeSize2`, as `checkpointConsistencyProof` requires of the checkpoint
|
|
21
|
+
* it decoded each link from. The chain's last link must end at the
|
|
20
22
|
* checkpoint's sealed size. */
|
|
21
23
|
consistencyProofs: readonly CheckpointConsistencyProof[];
|
|
22
24
|
/** Trusted base for a suffix chain — the size the caller already trusts
|
|
@@ -40,9 +42,11 @@ export type FreshenReceiptResult = {
|
|
|
40
42
|
sealedSize: bigint;
|
|
41
43
|
};
|
|
42
44
|
/**
|
|
43
|
-
* Freshen a stale receipt to the latest sealed state. Throws if
|
|
44
|
-
*
|
|
45
|
-
*
|
|
45
|
+
* Freshen a stale receipt to the latest sealed state. Throws if a link's
|
|
46
|
+
* signed `tree-size-2` disagrees with its declared one
|
|
47
|
+
* ({@link CheckpointSignedSizeMismatchError}), if the chain is not a
|
|
48
|
+
* contiguous cover from the trusted base to the checkpoint's sealed size, if
|
|
49
|
+
* a climb node is missing from the supplied proofs, or if the recomputed
|
|
46
50
|
* peak does not match the folded latest accumulator.
|
|
47
51
|
*/
|
|
48
52
|
export declare function freshenReceipt(input: FreshenReceiptInput): Promise<FreshenReceiptResult>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"freshen-receipt.d.ts","sourceRoot":"","sources":["../src/freshen-receipt.ts"],"names":[],"mappings":"AA2DA,OAAO,
|
|
1
|
+
{"version":3,"file":"freshen-receipt.d.ts","sourceRoot":"","sources":["../src/freshen-receipt.ts"],"names":[],"mappings":"AA2DA,OAAO,EAGL,KAAK,0BAA0B,EAChC,MAAM,uBAAuB,CAAC;AAI/B;;;;;;;;GAQG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,iEAAiE;IACjE,eAAe,EAAE,UAAU,CAAC;IAC5B;uEACmE;IACnE,SAAS,EAAE,UAAU,CAAC;IACtB;;;;;mCAK+B;IAC/B,iBAAiB,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACzD;;;;;kDAK8C;IAC9C,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC;IAC1D;wDACoD;IACpD,qBAAqB,EAAE,UAAU,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,wEAAwE;IACxE,OAAO,EAAE,UAAU,CAAC;IACpB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AASF;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAsL/B"}
|
package/dist/freshen-receipt.js
CHANGED
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
*/
|
|
48
48
|
import { calculateRoot, inclusionProofPath, mmrSizeForLeafCount, peakIndexForLeafProof, peakMMRIndexes, peaksBitmap, } from "@forestrie/merklelog";
|
|
49
49
|
import { assembleReceiptFromProof, parseCheckpoint, } from "./build-receipt-offline.js";
|
|
50
|
-
import { computeCheckpointAccumulator, } from "./checkpoint-chain.js";
|
|
50
|
+
import { CheckpointSignedSizeMismatchError, computeCheckpointAccumulator, } from "./checkpoint-chain.js";
|
|
51
51
|
import { parseReceipt } from "./parse-receipt.js";
|
|
52
52
|
import { SubtleHasher } from "./subtle-hasher.js";
|
|
53
53
|
function bytesEqual(a, b) {
|
|
@@ -59,9 +59,11 @@ function bytesEqual(a, b) {
|
|
|
59
59
|
return x === 0;
|
|
60
60
|
}
|
|
61
61
|
/**
|
|
62
|
-
* Freshen a stale receipt to the latest sealed state. Throws if
|
|
63
|
-
*
|
|
64
|
-
*
|
|
62
|
+
* Freshen a stale receipt to the latest sealed state. Throws if a link's
|
|
63
|
+
* signed `tree-size-2` disagrees with its declared one
|
|
64
|
+
* ({@link CheckpointSignedSizeMismatchError}), if the chain is not a
|
|
65
|
+
* contiguous cover from the trusted base to the checkpoint's sealed size, if
|
|
66
|
+
* a climb node is missing from the supplied proofs, or if the recomputed
|
|
65
67
|
* peak does not match the folded latest accumulator.
|
|
66
68
|
*/
|
|
67
69
|
export async function freshenReceipt(input) {
|
|
@@ -106,6 +108,18 @@ export async function freshenReceipt(input) {
|
|
|
106
108
|
if (baseAccumulator.length !== basePeaks) {
|
|
107
109
|
throw new Error(`base accumulator has ${baseAccumulator.length} peaks; trusted base size ${baseSize} has ${basePeaks}`);
|
|
108
110
|
}
|
|
111
|
+
// Every link's SIGNED tree-size-2 must equal its declared one — the same
|
|
112
|
+
// equality `checkpointConsistencyProof` enforces when it decodes a
|
|
113
|
+
// checkpoint (ADR-0066 D1 as amended, D5.5). These links arrive already
|
|
114
|
+
// decoded, so nothing here had re-read the field the type says was pinned
|
|
115
|
+
// to the signature, and a link carrying `signedTreeSize2: 999n` beside
|
|
116
|
+
// `treeSize2: 7n` freshened (review finding I5).
|
|
117
|
+
for (let i = 0; i < links.length; i++) {
|
|
118
|
+
const link = links[i];
|
|
119
|
+
if (link.signedTreeSize2 !== link.treeSize2) {
|
|
120
|
+
throw new CheckpointSignedSizeMismatchError(`consistency proof ${i}: signed tree-size-2 (-65933) ${link.signedTreeSize2} != declared consistency-proof tree-size-2 ${link.treeSize2}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
109
123
|
// The first link must continue from that size, not from a size it names
|
|
110
124
|
// itself (ADR-0066 D5.4).
|
|
111
125
|
if (firstLink.treeSize1 !== baseSize) {
|
package/dist/index.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export { findGrantLeafInMassif } from "./find-grant-leaf.js";
|
|
|
76
76
|
export type { LocatedLeaf } from "./find-grant-leaf.js";
|
|
77
77
|
/** Re-exported so callers of findGrantLeafInMassif can catch it (FOR-344). */
|
|
78
78
|
export { MissingIndexError } from "@forestrie/merklelog";
|
|
79
|
-
export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointSignedSizeMismatchError, type CheckpointChainLink, type CheckpointChainResult, type CheckpointConsistencyProof, } from "./checkpoint-chain.js";
|
|
79
|
+
export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointProtectedHeaderAlgError, CheckpointSignedSizeMismatchError, type CheckpointChainLink, type CheckpointChainResult, type CheckpointConsistencyProof, } from "./checkpoint-chain.js";
|
|
80
80
|
/**
|
|
81
81
|
* Univocity leaf commitment hash. Was CLI-private (forestrie-cli's own
|
|
82
82
|
* mirror, "hoist to the library when the FOR-297 multi-hop resolver lands");
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ export { grantCommitmentHashFromGrant } from "./grant-commitment.js";
|
|
|
61
61
|
export { findGrantLeafInMassif } from "./find-grant-leaf.js";
|
|
62
62
|
/** Re-exported so callers of findGrantLeafInMassif can catch it (FOR-344). */
|
|
63
63
|
export { MissingIndexError } from "@forestrie/merklelog";
|
|
64
|
-
export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointSignedSizeMismatchError, } from "./checkpoint-chain.js";
|
|
64
|
+
export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointProtectedHeaderAlgError, CheckpointSignedSizeMismatchError, } from "./checkpoint-chain.js";
|
|
65
65
|
/**
|
|
66
66
|
* Univocity leaf commitment hash. Was CLI-private (forestrie-cli's own
|
|
67
67
|
* mirror, "hoist to the library when the FOR-297 multi-hop resolver lands");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forestrie/receipt-verify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Offline SCITT grant receipt verification (ADR-0045)",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@noble/hashes": "^1.7.1",
|
|
31
31
|
"@forestrie/chain-rpc": "0.3.0",
|
|
32
|
-
"@forestrie/
|
|
33
|
-
"@forestrie/
|
|
32
|
+
"@forestrie/encoding": "0.9.0",
|
|
33
|
+
"@forestrie/merklelog": "0.5.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"esbuild": "^0.24.0",
|
package/src/checkpoint-chain.ts
CHANGED
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import {
|
|
42
42
|
COSE_ALG_ES256,
|
|
43
|
-
|
|
43
|
+
ProtectedHeaderAlgError,
|
|
44
44
|
isLowS,
|
|
45
|
+
readProtectedAlg,
|
|
45
46
|
readProtectedTreeSize2,
|
|
46
47
|
} from "@forestrie/encoding";
|
|
47
48
|
import {
|
|
@@ -104,6 +105,25 @@ export class CheckpointHighSSignatureError extends Error {
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* The checkpoint's protected header carries no integer `alg` (label 1).
|
|
110
|
+
*
|
|
111
|
+
* The univocity contract rejects such a header outright — the structural
|
|
112
|
+
* walk finds the size, and the `alg` requirement in the same call raises
|
|
113
|
+
* `ClaimNotFound(1)` or `UnexpectedMajorType`. Off-chain the header used to
|
|
114
|
+
* read as "no algorithm stated", which both turned OFF the high-s rejection
|
|
115
|
+
* below (gated on the algorithm being ES256) and still yielded a signed size
|
|
116
|
+
* to fold from — so a checkpoint the chain will never anchor verified here
|
|
117
|
+
* under weaker rules than a well-formed one (review finding S-1). Reported
|
|
118
|
+
* as `"proof_malformed"` by {@link verifyCheckpointChain}.
|
|
119
|
+
*/
|
|
120
|
+
export class CheckpointProtectedHeaderAlgError extends Error {
|
|
121
|
+
constructor(message: string) {
|
|
122
|
+
super(message);
|
|
123
|
+
this.name = "CheckpointProtectedHeaderAlgError";
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
107
127
|
/**
|
|
108
128
|
* Decode the embedded consistency proof (`vdp` 396 key -2) and require its
|
|
109
129
|
* declared `tree-size-2` to equal the checkpoint's SIGNED `tree-size-2` from
|
|
@@ -126,12 +146,27 @@ export class CheckpointHighSSignatureError extends Error {
|
|
|
126
146
|
* differs from the declared proof's tree-size-2
|
|
127
147
|
* @throws {CheckpointHighSSignatureError} when the checkpoint is ES256-signed
|
|
128
148
|
* with a high-s (malleable) signature
|
|
149
|
+
* @throws {CheckpointProtectedHeaderAlgError} when the protected header
|
|
150
|
+
* carries no integer `alg` (label 1)
|
|
129
151
|
*/
|
|
130
152
|
export function checkpointConsistencyProof(
|
|
131
153
|
checkpointBytes: Uint8Array,
|
|
132
154
|
): CheckpointConsistencyProof {
|
|
133
155
|
const { coseSign1, unprotected } = parseCheckpoint(checkpointBytes);
|
|
134
|
-
|
|
156
|
+
// Strict: a header with no integer alg is one the contract rejects, and
|
|
157
|
+
// reading it leniently would switch the high-s rejection below off while
|
|
158
|
+
// the signed size was still taken from it (S-1).
|
|
159
|
+
let alg: number;
|
|
160
|
+
try {
|
|
161
|
+
alg = readProtectedAlg(coseSign1[0]);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err instanceof ProtectedHeaderAlgError) {
|
|
164
|
+
throw new CheckpointProtectedHeaderAlgError(
|
|
165
|
+
`checkpoint protected header carries no integer alg (label 1): ${err.message}`,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
135
170
|
const signature = coseSign1[3];
|
|
136
171
|
if (alg === COSE_ALG_ES256 && signature.length === 64 && !isLowS(signature)) {
|
|
137
172
|
throw new CheckpointHighSSignatureError(
|
|
@@ -273,11 +308,14 @@ export type CheckpointChainResult =
|
|
|
273
308
|
* as the fold's starting accumulator (a suffix chain rooted in an
|
|
274
309
|
* already-trusted accumulator supplies both).
|
|
275
310
|
* - A supplied `trustedBase` must describe a state an MMR can be in: its
|
|
276
|
-
* `size` a complete MMR size,
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
* count no MMR has
|
|
311
|
+
* `size` a complete MMR size, its `accumulator` holding one peak per peak
|
|
312
|
+
* of that size, and every one of those peaks a 32-byte node value.
|
|
313
|
+
* `peaksBitmap` rounds an incomplete size DOWN to the largest MMR below
|
|
314
|
+
* it, so without the completeness check a size of 5 folds the 4 -> N shape
|
|
315
|
+
* while every link reports a base of 5 — a node count no MMR has; and
|
|
316
|
+
* without the byte-length check an origin peak of any length is copied
|
|
317
|
+
* through the empty-path branch into the detached payload. All three are
|
|
318
|
+
* `proof_malformed`.
|
|
281
319
|
* - The first link's declared `tree-size-1` must equal that trusted
|
|
282
320
|
* starting size, and every subsequent link's must equal the previous
|
|
283
321
|
* link's sealed `tree-size-2`; either disagreement is `size_mismatch`.
|
|
@@ -349,6 +387,25 @@ export async function verifyCheckpointChain(opts: {
|
|
|
349
387
|
links,
|
|
350
388
|
};
|
|
351
389
|
}
|
|
390
|
+
// …and every peak must be a 32-byte node value, the check arbor's
|
|
391
|
+
// producer applies to both path elements and right-peaks (`toNode32`).
|
|
392
|
+
// The count alone does not reach it: on the empty-path branch an origin
|
|
393
|
+
// peak is copied into the result verbatim, so a 0/31/33/64-byte peak
|
|
394
|
+
// reaches `accumulatorPayload` and shortens or lengthens the detached
|
|
395
|
+
// payload, with only the signature left to reject it (review finding
|
|
396
|
+
// I2, canopy C6).
|
|
397
|
+
for (let i = 0; i < trustedBase.accumulator.length; i++) {
|
|
398
|
+
const peak = trustedBase.accumulator[i] as unknown;
|
|
399
|
+
if (!(peak instanceof Uint8Array) || peak.length !== 32) {
|
|
400
|
+
return {
|
|
401
|
+
ok: false,
|
|
402
|
+
reason: "proof_malformed",
|
|
403
|
+
at: 0,
|
|
404
|
+
detail: `trusted base accumulator peak ${i} is not a 32-byte node value (${describePeak(peak)})`,
|
|
405
|
+
links,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
352
409
|
}
|
|
353
410
|
let accumulator = trustedBase?.accumulator ?? [];
|
|
354
411
|
let expectedBase = trustedBase?.size ?? 0n;
|
|
@@ -439,3 +496,11 @@ export async function verifyCheckpointChain(opts: {
|
|
|
439
496
|
}
|
|
440
497
|
return { ok: true, links, accumulator };
|
|
441
498
|
}
|
|
499
|
+
|
|
500
|
+
/** Name what was found where a 32-byte accumulator peak was required. */
|
|
501
|
+
function describePeak(peak: unknown): string {
|
|
502
|
+
if (peak instanceof Uint8Array) return `${peak.length} bytes`;
|
|
503
|
+
if (peak === null) return "null";
|
|
504
|
+
if (Array.isArray(peak)) return "an array";
|
|
505
|
+
return typeof peak;
|
|
506
|
+
}
|
package/src/freshen-receipt.ts
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
parseCheckpoint,
|
|
59
59
|
} from "./build-receipt-offline.js";
|
|
60
60
|
import {
|
|
61
|
+
CheckpointSignedSizeMismatchError,
|
|
61
62
|
computeCheckpointAccumulator,
|
|
62
63
|
type CheckpointConsistencyProof,
|
|
63
64
|
} from "./checkpoint-chain.js";
|
|
@@ -81,7 +82,9 @@ export type FreshenReceiptInput = {
|
|
|
81
82
|
leafValue: Uint8Array;
|
|
82
83
|
/** Consistency-proof chain covering [0 or the trusted base] → the latest
|
|
83
84
|
* sealed size, in ascending contiguous order (the raw per-checkpoint
|
|
84
|
-
* proofs, with `paths`).
|
|
85
|
+
* proofs, with `paths`). Every link's `signedTreeSize2` must equal its
|
|
86
|
+
* `treeSize2`, as `checkpointConsistencyProof` requires of the checkpoint
|
|
87
|
+
* it decoded each link from. The chain's last link must end at the
|
|
85
88
|
* checkpoint's sealed size. */
|
|
86
89
|
consistencyProofs: readonly CheckpointConsistencyProof[];
|
|
87
90
|
/** Trusted base for a suffix chain — the size the caller already trusts
|
|
@@ -111,9 +114,11 @@ function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
/**
|
|
114
|
-
* Freshen a stale receipt to the latest sealed state. Throws if
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
+
* Freshen a stale receipt to the latest sealed state. Throws if a link's
|
|
118
|
+
* signed `tree-size-2` disagrees with its declared one
|
|
119
|
+
* ({@link CheckpointSignedSizeMismatchError}), if the chain is not a
|
|
120
|
+
* contiguous cover from the trusted base to the checkpoint's sealed size, if
|
|
121
|
+
* a climb node is missing from the supplied proofs, or if the recomputed
|
|
117
122
|
* peak does not match the folded latest accumulator.
|
|
118
123
|
*/
|
|
119
124
|
export async function freshenReceipt(
|
|
@@ -170,6 +175,20 @@ export async function freshenReceipt(
|
|
|
170
175
|
`base accumulator has ${baseAccumulator.length} peaks; trusted base size ${baseSize} has ${basePeaks}`,
|
|
171
176
|
);
|
|
172
177
|
}
|
|
178
|
+
// Every link's SIGNED tree-size-2 must equal its declared one — the same
|
|
179
|
+
// equality `checkpointConsistencyProof` enforces when it decodes a
|
|
180
|
+
// checkpoint (ADR-0066 D1 as amended, D5.5). These links arrive already
|
|
181
|
+
// decoded, so nothing here had re-read the field the type says was pinned
|
|
182
|
+
// to the signature, and a link carrying `signedTreeSize2: 999n` beside
|
|
183
|
+
// `treeSize2: 7n` freshened (review finding I5).
|
|
184
|
+
for (let i = 0; i < links.length; i++) {
|
|
185
|
+
const link = links[i]!;
|
|
186
|
+
if (link.signedTreeSize2 !== link.treeSize2) {
|
|
187
|
+
throw new CheckpointSignedSizeMismatchError(
|
|
188
|
+
`consistency proof ${i}: signed tree-size-2 (-65933) ${link.signedTreeSize2} != declared consistency-proof tree-size-2 ${link.treeSize2}`,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
173
192
|
// The first link must continue from that size, not from a size it names
|
|
174
193
|
// itself (ADR-0066 D5.4).
|
|
175
194
|
if (firstLink.treeSize1 !== baseSize) {
|
package/src/index.ts
CHANGED