@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.
- package/dist/attach-transparent-statement-receipt.d.ts +5 -1
- package/dist/attach-transparent-statement-receipt.d.ts.map +1 -1
- package/dist/attach-transparent-statement-receipt.js +7 -3
- package/dist/build-receipt-offline.d.ts +8 -1
- package/dist/build-receipt-offline.d.ts.map +1 -1
- package/dist/build-receipt-offline.js +31 -24
- package/dist/chain-binding.d.ts +19 -0
- package/dist/chain-binding.d.ts.map +1 -0
- package/dist/chain-binding.js +1 -0
- package/dist/checkpoint-chain.d.ts +130 -17
- package/dist/checkpoint-chain.d.ts.map +1 -1
- package/dist/checkpoint-chain.js +216 -117
- package/dist/decode-chain-binding-from-genesis.d.ts +15 -0
- package/dist/decode-chain-binding-from-genesis.d.ts.map +1 -0
- package/dist/decode-chain-binding-from-genesis.js +55 -0
- package/dist/decode-checkpoint-consistency-proof.d.ts +38 -0
- package/dist/decode-checkpoint-consistency-proof.d.ts.map +1 -0
- package/dist/decode-checkpoint-consistency-proof.js +108 -0
- package/dist/decode-genesis-cbor-map.d.ts +12 -0
- package/dist/decode-genesis-cbor-map.d.ts.map +1 -0
- package/dist/decode-genesis-cbor-map.js +26 -0
- package/dist/decode-trust-root-from-genesis.d.ts +21 -3
- package/dist/decode-trust-root-from-genesis.d.ts.map +1 -1
- package/dist/decode-trust-root-from-genesis.js +32 -23
- package/dist/decoded-trust-root.d.ts +17 -0
- package/dist/decoded-trust-root.d.ts.map +1 -0
- package/dist/decoded-trust-root.js +1 -0
- package/dist/forest-genesis-labels.d.ts +7 -0
- package/dist/forest-genesis-labels.d.ts.map +1 -1
- package/dist/forest-genesis-labels.js +7 -0
- package/dist/freshen-receipt.d.ts +23 -6
- package/dist/freshen-receipt.d.ts.map +1 -1
- package/dist/freshen-receipt.js +29 -17
- package/dist/index.d.ts +22 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -2
- package/dist/parse-receipt.d.ts.map +1 -1
- package/dist/parse-receipt.js +2 -3
- package/package.json +4 -4
- package/src/attach-transparent-statement-receipt.ts +10 -3
- package/src/build-receipt-offline.ts +41 -22
- package/src/chain-binding.ts +18 -0
- package/src/checkpoint-chain.ts +279 -136
- package/src/decode-chain-binding-from-genesis.ts +77 -0
- package/src/decode-checkpoint-consistency-proof.ts +136 -0
- package/src/decode-genesis-cbor-map.ts +27 -0
- package/src/decode-trust-root-from-genesis.ts +40 -25
- package/src/decoded-trust-root.ts +17 -0
- package/src/forest-genesis-labels.ts +7 -0
- package/src/freshen-receipt.ts +56 -25
- package/src/index.ts +33 -1
- 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
|
|
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
|
-
*
|
|
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,
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
*
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
9
|
-
* size, in ascending contiguous order (the raw per-checkpoint
|
|
10
|
-
* `paths`). The chain's last link must end at the
|
|
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
|
|
13
|
-
*
|
|
14
|
-
|
|
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":"
|
|
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"}
|
package/dist/freshen-receipt.js
CHANGED
|
@@ -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:
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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");
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,4BAA4B,CAAC;AACpC,oFAAoF;AACpF,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,YAAY,EAAE,8BAA8B,EAAE,MAAM,mCAAmC,CAAC;AACxF,YAAY,EAAE,yBAAyB,EAAE,MAAM,mCAAmC,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,YAAY,EACV,wBAAwB,EACxB,uBAAuB,EACvB,eAAe,EACf,gBAAgB,GACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,GAChB,MAAM,4BAA4B,CAAC;AACpC,+EAA+E;AAC/E,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EACV,mBAAmB,EACnB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAC9B;;;GAGG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/dist/parse-receipt.js
CHANGED
|
@@ -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(
|
|
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": "
|
|
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.
|
|
32
|
-
"@forestrie/encoding": "0.
|
|
33
|
-
"@forestrie/merklelog": "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 {
|
|
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
|
-
/**
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
/**
|
|
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(
|
|
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(
|
|
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
|
|
256
|
-
*
|
|
257
|
-
*
|
|
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
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
return
|
|
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
|
}
|