@forestrie/receipt-verify 1.0.0 → 2.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.
Files changed (52) hide show
  1. package/dist/attach-transparent-statement-receipt.d.ts +5 -1
  2. package/dist/attach-transparent-statement-receipt.d.ts.map +1 -1
  3. package/dist/attach-transparent-statement-receipt.js +7 -3
  4. package/dist/build-receipt-offline.d.ts +8 -1
  5. package/dist/build-receipt-offline.d.ts.map +1 -1
  6. package/dist/build-receipt-offline.js +31 -24
  7. package/dist/chain-binding.d.ts +19 -0
  8. package/dist/chain-binding.d.ts.map +1 -0
  9. package/dist/chain-binding.js +1 -0
  10. package/dist/checkpoint-chain.d.ts +130 -17
  11. package/dist/checkpoint-chain.d.ts.map +1 -1
  12. package/dist/checkpoint-chain.js +216 -117
  13. package/dist/decode-chain-binding-from-genesis.d.ts +15 -0
  14. package/dist/decode-chain-binding-from-genesis.d.ts.map +1 -0
  15. package/dist/decode-chain-binding-from-genesis.js +55 -0
  16. package/dist/decode-checkpoint-consistency-proof.d.ts +38 -0
  17. package/dist/decode-checkpoint-consistency-proof.d.ts.map +1 -0
  18. package/dist/decode-checkpoint-consistency-proof.js +108 -0
  19. package/dist/decode-genesis-cbor-map.d.ts +12 -0
  20. package/dist/decode-genesis-cbor-map.d.ts.map +1 -0
  21. package/dist/decode-genesis-cbor-map.js +26 -0
  22. package/dist/decode-trust-root-from-genesis.d.ts +21 -3
  23. package/dist/decode-trust-root-from-genesis.d.ts.map +1 -1
  24. package/dist/decode-trust-root-from-genesis.js +32 -23
  25. package/dist/decoded-trust-root.d.ts +17 -0
  26. package/dist/decoded-trust-root.d.ts.map +1 -0
  27. package/dist/decoded-trust-root.js +1 -0
  28. package/dist/forest-genesis-labels.d.ts +7 -0
  29. package/dist/forest-genesis-labels.d.ts.map +1 -1
  30. package/dist/forest-genesis-labels.js +7 -0
  31. package/dist/freshen-receipt.d.ts +23 -6
  32. package/dist/freshen-receipt.d.ts.map +1 -1
  33. package/dist/freshen-receipt.js +29 -17
  34. package/dist/index.d.ts +22 -2
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +20 -2
  37. package/dist/parse-receipt.d.ts.map +1 -1
  38. package/dist/parse-receipt.js +2 -3
  39. package/package.json +4 -4
  40. package/src/attach-transparent-statement-receipt.ts +10 -3
  41. package/src/build-receipt-offline.ts +41 -22
  42. package/src/chain-binding.ts +18 -0
  43. package/src/checkpoint-chain.ts +279 -136
  44. package/src/decode-chain-binding-from-genesis.ts +77 -0
  45. package/src/decode-checkpoint-consistency-proof.ts +136 -0
  46. package/src/decode-genesis-cbor-map.ts +27 -0
  47. package/src/decode-trust-root-from-genesis.ts +40 -25
  48. package/src/decoded-trust-root.ts +17 -0
  49. package/src/forest-genesis-labels.ts +7 -0
  50. package/src/freshen-receipt.ts +56 -25
  51. package/src/index.ts +33 -1
  52. package/src/parse-receipt.ts +2 -4
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Shared decode step for a genesis document's CBOR body: deterministic CBOR
3
+ * decodes an integer-keyed map either as a native `Map` or as a plain
4
+ * object (encoder-dependent), so callers that read genesis labels normalise
5
+ * to a `Map<number, unknown>` once here rather than each re-implementing
6
+ * the same fallback. Used by {@link decodeTrustRootFromGenesis} and
7
+ * {@link decodeChainBindingFromGenesis}.
8
+ */
9
+ export declare function decodeGenesisBodyAsIntKeyMap(raw: unknown): Map<number, unknown> | null;
10
+ /** Narrow a decoded genesis field to `Uint8Array`, or `null` if it is not one. */
11
+ export declare function asGenesisUint8Array(v: unknown): Uint8Array | null;
12
+ //# sourceMappingURL=decode-genesis-cbor-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decode-genesis-cbor-map.d.ts","sourceRoot":"","sources":["../src/decode-genesis-cbor-map.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,GAAG,EAAE,OAAO,GACX,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAW7B;AAED,kFAAkF;AAClF,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAEjE"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared decode step for a genesis document's CBOR body: deterministic CBOR
3
+ * decodes an integer-keyed map either as a native `Map` or as a plain
4
+ * object (encoder-dependent), so callers that read genesis labels normalise
5
+ * to a `Map<number, unknown>` once here rather than each re-implementing
6
+ * the same fallback. Used by {@link decodeTrustRootFromGenesis} and
7
+ * {@link decodeChainBindingFromGenesis}.
8
+ */
9
+ export function decodeGenesisBodyAsIntKeyMap(raw) {
10
+ if (raw instanceof Map)
11
+ return raw;
12
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
13
+ const out = new Map();
14
+ for (const [k, v] of Object.entries(raw)) {
15
+ const n = Number(k);
16
+ if (Number.isFinite(n))
17
+ out.set(n, v);
18
+ }
19
+ return out;
20
+ }
21
+ return null;
22
+ }
23
+ /** Narrow a decoded genesis field to `Uint8Array`, or `null` if it is not one. */
24
+ export function asGenesisUint8Array(v) {
25
+ return v instanceof Uint8Array ? v : null;
26
+ }
@@ -1,7 +1,25 @@
1
- import type { RootVerifyKey } from "./root-verify-key.js";
1
+ import { type RootVerifyKey } from "./root-verify-key.js";
2
+ import type { DecodedTrustRoot } from "./decoded-trust-root.js";
2
3
  /**
3
- * Extract receipt verify key from a forest genesis document CBOR blob.
4
- * Offline path: genesis-only trust anchor (ADR-0045).
4
+ * Extract the receipt verify key from a forest genesis document CBOR blob,
5
+ * alongside `bootstrapKeyXy` when the bootstrap key is ES256. Offline path:
6
+ * genesis-only trust anchor (ADR-0045). See {@link DecodedTrustRoot}.
7
+ * `bootstrapKeyXy` is read directly from the genesis-encoded bytes (never
8
+ * exported from the non-extractable `CryptoKey` in `key`), and is
9
+ * `undefined` for a KS256 v2 bootstrap key (an on-chain address — there is
10
+ * no P-256 public key to give up in that case).
11
+ *
12
+ * This is the single decode path: {@link decodeTrustRootFromGenesis} is
13
+ * `(await decodeTrustRootDetailsFromGenesis(genesisCbor)).key`.
14
+ */
15
+ export declare function decodeTrustRootDetailsFromGenesis(genesisCbor: Uint8Array): Promise<DecodedTrustRoot>;
16
+ /**
17
+ * Extract the receipt verify key from a forest genesis document CBOR blob.
18
+ * Offline path: genesis-only trust anchor (ADR-0045). The origin/main
19
+ * signature and behaviour (including a KS256 v2 bootstrap key resolving to
20
+ * the `ParsedKs256RootKey` on-chain address, not a throw): callers pinned
21
+ * to `RootVerifyKey` keep working unchanged. For the raw bootstrap public
22
+ * key bytes as well, use {@link decodeTrustRootDetailsFromGenesis}.
5
23
  */
6
24
  export declare function decodeTrustRootFromGenesis(genesisCbor: Uint8Array): Promise<RootVerifyKey>;
7
25
  //# sourceMappingURL=decode-trust-root-from-genesis.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"decode-trust-root-from-genesis.d.ts","sourceRoot":"","sources":["../src/decode-trust-root-from-genesis.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAmB1D;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,UAAU,GACtB,OAAO,CAAC,aAAa,CAAC,CA0CxB"}
1
+ {"version":3,"file":"decode-trust-root-from-genesis.d.ts","sourceRoot":"","sources":["../src/decode-trust-root-from-genesis.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAKhF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE;;;;;;;;;;;GAWG;AACH,wBAAsB,iCAAiC,CACrD,WAAW,EAAE,UAAU,GACtB,OAAO,CAAC,gBAAgB,CAAC,CA+C3B;AAED;;;;;;;GAOG;AACH,wBAAsB,0BAA0B,CAC9C,WAAW,EAAE,UAAU,GACtB,OAAO,CAAC,aAAa,CAAC,CAExB"}
@@ -2,28 +2,21 @@ import { decodeCborDeterministic } from "@forestrie/encoding";
2
2
  import { COSE_ALG_ES256, COSE_CRV_P256, COSE_EC2_CRV, COSE_EC2_X, COSE_EC2_Y, COSE_KEY_ALG, COSE_KEY_KTY, COSE_KTY_EC2, } from "./cose-key.js";
3
3
  import { FOREST_GENESIS_LABEL_BOOTSTRAP_KEY, FOREST_GENESIS_LABEL_GENESIS_ALG, FOREST_GENESIS_LABEL_GENESIS_VERSION, FOREST_GENESIS_SCHEMA_V1, FOREST_GENESIS_SCHEMA_V2, } from "./forest-genesis-labels.js";
4
4
  import { decodeTrustRootCbor } from "./decode-trust-root-cbor.js";
5
- function decodeBodyAsIntKeyMap(raw) {
6
- if (raw instanceof Map)
7
- return raw;
8
- if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
9
- const out = new Map();
10
- for (const [k, v] of Object.entries(raw)) {
11
- const n = Number(k);
12
- if (Number.isFinite(n))
13
- out.set(n, v);
14
- }
15
- return out;
16
- }
17
- return null;
18
- }
19
- function asGenesisUint8Array(v) {
20
- return v instanceof Uint8Array ? v : null;
21
- }
5
+ import { isParsedKs256RootKey } from "./root-verify-key.js";
6
+ import { asGenesisUint8Array, decodeGenesisBodyAsIntKeyMap, } from "./decode-genesis-cbor-map.js";
22
7
  /**
23
- * Extract receipt verify key from a forest genesis document CBOR blob.
24
- * Offline path: genesis-only trust anchor (ADR-0045).
8
+ * Extract the receipt verify key from a forest genesis document CBOR blob,
9
+ * alongside `bootstrapKeyXy` when the bootstrap key is ES256. Offline path:
10
+ * genesis-only trust anchor (ADR-0045). See {@link DecodedTrustRoot}.
11
+ * `bootstrapKeyXy` is read directly from the genesis-encoded bytes (never
12
+ * exported from the non-extractable `CryptoKey` in `key`), and is
13
+ * `undefined` for a KS256 v2 bootstrap key (an on-chain address — there is
14
+ * no P-256 public key to give up in that case).
15
+ *
16
+ * This is the single decode path: {@link decodeTrustRootFromGenesis} is
17
+ * `(await decodeTrustRootDetailsFromGenesis(genesisCbor)).key`.
25
18
  */
26
- export async function decodeTrustRootFromGenesis(genesisCbor) {
19
+ export async function decodeTrustRootDetailsFromGenesis(genesisCbor) {
27
20
  let raw;
28
21
  try {
29
22
  raw = decodeCborDeterministic(genesisCbor);
@@ -31,7 +24,7 @@ export async function decodeTrustRootFromGenesis(genesisCbor) {
31
24
  catch {
32
25
  throw new Error("genesis CBOR decode failed");
33
26
  }
34
- const m = decodeBodyAsIntKeyMap(raw);
27
+ const m = decodeGenesisBodyAsIntKeyMap(raw);
35
28
  if (!m)
36
29
  throw new Error("genesis document must be a CBOR map");
37
30
  const versionRaw = m.get(FOREST_GENESIS_LABEL_GENESIS_VERSION);
@@ -41,7 +34,11 @@ export async function decodeTrustRootFromGenesis(genesisCbor) {
41
34
  if (bootstrapKey === null) {
42
35
  throw new Error("v2 genesis missing bootstrapKey");
43
36
  }
44
- return decodeTrustRootCbor({ alg, key: bootstrapKey });
37
+ const key = await decodeTrustRootCbor({ alg, key: bootstrapKey });
38
+ return {
39
+ key,
40
+ bootstrapKeyXy: isParsedKs256RootKey(key) ? undefined : bootstrapKey,
41
+ };
45
42
  }
46
43
  const kty = m.get(COSE_KEY_KTY);
47
44
  const crv = m.get(COSE_EC2_CRV);
@@ -55,10 +52,22 @@ export async function decodeTrustRootFromGenesis(genesisCbor) {
55
52
  const xy = new Uint8Array(64);
56
53
  xy.set(x, 0);
57
54
  xy.set(y, 32);
58
- return decodeTrustRootCbor({ alg: COSE_ALG_ES256, key: xy });
55
+ const key = await decodeTrustRootCbor({ alg: COSE_ALG_ES256, key: xy });
56
+ return { key, bootstrapKeyXy: xy };
59
57
  }
60
58
  if (versionRaw === FOREST_GENESIS_SCHEMA_V1 || versionRaw === undefined) {
61
59
  throw new Error("unsupported or invalid genesis schema for trust root");
62
60
  }
63
61
  throw new Error("unsupported genesis document");
64
62
  }
63
+ /**
64
+ * Extract the receipt verify key from a forest genesis document CBOR blob.
65
+ * Offline path: genesis-only trust anchor (ADR-0045). The origin/main
66
+ * signature and behaviour (including a KS256 v2 bootstrap key resolving to
67
+ * the `ParsedKs256RootKey` on-chain address, not a throw): callers pinned
68
+ * to `RootVerifyKey` keep working unchanged. For the raw bootstrap public
69
+ * key bytes as well, use {@link decodeTrustRootDetailsFromGenesis}.
70
+ */
71
+ export async function decodeTrustRootFromGenesis(genesisCbor) {
72
+ return (await decodeTrustRootDetailsFromGenesis(genesisCbor)).key;
73
+ }
@@ -0,0 +1,17 @@
1
+ import type { RootVerifyKey } from "./root-verify-key.js";
2
+ /**
3
+ * Result of {@link decodeTrustRootDetailsFromGenesis}: the decoded verify
4
+ * key (an ES256 `CryptoKey`, or a KS256 on-chain address) plus, for the
5
+ * ES256 case, the raw 64-byte x||y P-256 public key coordinates the genesis
6
+ * document carries. `bootstrapKeyXy` is read directly from the
7
+ * genesis-encoded bytes, not exported from `key` (`key` stays
8
+ * non-extractable) — plan-2609-07 L3, for callers that need the
9
+ * serialisable public key material the `CryptoKey` cannot give up.
10
+ * `bootstrapKeyXy` is `undefined` for a KS256 v2 bootstrap key (an on-chain
11
+ * address — there is no P-256 public key to give up).
12
+ */
13
+ export interface DecodedTrustRoot {
14
+ key: RootVerifyKey;
15
+ bootstrapKeyXy?: Uint8Array;
16
+ }
17
+ //# sourceMappingURL=decoded-trust-root.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decoded-trust-root.d.ts","sourceRoot":"","sources":["../src/decoded-trust-root.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,aAAa,CAAC;IACnB,cAAc,CAAC,EAAE,UAAU,CAAC;CAC7B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,13 @@
1
1
  export declare const FOREST_GENESIS_LABEL_GENESIS_VERSION = -68009;
2
2
  export declare const FOREST_GENESIS_LABEL_GENESIS_ALG = -68014;
3
3
  export declare const FOREST_GENESIS_LABEL_BOOTSTRAP_KEY = -68015;
4
+ /**
5
+ * The forest's own log id, wire-encoded as 32 bytes: 16 zero bytes then the
6
+ * 16-byte forest bootstrap log id (plan-2609-07 L3; mirrors mcp-resolve's
7
+ * `genesis-binding.ts` naming so that consumer can import this instead of
8
+ * defining it locally). See {@link decodeChainBindingFromGenesis}.
9
+ */
10
+ export declare const FOREST_GENESIS_LABEL_LOG_ID = -68010;
4
11
  export declare const FOREST_GENESIS_LABEL_UNIVOCITY_ADDR = -68011;
5
12
  export declare const FOREST_GENESIS_LABEL_CHAIN_ID = -68013;
6
13
  export declare const FOREST_GENESIS_SCHEMA_V2 = 2;
@@ -1 +1 @@
1
- {"version":3,"file":"forest-genesis-labels.d.ts","sourceRoot":"","sources":["../src/forest-genesis-labels.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oCAAoC,SAAS,CAAC;AAC3D,eAAO,MAAM,gCAAgC,SAAS,CAAC;AACvD,eAAO,MAAM,kCAAkC,SAAS,CAAC;AACzD,eAAO,MAAM,mCAAmC,SAAS,CAAC;AAC1D,eAAO,MAAM,6BAA6B,SAAS,CAAC;AACpD,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,kBAAkB,SAAS,CAAC;AACzC,eAAO,MAAM,yBAAyB,SAAS,CAAC"}
1
+ {"version":3,"file":"forest-genesis-labels.d.ts","sourceRoot":"","sources":["../src/forest-genesis-labels.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,oCAAoC,SAAS,CAAC;AAC3D,eAAO,MAAM,gCAAgC,SAAS,CAAC;AACvD,eAAO,MAAM,kCAAkC,SAAS,CAAC;AACzD;;;;;GAKG;AACH,eAAO,MAAM,2BAA2B,SAAS,CAAC;AAClD,eAAO,MAAM,mCAAmC,SAAS,CAAC;AAC1D,eAAO,MAAM,6BAA6B,SAAS,CAAC;AACpD,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAC1C,eAAO,MAAM,wBAAwB,IAAI,CAAC;AAE1C,eAAO,MAAM,kBAAkB,SAAS,CAAC;AACzC,eAAO,MAAM,yBAAyB,SAAS,CAAC"}
@@ -1,6 +1,13 @@
1
1
  export const FOREST_GENESIS_LABEL_GENESIS_VERSION = -68009;
2
2
  export const FOREST_GENESIS_LABEL_GENESIS_ALG = -68014;
3
3
  export const FOREST_GENESIS_LABEL_BOOTSTRAP_KEY = -68015;
4
+ /**
5
+ * The forest's own log id, wire-encoded as 32 bytes: 16 zero bytes then the
6
+ * 16-byte forest bootstrap log id (plan-2609-07 L3; mirrors mcp-resolve's
7
+ * `genesis-binding.ts` naming so that consumer can import this instead of
8
+ * defining it locally). See {@link decodeChainBindingFromGenesis}.
9
+ */
10
+ export const FOREST_GENESIS_LABEL_LOG_ID = -68010;
4
11
  export const FOREST_GENESIS_LABEL_UNIVOCITY_ADDR = -68011;
5
12
  export const FOREST_GENESIS_LABEL_CHAIN_ID = -68013;
6
13
  export const FOREST_GENESIS_SCHEMA_V2 = 2;
@@ -1,17 +1,34 @@
1
1
  import { type CheckpointConsistencyProof } from "./checkpoint-chain.js";
2
+ /**
3
+ * BREAKING (within the 2.0.0 major already in flight): the sizeless
4
+ * `accumulatorFrom?: Uint8Array[]` seed is replaced by `trustedBase`, which
5
+ * carries the seed's SIZE alongside its peaks. Without a size the fold ran
6
+ * each link against the size read off that link's own proof, which is what
7
+ * {@link computeCheckpointAccumulator} exists to prevent (ADR-0066 D5.4) —
8
+ * a seed could only ever be checked against the peak count the proof's own
9
+ * declared base implies, and many sizes share a peak count.
10
+ */
2
11
  export type FreshenReceiptInput = {
3
12
  /** The stale receipt (COSE Sign1 with a 396 inclusion proof). */
4
13
  oldReceiptBytes: Uint8Array;
5
14
  /** The leaf's committed value: `SHA-256(idtimestamp ‖ inner)` — the same
6
15
  * value `verify` recomputes from the entry (caller derives it). */
7
16
  leafValue: Uint8Array;
8
- /** Consistency-proof chain covering [0 or a trusted seed] → the latest sealed
9
- * size, in ascending contiguous order (the raw per-checkpoint proofs, with
10
- * `paths`). The chain's last link must end at the checkpoint's sealed size. */
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`). The chain's last link must end at the
20
+ * checkpoint's sealed size. */
11
21
  consistencyProofs: readonly CheckpointConsistencyProof[];
12
- /** Trusted accumulator seed for a suffix chain; omit for a chain from base 0.
13
- * Its peak count must match the first link's tree-size-1. */
14
- accumulatorFrom?: Uint8Array[];
22
+ /** Trusted base for a suffix chain — the size the caller already trusts
23
+ * and that size's accumulator; omit for a chain from base 0 (size 0, an
24
+ * empty accumulator). The size must be a complete MMR size, the
25
+ * accumulator must hold one peak per peak of that size, and the first
26
+ * link's declared `tree-size-1` must equal the size. Same shape as
27
+ * `verifyCheckpointChain`'s `trustedBase`. */
28
+ trustedBase?: {
29
+ size: bigint;
30
+ accumulator: Uint8Array[];
31
+ };
15
32
  /** The latest checkpoint (`.sth`): its pre-signed peak receipts + delegation
16
33
  * cert become the freshened receipt's signature. */
17
34
  latestCheckpointBytes: Uint8Array;
@@ -1 +1 @@
1
- {"version":3,"file":"freshen-receipt.d.ts","sourceRoot":"","sources":["../src/freshen-receipt.ts"],"names":[],"mappings":"AAyDA,OAAO,EAEL,KAAK,0BAA0B,EAChC,MAAM,uBAAuB,CAAC;AAI/B,MAAM,MAAM,mBAAmB,GAAG;IAChC,iEAAiE;IACjE,eAAe,EAAE,UAAU,CAAC;IAC5B;uEACmE;IACnE,SAAS,EAAE,UAAU,CAAC;IACtB;;mFAE+E;IAC/E,iBAAiB,EAAE,SAAS,0BAA0B,EAAE,CAAC;IACzD;iEAC6D;IAC7D,eAAe,CAAC,EAAE,UAAU,EAAE,CAAC;IAC/B;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;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAyJ/B"}
1
+ {"version":3,"file":"freshen-receipt.d.ts","sourceRoot":"","sources":["../src/freshen-receipt.ts"],"names":[],"mappings":"AA2DA,OAAO,EAEL,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;;;mCAG+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;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,oBAAoB,CAAC,CAwK/B"}
@@ -45,7 +45,7 @@
45
45
  * tested go-merklelog port); this module only assembles node values by index
46
46
  * from the old receipt path + the consistency proofs.
47
47
  */
48
- import { calculateRoot, inclusionProofPath, peakIndexForLeafProof, peakMMRIndexes, } from "@forestrie/merklelog";
48
+ import { calculateRoot, inclusionProofPath, mmrSizeForLeafCount, peakIndexForLeafProof, peakMMRIndexes, peaksBitmap, } from "@forestrie/merklelog";
49
49
  import { assembleReceiptFromProof, parseCheckpoint, } from "./build-receipt-offline.js";
50
50
  import { computeCheckpointAccumulator, } from "./checkpoint-chain.js";
51
51
  import { parseReceipt } from "./parse-receipt.js";
@@ -90,19 +90,26 @@ export async function freshenReceipt(input) {
90
90
  throw new Error("freshen requires at least one consistency proof linking the receipt's era to the checkpoint");
91
91
  }
92
92
  const firstLink = links[0];
93
- // Base: a base-0 chain starts from an empty accumulator; a suffix chain's
94
- // trusted seed must have the peak count of its tree-size-1.
95
- const baseCount = input.accumulatorFrom?.length ?? 0;
96
- if (firstLink.treeSize1 === 0n) {
97
- if (baseCount !== 0) {
98
- throw new Error("base-0 consistency chain must start from an empty accumulator seed");
99
- }
100
- }
101
- else {
102
- const wanted = peakMMRIndexes(firstLink.treeSize1 - 1n).length;
103
- if (baseCount !== wanted) {
104
- throw new Error(`base accumulator has ${baseCount} peaks; first link size ${firstLink.treeSize1} requires ${wanted}`);
105
- }
93
+ // Base: the CALLER's trusted size and its accumulator (size 0 and an empty
94
+ // accumulator for a whole-log chain). The size has to be a size an MMR can
95
+ // have — `peaksBitmap` rounds an incomplete one DOWN, so a size of 5 would
96
+ // fold the 4 -> N shape — and the accumulator has to hold that size's
97
+ // peaks, which is the check the proof's own declared base used to stand in
98
+ // for.
99
+ const baseSize = input.trustedBase?.size ?? 0n;
100
+ const baseAccumulator = input.trustedBase?.accumulator ?? [];
101
+ if (baseSize < 0n ||
102
+ mmrSizeForLeafCount(peaksBitmap(baseSize)) !== baseSize) {
103
+ throw new Error(`trusted base size ${baseSize} is not a complete MMR size`);
104
+ }
105
+ const basePeaks = baseSize === 0n ? 0 : peakMMRIndexes(baseSize - 1n).length;
106
+ if (baseAccumulator.length !== basePeaks) {
107
+ throw new Error(`base accumulator has ${baseAccumulator.length} peaks; trusted base size ${baseSize} has ${basePeaks}`);
108
+ }
109
+ // The first link must continue from that size, not from a size it names
110
+ // itself (ADR-0066 D5.4).
111
+ if (firstLink.treeSize1 !== baseSize) {
112
+ throw new Error(`first consistency proof declares tree-size-1 ${firstLink.treeSize1}; the trusted base size is ${baseSize}`);
106
113
  }
107
114
  // Contiguity: each link continues where the previous one sealed.
108
115
  for (let i = 1; i < links.length; i++) {
@@ -115,10 +122,15 @@ export async function freshenReceipt(input) {
115
122
  if (lastLink.treeSize2 !== sealedSize) {
116
123
  throw new Error(`consistency chain ends at size ${lastLink.treeSize2} but the checkpoint sealed size ${sealedSize}`);
117
124
  }
118
- // Fold the chain to the latest accumulator (self-check target).
119
- let accumulator = input.accumulatorFrom ?? [];
125
+ // Fold the chain to the latest accumulator (self-check target). Each step
126
+ // runs against a size the caller trusts, never one read off the link being
127
+ // folded: the trusted base for the first link, and the size the previous
128
+ // link was just folded TO for every link after it.
129
+ let accumulator = baseAccumulator;
130
+ let sizeFrom = baseSize;
120
131
  for (const p of links) {
121
- accumulator = await computeCheckpointAccumulator(p, accumulator);
132
+ accumulator = await computeCheckpointAccumulator(p, accumulator, sizeFrom);
133
+ sizeFrom = p.treeSize2;
122
134
  }
123
135
  const aLatest = accumulator;
124
136
  // Cross-check the fold against the checkpoint we are borrowing from (F1): the
package/dist/index.d.ts CHANGED
@@ -14,7 +14,27 @@ export type { FreshenReceiptInput, FreshenReceiptResult, } from "./freshen-recei
14
14
  * re-exported here to preserve the receipt-verify public surface.
15
15
  */
16
16
  export { peakMMRIndexes } from "@forestrie/merklelog";
17
- export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
17
+ /**
18
+ * `decodeTrustRootFromGenesis` keeps its origin/main signature and
19
+ * behaviour (`Promise<RootVerifyKey>`, including a KS256 v2 bootstrap key
20
+ * resolving without a throw) so pinned consumers (mcp-verify 1.0.0) are
21
+ * unaffected. `decodeTrustRootDetailsFromGenesis` is the additive sibling
22
+ * (plan-2609-07 L3) carrying `bootstrapKeyXy` alongside `key`.
23
+ */
24
+ export { decodeTrustRootDetailsFromGenesis, decodeTrustRootFromGenesis, } from "./decode-trust-root-from-genesis.js";
25
+ export type { DecodedTrustRoot } from "./decoded-trust-root.js";
26
+ /**
27
+ * Genesis document label constants (plan-2609-07 L3): exported from the
28
+ * package root so consumers stop redefining them locally (mcp-resolve's own
29
+ * `genesis-binding.ts` did, before this).
30
+ */
31
+ export { FOREST_GENESIS_LABEL_BOOTSTRAP_KEY, FOREST_GENESIS_LABEL_CHAIN_ID, FOREST_GENESIS_LABEL_GENESIS_ALG, FOREST_GENESIS_LABEL_GENESIS_VERSION, FOREST_GENESIS_LABEL_LOG_ID, FOREST_GENESIS_LABEL_UNIVOCITY_ADDR, } from "./forest-genesis-labels.js";
32
+ /**
33
+ * Genesis-bound chain binding (plan-2609-07 L3): mirrors mcp-resolve's own
34
+ * `decodeChainBindingFromGenesis` so that consumer can delete its copy.
35
+ */
36
+ export { decodeChainBindingFromGenesis } from "./decode-chain-binding-from-genesis.js";
37
+ export type { ChainBinding } from "./chain-binding.js";
18
38
  export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
19
39
  export { verifyReceiptOffline } from "./verify-grant-receipt-offline.js";
20
40
  /**
@@ -56,7 +76,7 @@ export { findGrantLeafInMassif } from "./find-grant-leaf.js";
56
76
  export type { LocatedLeaf } from "./find-grant-leaf.js";
57
77
  /** Re-exported so callers of findGrantLeafInMassif can catch it (FOR-344). */
58
78
  export { MissingIndexError } from "@forestrie/merklelog";
59
- export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, type CheckpointChainLink, type CheckpointChainResult, type CheckpointConsistencyProof, } from "./checkpoint-chain.js";
79
+ export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointSignedSizeMismatchError, type CheckpointChainLink, type CheckpointChainResult, type CheckpointConsistencyProof, } from "./checkpoint-chain.js";
60
80
  /**
61
81
  * Univocity leaf commitment hash. Was CLI-private (forestrie-cli's own
62
82
  * 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
@@ -7,7 +7,25 @@ export { freshenReceipt } from "./freshen-receipt.js";
7
7
  * re-exported here to preserve the receipt-verify public surface.
8
8
  */
9
9
  export { peakMMRIndexes } from "@forestrie/merklelog";
10
- export { decodeTrustRootFromGenesis } from "./decode-trust-root-from-genesis.js";
10
+ /**
11
+ * `decodeTrustRootFromGenesis` keeps its origin/main signature and
12
+ * behaviour (`Promise<RootVerifyKey>`, including a KS256 v2 bootstrap key
13
+ * resolving without a throw) so pinned consumers (mcp-verify 1.0.0) are
14
+ * unaffected. `decodeTrustRootDetailsFromGenesis` is the additive sibling
15
+ * (plan-2609-07 L3) carrying `bootstrapKeyXy` alongside `key`.
16
+ */
17
+ export { decodeTrustRootDetailsFromGenesis, decodeTrustRootFromGenesis, } from "./decode-trust-root-from-genesis.js";
18
+ /**
19
+ * Genesis document label constants (plan-2609-07 L3): exported from the
20
+ * package root so consumers stop redefining them locally (mcp-resolve's own
21
+ * `genesis-binding.ts` did, before this).
22
+ */
23
+ export { FOREST_GENESIS_LABEL_BOOTSTRAP_KEY, FOREST_GENESIS_LABEL_CHAIN_ID, FOREST_GENESIS_LABEL_GENESIS_ALG, FOREST_GENESIS_LABEL_GENESIS_VERSION, FOREST_GENESIS_LABEL_LOG_ID, FOREST_GENESIS_LABEL_UNIVOCITY_ADDR, } from "./forest-genesis-labels.js";
24
+ /**
25
+ * Genesis-bound chain binding (plan-2609-07 L3): mirrors mcp-resolve's own
26
+ * `decodeChainBindingFromGenesis` so that consumer can delete its copy.
27
+ */
28
+ export { decodeChainBindingFromGenesis } from "./decode-chain-binding-from-genesis.js";
11
29
  export { verifyGrantReceiptOffline } from "./verify-grant-receipt-offline.js";
12
30
  export { verifyReceiptOffline } from "./verify-grant-receipt-offline.js";
13
31
  /**
@@ -43,7 +61,7 @@ export { grantCommitmentHashFromGrant } from "./grant-commitment.js";
43
61
  export { findGrantLeafInMassif } from "./find-grant-leaf.js";
44
62
  /** Re-exported so callers of findGrantLeafInMassif can catch it (FOR-344). */
45
63
  export { MissingIndexError } from "@forestrie/merklelog";
46
- export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, } from "./checkpoint-chain.js";
64
+ export { accumulatorPayload, checkpointConsistencyProof, computeCheckpointAccumulator, verifyCheckpointChain, CheckpointHighSSignatureError, CheckpointSignedSizeMismatchError, } from "./checkpoint-chain.js";
47
65
  /**
48
66
  * Univocity leaf commitment hash. Was CLI-private (forestrie-cli's own
49
67
  * mirror, "hoist to the library when the FOR-297 multi-hop resolver lands");
@@ -1 +1 @@
1
- {"version":3,"file":"parse-receipt.d.ts","sourceRoot":"","sources":["../src/parse-receipt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAIlD,MAAM,MAAM,SAAS,GAAG;IACtB,eAAe,EAAE,UAAU;IAC3B,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,OAAO,EAAE,UAAU,GAAG,IAAI;IAC1B,SAAS,EAAE,UAAU;CACtB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW1D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAiB1D;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpD,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAUtB;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,UAAU,GAAG;IACtD,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;CACtB,CAwDA"}
1
+ {"version":3,"file":"parse-receipt.d.ts","sourceRoot":"","sources":["../src/parse-receipt.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAElD,MAAM,MAAM,SAAS,GAAG;IACtB,eAAe,EAAE,UAAU;IAC3B,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,OAAO,EAAE,UAAU,GAAG,IAAI;IAC1B,SAAS,EAAE,UAAU;CACtB,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW1D;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAiB1D;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACpD,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAUtB;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,UAAU,GAAG;IACtD,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,SAAS,CAAC;CACtB,CAwDA"}
@@ -1,5 +1,4 @@
1
- import { decodeCborDeterministic } from "@forestrie/encoding";
2
- const VDS_COSE_RECEIPT_PROOFS_TAG = 396;
1
+ import { COSE_LABEL_VDP, decodeCborDeterministic } from "@forestrie/encoding";
3
2
  export function unwrapCoseSign1Tag(value) {
4
3
  if (value && typeof value === "object" && !(value instanceof Map)) {
5
4
  const tagged = value;
@@ -58,7 +57,7 @@ export function parseReceipt(receiptBytes) {
58
57
  throw new Error("Receipt payload must be nil (detached) or 32-byte peak hash");
59
58
  }
60
59
  const unprotected = toHeaderMap(coseSign1[1]);
61
- const proofsRaw = unprotected.get(VDS_COSE_RECEIPT_PROOFS_TAG);
60
+ const proofsRaw = unprotected.get(COSE_LABEL_VDP);
62
61
  if (!proofsRaw || typeof proofsRaw !== "object") {
63
62
  throw new Error("Receipt missing header 396 (inclusion proof)");
64
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestrie/receipt-verify",
3
- "version": "1.0.0",
3
+ "version": "2.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.2.0",
32
- "@forestrie/encoding": "0.7.0",
33
- "@forestrie/merklelog": "0.3.0"
31
+ "@forestrie/chain-rpc": "0.3.0",
32
+ "@forestrie/encoding": "0.8.0",
33
+ "@forestrie/merklelog": "0.4.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "esbuild": "^0.24.0",
@@ -6,11 +6,18 @@
6
6
  * Golden vectors for receipt construction/verification are tracked by
7
7
  * FOR-289.
8
8
  */
9
- import { mergeUnprotectedIntoCoseSign1 } from "@forestrie/encoding";
9
+ import {
10
+ COSE_LABEL_VDP,
11
+ mergeUnprotectedIntoCoseSign1,
12
+ } from "@forestrie/encoding";
10
13
  import { HEADER_IDTIMESTAMP } from "./forest-genesis-labels.js";
11
14
 
12
- /** SCITT transparent statement unprotected receipt label (grants.md §3.2). */
13
- export const HEADER_RECEIPT = 396;
15
+ /**
16
+ * SCITT transparent statement unprotected receipt label (grants.md §3.2).
17
+ * Alias of {@link COSE_LABEL_VDP} — kept exported under this name for
18
+ * existing callers.
19
+ */
20
+ export const HEADER_RECEIPT = COSE_LABEL_VDP;
14
21
 
15
22
  export function attachReceiptAndIdtimestampToTransparentStatement(
16
23
  statementBytes: Uint8Array,
@@ -16,8 +16,11 @@
16
16
  */
17
17
 
18
18
  import {
19
+ COSE_LABEL_PEAK_RECEIPTS,
20
+ COSE_LABEL_VDP,
19
21
  decodeCborDeterministic,
20
22
  encodeCborDeterministic,
23
+ readProtectedTreeSize2,
21
24
  } from "@forestrie/encoding";
22
25
  import {
23
26
  calculateRoot,
@@ -34,12 +37,11 @@ import {
34
37
  unwrapCoseSign1Tag,
35
38
  type CoseSign1,
36
39
  } from "./parse-receipt.js";
40
+ import { decodeConsistencyProofFromUnprotected } from "./decode-checkpoint-consistency-proof.js";
37
41
  import { SubtleHasher } from "./subtle-hasher.js";
38
42
 
39
- const VDS_COSE_RECEIPT_PROOFS_TAG = 396;
40
- const SEAL_PEAK_RECEIPTS_LABEL = -65931;
43
+ /** Not in scope for the shared cose-labels module (FOR-568 §4.1). */
41
44
  const DELEGATION_CERT_LABEL = 1000;
42
- const VDP_CONSISTENCY_PROOF_KEY = -2;
43
45
 
44
46
  /**
45
47
  * Re-exported for compatibility. The MMR proof math and massif node store now
@@ -54,7 +56,14 @@ export type ParsedCheckpoint = {
54
56
  unprotected: Map<number, unknown>;
55
57
  /** Pre-signed peak receipts (label -65931), or null when absent. */
56
58
  peakReceipts: unknown[] | null;
57
- /** Sealed tree size (tree-size-2 of the embedded consistency proof). */
59
+ /**
60
+ * Sealed tree size: the SIGNED `tree-size-2` from the checkpoint's
61
+ * PROTECTED header (ADR-0066 D1 as amended, label -65933), not the
62
+ * unprotected consistency proof's declared value. `null` when the
63
+ * checkpoint carries no embedded consistency proof or no signed
64
+ * tree-size-2 — such a checkpoint is not verifiable and callers must
65
+ * treat it exactly as they would a checkpoint with no proof at all.
66
+ */
58
67
  mmrSize: bigint | null;
59
68
  delegationCert: Uint8Array | null;
60
69
  };
@@ -65,7 +74,7 @@ export function parseCheckpoint(checkpointBytes: Uint8Array): ParsedCheckpoint {
65
74
  const coseSign1 = requireCoseSign1(unwrapCoseSign1Tag(decoded));
66
75
  const unprotected = toHeaderMap(coseSign1[1]);
67
76
 
68
- const peakReceiptsRaw = unprotected.get(SEAL_PEAK_RECEIPTS_LABEL);
77
+ const peakReceiptsRaw = unprotected.get(COSE_LABEL_PEAK_RECEIPTS);
69
78
  const peakReceipts = Array.isArray(peakReceiptsRaw) ? peakReceiptsRaw : null;
70
79
 
71
80
  const delegationCertRaw = unprotected.get(DELEGATION_CERT_LABEL);
@@ -78,7 +87,7 @@ export function parseCheckpoint(checkpointBytes: Uint8Array): ParsedCheckpoint {
78
87
  coseSign1,
79
88
  unprotected,
80
89
  peakReceipts,
81
- mmrSize: sealedSizeFromCheckpoint(unprotected),
90
+ mmrSize: sealedSizeFromCheckpoint(coseSign1, unprotected),
82
91
  delegationCert,
83
92
  };
84
93
  }
@@ -186,7 +195,7 @@ export function assembleReceiptFromProof(
186
195
  const verifiableProofs = new Map<number, unknown>([
187
196
  [-1, [inclusionProofEntry]],
188
197
  ]);
189
- receiptUnprotected.set(VDS_COSE_RECEIPT_PROOFS_TAG, verifiableProofs);
198
+ receiptUnprotected.set(COSE_LABEL_VDP, verifiableProofs);
190
199
 
191
200
  // Peak receipts are signed with detached payload; emit nil so verify uses
192
201
  // the peak derived from the inclusion proof.
@@ -252,24 +261,34 @@ function cborBytes(value: unknown): Uint8Array {
252
261
  }
253
262
 
254
263
  /**
255
- * Sealed mmr size from a format-v3 checkpoint: tree-size-2 of the consistency
256
- * proof (`bstr .cbor [tree-size-1, tree-size-2, paths, right-peaks]`) under
257
- * the verifiable-proofs unprotected header (label 396, key -2).
264
+ * Sealed mmr size from a format-v3 checkpoint: the SIGNED `tree-size-2`
265
+ * (ADR-0066 D1 as amended, label -65933) from the PROTECTED header — not the
266
+ * unprotected consistency proof's declared value, which a checkpoint without
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.
272
+ *
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.
258
277
  */
259
278
  function sealedSizeFromCheckpoint(
279
+ coseSign1: CoseSign1,
260
280
  unprotected: Map<number, unknown>,
261
281
  ): bigint | null {
262
- const vdpRaw = unprotected.get(VDS_COSE_RECEIPT_PROOFS_TAG);
263
- if (vdpRaw === undefined || vdpRaw === null) return null;
264
- const vdp = toHeaderMap(vdpRaw as Map<number, unknown>);
265
- const proofBstr = vdp.get(VDP_CONSISTENCY_PROOF_KEY);
266
- if (!(proofBstr instanceof Uint8Array)) return null;
267
- const proof = decodeCborDeterministic(proofBstr);
268
- if (!Array.isArray(proof) || proof.length < 2) return null;
269
- const treeSize2 = proof[1];
270
- if (typeof treeSize2 === "bigint") return treeSize2;
271
- if (typeof treeSize2 === "number" && Number.isSafeInteger(treeSize2)) {
272
- return BigInt(treeSize2);
282
+ let declared;
283
+ try {
284
+ declared = decodeConsistencyProofFromUnprotected(unprotected);
285
+ } catch {
286
+ return null;
287
+ }
288
+ if (declared === null) return null;
289
+ try {
290
+ return readProtectedTreeSize2(coseSign1[0]);
291
+ } catch {
292
+ return null;
273
293
  }
274
- return null;
275
294
  }