@forestrie/receipt-verify 2.2.0 → 3.0.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.
@@ -1 +1 @@
1
- {"version":3,"file":"decode-checkpoint-consistency-proof.d.ts","sourceRoot":"","sources":["../src/decode-checkpoint-consistency-proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAQH,8EAA8E;AAC9E,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;IACtB,uEAAuE;IACvE,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B,CAAC;AA6BF;;;;;;;;;;;;GAYG;AACH,wBAAgB,qCAAqC,CACnD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,uBAAuB,GAAG,IAAI,CA8DhC"}
1
+ {"version":3,"file":"decode-checkpoint-consistency-proof.d.ts","sourceRoot":"","sources":["../src/decode-checkpoint-consistency-proof.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAQH,8EAA8E;AAC9E,MAAM,MAAM,uBAAuB,GAAG;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,KAAK,EAAE,UAAU,EAAE,EAAE,CAAC;IACtB,uEAAuE;IACvE,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B,CAAC;AAEF;;;;;;;GAOG;AACH,qBAAa,2BAA4B,SAAQ,KAAK;gBACxC,OAAO,EAAE,MAAM;CAI5B;AA8GD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,sCAAsC,CACpD,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,uBAAuB,EAAE,GAAG,IAAI,CA6BlC"}
@@ -1,7 +1,20 @@
1
1
  /**
2
- * Decode the draft-bryce consistency proof `[tree-size-1, tree-size-2, paths,
3
- * right-peaks]` carried under a checkpoint's verifiable-proofs UNPROTECTED
4
- * header (draft-bryce label 396, key -2 = `VDP_CONSISTENCY_PROOF_KEY`).
2
+ * Decode the draft-bryce consistency proofs carried under a checkpoint's
3
+ * verifiable-proofs UNPROTECTED header (draft-bryce label 396, key -2 =
4
+ * `VDP_CONSISTENCY_PROOF_KEY`).
5
+ *
6
+ * The draft's CDDL is
7
+ * `consistency-proofs = [ + consistency-proof ]`, with each
8
+ * `consistency-proof = bstr .cbor [tree-size-1, tree-size-2, paths,
9
+ * right-peaks]` — one or more proofs, relayed in chain order under a single
10
+ * signature (ADR-0066 D2). Both shapes are accepted under the -2 key:
11
+ *
12
+ * - an ARRAY of one or more proof bstrs — the draft's wire form, and the
13
+ * only form that can carry a relayed chain;
14
+ * - a BARE proof bstr — the shape every checkpoint sealed before the array
15
+ * form carries, and the shape the pinned `checkpoint-receipt-kat39.json`
16
+ * vector's `conventions.receipt` still states. It decodes to the array of
17
+ * one, so nothing downstream distinguishes it.
5
18
  *
6
19
  * Single source of truth for this decode, shared by `parseCheckpoint`
7
20
  * (build-receipt-offline.ts, lenient: an absent or malformed proof yields a
@@ -9,10 +22,25 @@
9
22
  * (checkpoint-chain.ts, full validation: an absent proof or a malformed
10
23
  * shape throws, and the SIGNED `tree-size-2` from the protected header —
11
24
  * read separately via `readProtectedTreeSize2`, ADR-0066 D1 as amended —
12
- * must match the `tree-size-2` decoded here; `tree-size-1` is unsigned
13
- * prover context and is not cross-checked against a signed value).
25
+ * must match the `tree-size-2` of the LAST proof decoded here;
26
+ * `tree-size-1` is unsigned prover context and is not cross-checked against
27
+ * a signed value).
14
28
  */
15
29
  import { COSE_LABEL_VDP, VDP_CONSISTENCY_PROOF_KEY, decodeCborDeterministic, } from "@forestrie/encoding";
30
+ /**
31
+ * The verifiable-proofs header carries the consistency-proof key with an
32
+ * EMPTY array. Distinct from an absent proof (no -2 key at all, which
33
+ * decodes to `null`): the key is present and claims to relay a chain, but
34
+ * the chain has no links, so there is nothing to fold and no last proof for
35
+ * the signed `tree-size-2` to equal. `consistency-proofs = [ + ... ]`
36
+ * requires at least one.
37
+ */
38
+ export class EmptyConsistencyProofsError extends Error {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = "EmptyConsistencyProofsError";
42
+ }
43
+ }
16
44
  function asBigint(v, what) {
17
45
  // Unsigned only: a negative size flows into peakMMRIndexes /
18
46
  // consistentRootsForSizes, which reject or spin on a non-positive
@@ -36,35 +64,15 @@ function asBytesArray(v, what) {
36
64
  }
37
65
  return v;
38
66
  }
39
- /**
40
- * Decode the embedded consistency proof from a checkpoint's UNPROTECTED
41
- * header map. Returns `null` when the checkpoint carries no verifiable-proofs
42
- * header (396) or no consistency-proof bstr there (key -2) — an ABSENT
43
- * proof, not a malformed one.
44
- *
45
- * @throws When a consistency-proof bstr IS present but its contents are not
46
- * the shape `[tree-size-1, tree-size-2, paths, right-peaks]`, either size
47
- * is not an unsigned integer, the proof does not grow the tree
48
- * (`tree-size-2 <= tree-size-1`), a path element or a right-peak is not a
49
- * 32-byte string — or when header 396 is present but is not map-valued,
50
- * or its `-2` entry is present but not a byte string.
51
- */
52
- export function decodeConsistencyProofFromUnprotected(unprotected) {
53
- const vdpRaw = unprotected.get(COSE_LABEL_VDP);
54
- if (vdpRaw === undefined || vdpRaw === null)
55
- return null;
56
- if (!(vdpRaw instanceof Map)) {
57
- throw new Error("checkpoint carries no verifiable-proofs header (396)");
58
- }
59
- const proofBstr = vdpRaw.get(VDP_CONSISTENCY_PROOF_KEY);
60
- if (proofBstr === undefined || proofBstr === null)
61
- return null;
62
- if (!(proofBstr instanceof Uint8Array)) {
63
- throw new Error("checkpoint carries no consistency proof (vdp key -2)");
64
- }
67
+ /** Decode one `bstr .cbor [tree-size-1, tree-size-2, paths, right-peaks]`. */
68
+ function decodeOneProof(proofBstr) {
65
69
  const proof = decodeCborDeterministic(proofBstr);
66
- if (!Array.isArray(proof) || proof.length < 4) {
67
- throw new Error("consistency proof must be [tree-size-1, tree-size-2, paths, right-peaks]");
70
+ // Exactly 4 — the draft's CDDL names a fixed-arity array, and
71
+ // go-merklelog's decoder rejects any other length (F2). A 5th element
72
+ // (e.g. another proof tuple, mistaken for a chain of two) is as malformed
73
+ // as a 3rd missing.
74
+ if (!Array.isArray(proof) || proof.length !== 4) {
75
+ throw new Error(`consistency proof must be [tree-size-1, tree-size-2, paths, right-peaks] (4 elements), got ${Array.isArray(proof) ? proof.length : typeof proof}`);
68
76
  }
69
77
  const pathsRaw = proof[2];
70
78
  if (!Array.isArray(pathsRaw)) {
@@ -106,3 +114,67 @@ export function decodeConsistencyProofFromUnprotected(unprotected) {
106
114
  rightPeaks: asBytesArray(proof[3], "right-peaks"),
107
115
  };
108
116
  }
117
+ /**
118
+ * Decode proof `at` of a relayed chain, naming its position in the message
119
+ * so a chain of several says which link is malformed. A header carrying a
120
+ * single proof names no position: its message is the one a single-proof
121
+ * checkpoint has always produced.
122
+ */
123
+ function decodeProofAt(proofBstr, at) {
124
+ if (at === null)
125
+ return decodeOneProof(proofBstr);
126
+ try {
127
+ return decodeOneProof(proofBstr);
128
+ }
129
+ catch (err) {
130
+ throw new Error(`consistency-proofs entry ${at}: ${err instanceof Error ? err.message : String(err)}`);
131
+ }
132
+ }
133
+ /**
134
+ * Decode the embedded consistency proofs from a checkpoint's UNPROTECTED
135
+ * header map, in the order they are relayed. Returns `null` when the
136
+ * checkpoint carries no verifiable-proofs header (396) or no
137
+ * consistency-proof entry there (key -2) — an ABSENT proof, not a malformed
138
+ * one. A returned array always holds at least one proof.
139
+ *
140
+ * This decode establishes each proof's shape only. Nothing here relates one
141
+ * proof to the next, or to a signed size: the chain has to be checked
142
+ * against state the CALLER trusts, which is `computeCheckpointAccumulator`
143
+ * and `checkpointConsistencyProof` (ADR-0066 D5.4).
144
+ *
145
+ * @throws {EmptyConsistencyProofsError} when the -2 entry is an empty array
146
+ * @throws When a consistency proof IS present but its contents are not the
147
+ * shape `[tree-size-1, tree-size-2, paths, right-peaks]`, either size is
148
+ * not an unsigned integer, a proof does not grow the tree
149
+ * (`tree-size-2 <= tree-size-1`), a path element or a right-peak is not a
150
+ * 32-byte string — or when header 396 is present but is not map-valued,
151
+ * or its `-2` entry is neither a byte string nor an array of byte strings.
152
+ */
153
+ export function decodeConsistencyProofsFromUnprotected(unprotected) {
154
+ const vdpRaw = unprotected.get(COSE_LABEL_VDP);
155
+ if (vdpRaw === undefined || vdpRaw === null)
156
+ return null;
157
+ if (!(vdpRaw instanceof Map)) {
158
+ throw new Error("checkpoint carries no verifiable-proofs header (396)");
159
+ }
160
+ const entry = vdpRaw.get(VDP_CONSISTENCY_PROOF_KEY);
161
+ if (entry === undefined || entry === null)
162
+ return null;
163
+ if (entry instanceof Uint8Array) {
164
+ // The pre-array shape: a single proof written straight under -2. It is
165
+ // the array of one, and is reported as such.
166
+ return [decodeOneProof(entry)];
167
+ }
168
+ if (!Array.isArray(entry)) {
169
+ throw new Error("checkpoint carries no consistency proof (vdp key -2)");
170
+ }
171
+ if (entry.length === 0) {
172
+ throw new EmptyConsistencyProofsError("consistency-proofs (vdp key -2) is empty; at least one proof is required");
173
+ }
174
+ return entry.map((proofBstr, i) => {
175
+ if (!(proofBstr instanceof Uint8Array)) {
176
+ throw new Error(`consistency-proofs entry ${i} is not a byte string (vdp key -2)`);
177
+ }
178
+ return decodeProofAt(proofBstr, entry.length === 1 ? null : i);
179
+ });
180
+ }
@@ -15,11 +15,13 @@ export type FreshenReceiptInput = {
15
15
  * value `verify` recomputes from the entry (caller derives it). */
16
16
  leafValue: Uint8Array;
17
17
  /** Consistency-proof chain covering [0 or the trusted base] → the latest
18
- * sealed size, in ascending contiguous order (the raw per-checkpoint
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
22
- * checkpoint's sealed size. */
18
+ * sealed size, in ascending contiguous order: one entry per checkpoint,
19
+ * each holding the proofs that checkpoint relays (`proofs`, one or more —
20
+ * ADR-0066 D2). Every link's `signedTreeSize2` must equal its
21
+ * `treeSize2` — the size its LAST relayed proof reaches — as
22
+ * `checkpointConsistencyProof` requires of the checkpoint it decoded each
23
+ * link from. The chain's last link must end at the checkpoint's sealed
24
+ * size. */
23
25
  consistencyProofs: readonly CheckpointConsistencyProof[];
24
26
  /** Trusted base for a suffix chain — the size the caller already trusts
25
27
  * and that size's accumulator; omit for a chain from base 0 (size 0, an
@@ -1 +1 @@
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"}
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;;;;;;;eAOW;IACX,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,CA4L/B"}
@@ -125,7 +125,9 @@ export async function freshenReceipt(input) {
125
125
  if (firstLink.treeSize1 !== baseSize) {
126
126
  throw new Error(`first consistency proof declares tree-size-1 ${firstLink.treeSize1}; the trusted base size is ${baseSize}`);
127
127
  }
128
- // Contiguity: each link continues where the previous one sealed.
128
+ // Contiguity BETWEEN checkpoints: each link continues where the previous
129
+ // one sealed. Contiguity WITHIN a link — between the proofs one
130
+ // checkpoint relays — is `computeCheckpointAccumulator`'s, below.
129
131
  for (let i = 1; i < links.length; i++) {
130
132
  if (links[i].treeSize1 !== links[i - 1].treeSize2) {
131
133
  throw new Error(`consistency chain is not contiguous at link ${i}: base ${links[i].treeSize1} != previous sealed size ${links[i - 1].treeSize2}`);
@@ -136,10 +138,11 @@ export async function freshenReceipt(input) {
136
138
  if (lastLink.treeSize2 !== sealedSize) {
137
139
  throw new Error(`consistency chain ends at size ${lastLink.treeSize2} but the checkpoint sealed size ${sealedSize}`);
138
140
  }
139
- // Fold the chain to the latest accumulator (self-check target). Each step
141
+ // Fold the chain to the latest accumulator (self-check target). Each link
140
142
  // runs against a size the caller trusts, never one read off the link being
141
143
  // folded: the trusted base for the first link, and the size the previous
142
- // link was just folded TO for every link after it.
144
+ // link was just folded TO for every link after it. A link relaying several
145
+ // proofs folds them all, in order, and ends at its last one.
143
146
  let accumulator = baseAccumulator;
144
147
  let sizeFrom = baseSize;
145
148
  for (const p of links) {
@@ -173,20 +176,23 @@ export async function freshenReceipt(input) {
173
176
  for (let k = 0; k < oldPath.length; k++) {
174
177
  store.set(fullIndices[k], oldPath[k]);
175
178
  }
176
- for (const link of links) {
177
- // A base-0 link (treeSize1 === 0) has no from-peaks to climb — a 0→N
179
+ // Every relayed proof contributes, not just one per checkpoint: a link
180
+ // that relays several sealed steps (ADR-0066 D2) carries one set of paths
181
+ // per step, each addressed by that step's own two sizes.
182
+ for (const step of links.flatMap((link) => link.proofs)) {
183
+ // A base-0 step (treeSize1 === 0) has no from-peaks to climb — a 0→N
178
184
  // consistency proof carries `paths: []` (the whole accumulator is its
179
185
  // right-peaks). Skip it: it contributes no store nodes, and calling
180
186
  // `peakMMRIndexes(-1n)` would throw (`posHeight(0)`, FOR-414). A genesis-
181
- // rooted `.sth` chain always starts with such a link.
182
- if (link.treeSize1 === 0n)
187
+ // rooted `.sth` chain always starts with such a step.
188
+ if (step.treeSize1 === 0n)
183
189
  continue;
184
- const fromPeaks = peakMMRIndexes(link.treeSize1 - 1n);
190
+ const fromPeaks = peakMMRIndexes(step.treeSize1 - 1n);
185
191
  fromPeaks.forEach((peakIndex, j) => {
186
- const climb = link.paths[j];
192
+ const climb = step.paths[j];
187
193
  if (climb === undefined)
188
194
  return;
189
- const climbIndices = inclusionProofPath(link.treeSize2 - 1n, peakIndex);
195
+ const climbIndices = inclusionProofPath(step.treeSize2 - 1n, peakIndex);
190
196
  climbIndices.forEach((ix, e) => {
191
197
  const v = climb[e];
192
198
  if (v !== undefined)
package/dist/index.d.ts CHANGED
@@ -76,7 +76,10 @@ 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, CheckpointProtectedHeaderAlgError, CheckpointSignedSizeMismatchError, type CheckpointChainLink, type CheckpointChainResult, type CheckpointConsistencyProof, } from "./checkpoint-chain.js";
79
+ export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointProtectedHeaderAlgError, CheckpointSignedSizeMismatchError, ConsistencyChainNotContiguousError, EmptyConsistencyProofsError, type CheckpointChainLink, type CheckpointChainResult, type CheckpointConsistencyProof, } from "./checkpoint-chain.js";
80
+ /** One relayed consistency proof; `CheckpointConsistencyProof.proofs` holds
81
+ * the chain of them a checkpoint carries (ADR-0066 D2). */
82
+ export type { DecodedConsistencyProof } from "./decode-checkpoint-consistency-proof.js";
80
83
  /**
81
84
  * Univocity leaf commitment hash. Was CLI-private (forestrie-cli's own
82
85
  * mirror, "hoist to the library when the FOR-297 multi-hop resolver lands");
@@ -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, CheckpointProtectedHeaderAlgError, CheckpointSignedSizeMismatchError, } from "./checkpoint-chain.js";
64
+ export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointProtectedHeaderAlgError, CheckpointSignedSizeMismatchError, ConsistencyChainNotContiguousError, EmptyConsistencyProofsError, } 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.2.0",
3
+ "version": "3.0.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Offline SCITT grant receipt verification (ADR-0045)",
@@ -28,9 +28,9 @@
28
28
  ],
29
29
  "dependencies": {
30
30
  "@noble/hashes": "^1.7.1",
31
- "@forestrie/chain-rpc": "0.3.0",
32
- "@forestrie/encoding": "0.9.0",
33
- "@forestrie/merklelog": "0.5.0"
31
+ "@forestrie/encoding": "0.9.1",
32
+ "@forestrie/merklelog": "0.5.0",
33
+ "@forestrie/chain-rpc": "0.3.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "esbuild": "^0.24.0",
@@ -37,7 +37,7 @@ import {
37
37
  unwrapCoseSign1Tag,
38
38
  type CoseSign1,
39
39
  } from "./parse-receipt.js";
40
- import { decodeConsistencyProofFromUnprotected } from "./decode-checkpoint-consistency-proof.js";
40
+ import { decodeConsistencyProofsFromUnprotected } from "./decode-checkpoint-consistency-proof.js";
41
41
  import { SubtleHasher } from "./subtle-hasher.js";
42
42
 
43
43
  /** Not in scope for the shared cose-labels module (FOR-568 §4.1). */
@@ -265,15 +265,19 @@ function cborBytes(value: unknown): Uint8Array {
265
265
  * (ADR-0066 D1 as amended, label -65933) from the PROTECTED header — not the
266
266
  * unprotected consistency proof's declared value, which a checkpoint without
267
267
  * a signing key could freely restate (ADR-0066's "keyless first checkpoint"
268
- * case). A consistency proof must still be present (an unsigned checkpoint
269
- * with no proof is not sealed at all); its declared sizes are not otherwise
270
- * used here — {@link checkpointConsistencyProof} in `checkpoint-chain.ts`
271
- * is the validating decode that requires the two to agree.
268
+ * case). At least one consistency proof must still be present (an unsigned
269
+ * checkpoint with no proof is not sealed at all) — one or more, since a
270
+ * checkpoint may relay a chain of sealed steps under one signature
271
+ * (ADR-0066 D2). Their declared sizes are not otherwise used here;
272
+ * {@link checkpointConsistencyProof} in `checkpoint-chain.ts` is the
273
+ * validating decode that requires the signed size and the last relayed
274
+ * proof's to agree.
272
275
  *
273
- * Lenient by design: an absent proof, a malformed proof, or an absent or
274
- * malformed signed tree-size-2 all yield `null` rather than a throw, so a
275
- * caller of {@link parseCheckpoint} sees exactly the same "not verifiable"
276
- * outcome either way.
276
+ * Lenient by design: an absent proof, an empty or malformed
277
+ * consistency-proofs array, or an absent or malformed signed tree-size-2
278
+ * all yield `null` rather than a throw, so a caller of
279
+ * {@link parseCheckpoint} sees exactly the same "not verifiable" outcome
280
+ * either way.
277
281
  */
278
282
  function sealedSizeFromCheckpoint(
279
283
  coseSign1: CoseSign1,
@@ -281,7 +285,7 @@ function sealedSizeFromCheckpoint(
281
285
  ): bigint | null {
282
286
  let declared;
283
287
  try {
284
- declared = decodeConsistencyProofFromUnprotected(unprotected);
288
+ declared = decodeConsistencyProofsFromUnprotected(unprotected);
285
289
  } catch {
286
290
  return null;
287
291
  }