@forestrie/receipt-verify 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/checkpoint-chain-receipt-outcome.d.ts +56 -0
  2. package/dist/checkpoint-chain-receipt-outcome.d.ts.map +1 -0
  3. package/dist/checkpoint-chain-receipt-outcome.js +1 -0
  4. package/dist/checkpoint-link-record.d.ts +69 -0
  5. package/dist/checkpoint-link-record.d.ts.map +1 -0
  6. package/dist/checkpoint-link-record.js +1 -0
  7. package/dist/checkpoint-link-verifier.d.ts +80 -0
  8. package/dist/checkpoint-link-verifier.d.ts.map +1 -0
  9. package/dist/checkpoint-link-verifier.js +181 -0
  10. package/dist/inclusion-walk-end.d.ts +6 -0
  11. package/dist/inclusion-walk-end.d.ts.map +1 -0
  12. package/dist/inclusion-walk-end.js +35 -0
  13. package/dist/index.d.ts +14 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +11 -0
  16. package/dist/known-accumulator.d.ts.map +1 -1
  17. package/dist/known-accumulator.js +25 -9
  18. package/dist/ks256-attestation-usage.d.ts +50 -0
  19. package/dist/ks256-attestation-usage.d.ts.map +1 -0
  20. package/dist/ks256-attestation-usage.js +51 -0
  21. package/dist/select-ks256-attestation.d.ts +57 -0
  22. package/dist/select-ks256-attestation.d.ts.map +1 -0
  23. package/dist/select-ks256-attestation.js +119 -0
  24. package/dist/verify-grant-receipt-offline.d.ts +11 -0
  25. package/dist/verify-grant-receipt-offline.d.ts.map +1 -1
  26. package/dist/verify-grant-receipt-offline.js +25 -23
  27. package/dist/verify-receipt-against-checkpoint-chain.d.ts +42 -0
  28. package/dist/verify-receipt-against-checkpoint-chain.d.ts.map +1 -0
  29. package/dist/verify-receipt-against-checkpoint-chain.js +205 -0
  30. package/package.json +3 -3
  31. package/src/checkpoint-chain-receipt-outcome.ts +67 -0
  32. package/src/checkpoint-link-record.ts +74 -0
  33. package/src/checkpoint-link-verifier.ts +241 -0
  34. package/src/inclusion-walk-end.ts +38 -0
  35. package/src/index.ts +30 -0
  36. package/src/known-accumulator.ts +30 -8
  37. package/src/ks256-attestation-usage.ts +103 -0
  38. package/src/select-ks256-attestation.ts +151 -0
  39. package/src/verify-grant-receipt-offline.ts +43 -27
  40. package/src/verify-receipt-against-checkpoint-chain.ts +256 -0
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The typed result of {@link verifyReceiptAgainstCheckpointChain}: the
3
+ * verdict, where it was decided, and everything a reporter needs (folded
4
+ * links, per-link delegation records, attestation usage).
5
+ */
6
+ import type { CheckpointChainLink } from "./checkpoint-chain.js";
7
+ import type { CheckpointLinkFailure, CheckpointLinkRecord } from "./checkpoint-link-record.js";
8
+ import type { Ks256AttestationUsage } from "./ks256-attestation-usage.js";
9
+ /**
10
+ * Where a failing run stopped:
11
+ * - `input`: the caller's inputs (no root keys, a forest genesis without a
12
+ * chain binding).
13
+ * - `chain`: the checkpoint chain did not fold or a link was refused.
14
+ * - `parse`: the receipt or idtimestamp did not parse.
15
+ * - `anchor`: the chain folded but the receipt's leaf-derived peak is in no
16
+ * link (`peak_not_in_checkpoint_chain`), or the leaf is past the chain
17
+ * (`receipt_newer_than_checkpoint_chain`; refresh, not tamper).
18
+ * - `delegation`: the receipt matched, but a link covering its leaf was
19
+ * signed under an expired certificate (`delegation_expired`).
20
+ */
21
+ export type CheckpointChainReceiptStage = "input" | "chain" | "parse" | "anchor" | "delegation";
22
+ /** Fields every outcome carries, pass or fail. */
23
+ type OutcomeCommon = {
24
+ /** The authenticated links (the verified prefix on a chain failure). */
25
+ links: CheckpointChainLink[];
26
+ /** Per-link delegation facts, one per authenticated link. */
27
+ records: CheckpointLinkRecord[];
28
+ /** Every supplied attestation, as used or unused with a reason. */
29
+ attestations: Ks256AttestationUsage;
30
+ };
31
+ /** A receipt anchored in a retained checkpoint chain, or why not. */
32
+ export type CheckpointChainReceiptOutcome = (OutcomeCommon & {
33
+ ok: true;
34
+ /** Index of the newest link whose accumulator holds the peak. */
35
+ matchedLink: number;
36
+ /** Index of the matching peak within that link's accumulator. */
37
+ matchedPeak: number;
38
+ }) | (OutcomeCommon & {
39
+ ok: false;
40
+ stage: CheckpointChainReceiptStage;
41
+ /**
42
+ * Stable reason: a {@link CheckpointLinkFailure} reason, a
43
+ * `CheckpointChainResult` reason prefixed `checkpoint_chain_`, a
44
+ * receipt parse reason, or one of the anchor/delegation reasons
45
+ * above.
46
+ */
47
+ reason: string;
48
+ /** Offending link index, for `chain` and `delegation`. */
49
+ at?: number;
50
+ /** Human detail from the chain fold, when it failed there. */
51
+ detail?: string;
52
+ /** The per-link verifier's refusal, when it caused a `chain` failure. */
53
+ linkFailure?: CheckpointLinkFailure;
54
+ });
55
+ export {};
56
+ //# sourceMappingURL=checkpoint-chain-receipt-outcome.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-chain-receipt-outcome.d.ts","sourceRoot":"","sources":["../src/checkpoint-chain-receipt-outcome.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAE1E;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,2BAA2B,GACnC,OAAO,GACP,OAAO,GACP,OAAO,GACP,QAAQ,GACR,YAAY,CAAC;AAEjB,kDAAkD;AAClD,KAAK,aAAa,GAAG;IACnB,wEAAwE;IACxE,KAAK,EAAE,mBAAmB,EAAE,CAAC;IAC7B,6DAA6D;IAC7D,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,mEAAmE;IACnE,YAAY,EAAE,qBAAqB,CAAC;CACrC,CAAC;AAEF,qEAAqE;AACrE,MAAM,MAAM,6BAA6B,GACrC,CAAC,aAAa,GAAG;IACf,EAAE,EAAE,IAAI,CAAC;IACT,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC,GACF,CAAC,aAAa,GAAG;IACf,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,2BAA2B,CAAC;IACnC;;;;;OAKG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,0DAA0D;IAC1D,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACrC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ /**
2
+ * What the per-link checkpoint verifier learned about each link of a
3
+ * retained `.sth` chain ({@link makeCheckpointLinkVerifier}). Consumers
4
+ * (mcp-verify, forestrie-cli) report these facts; the post-match expiry
5
+ * check ({@link checkCheckpointLinkExpiry}) reads them.
6
+ */
7
+ import type { DelegationConstraints } from "./resolve-delegated-verify-key.js";
8
+ /**
9
+ * Which key verified a link's checkpoint signature, and what vouched for it:
10
+ *
11
+ * - `es256-root`: an ES256 root key verified the checkpoint directly (no
12
+ * certificate, or the root signed despite one).
13
+ * - `delegated`: the label-1000 certificate's delegated key verified it, and
14
+ * an ES256 root (or passkey root) vouched for the certificate.
15
+ * - `ks256-root`: the delegated key verified it, and a KS256 root vouched for
16
+ * the certificate: by ecrecover, or by an ERC-1271 attestation when
17
+ * {@link CheckpointLinkRecord.attestationNeeded}.
18
+ */
19
+ export type CheckpointLinkVerifiedBy = "es256-root" | "ks256-root" | "delegated";
20
+ /** One authenticated link of a checkpoint chain. */
21
+ export type CheckpointLinkRecord = {
22
+ /** Position in the supplied checkpoint list (fold order). */
23
+ index: number;
24
+ /** The checkpoint's SIGNED tree size (protected label -65933). */
25
+ signedSize: bigint;
26
+ /**
27
+ * The delegation certificate's limits, `null` when the link carries no
28
+ * certificate or the certificate declares none of labels 3/4/8/9.
29
+ */
30
+ constraints: DelegationConstraints | null;
31
+ verifiedBy: CheckpointLinkVerifiedBy;
32
+ /**
33
+ * Hex (no `0x`) of the digest of the attestation this link needed, set
34
+ * only when {@link attestationNeeded}.
35
+ */
36
+ attestationUsed?: string;
37
+ /**
38
+ * Index, in the caller's attestation list, of the attestation made for
39
+ * this link's certificate (digest and signature hash match), whether or
40
+ * not the link needed it.
41
+ */
42
+ attestationIndex?: number;
43
+ /**
44
+ * Indexes of attestations made for this link's certificate that were
45
+ * tried before {@link attestationIndex} and failed their own checks
46
+ * (address, chain id, magic).
47
+ */
48
+ attestationsRejected?: number[];
49
+ /**
50
+ * The link verified only because of an attestation: re-resolving its
51
+ * certificate under the same roots without one fails. Pure and offline.
52
+ */
53
+ attestationNeeded: boolean;
54
+ };
55
+ /** Why the per-link verifier refused a link. */
56
+ export type CheckpointLinkFailure = {
57
+ index: number;
58
+ /**
59
+ * `delegation_invalid`, a KS256 reason (`ks256_root_not_recovered`,
60
+ * `ks256_attestation_*`, `delegation_not_ks256`), `delegation_out_of_range`,
61
+ * `signature_invalid` or `proof_malformed`.
62
+ */
63
+ reason: string;
64
+ /** As {@link CheckpointLinkRecord.attestationIndex}. */
65
+ attestationIndex?: number;
66
+ /** As {@link CheckpointLinkRecord.attestationsRejected}. */
67
+ attestationsRejected?: number[];
68
+ };
69
+ //# sourceMappingURL=checkpoint-link-record.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-link-record.d.ts","sourceRoot":"","sources":["../src/checkpoint-link-record.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE/E;;;;;;;;;;GAUG;AACH,MAAM,MAAM,wBAAwB,GAChC,YAAY,GACZ,YAAY,GACZ,WAAW,CAAC;AAEhB,oDAAoD;AACpD,MAAM,MAAM,oBAAoB,GAAG;IACjC,6DAA6D;IAC7D,KAAK,EAAE,MAAM,CAAC;IACd,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,WAAW,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC1C,UAAU,EAAE,wBAAwB,CAAC;IACrC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC;;;OAGG;IACH,iBAAiB,EAAE,OAAO,CAAC;CAC5B,CAAC;AAEF,gDAAgD;AAChD,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4DAA4D;IAC5D,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,80 @@
1
+ import type { CheckpointLinkFailure, CheckpointLinkRecord } from "./checkpoint-link-record.js";
2
+ import type { Ks256Attestation } from "./ks256-attestation.js";
3
+ import { type DelegationConstraints } from "./resolve-delegated-verify-key.js";
4
+ import { type RootVerifyKey } from "./root-verify-key.js";
5
+ /** The per-link verifier and what it has learned so far. */
6
+ export type CheckpointLinkVerifier = {
7
+ /** Pass as `verifyCheckpointChain({ verifySignature })`. */
8
+ verifySignature: (checkpointBytes: Uint8Array, detachedPayload: Uint8Array) => Promise<boolean>;
9
+ /** One record per link that verified, in fold order. */
10
+ records: CheckpointLinkRecord[];
11
+ /** Set when the verifier refused a link; the chain fold stops there. */
12
+ failure(): CheckpointLinkFailure | undefined;
13
+ };
14
+ /**
15
+ * Build the per-link signature verifier for {@link verifyCheckpointChain}.
16
+ *
17
+ * For each checkpoint, in fold order: resolve its label-1000 certificate
18
+ * under `rootKeys` (with the attestation made for that certificate, if any),
19
+ * verify the COSE signature over the FOLDED accumulator (the detached
20
+ * payload the fold computes, never one read from the checkpoint; ADR-0046),
21
+ * then enforce the certificate's size bound against the signed size. KS256
22
+ * roots vouch only for certificates; only ES256 roots may verify a
23
+ * checkpoint directly.
24
+ *
25
+ * The callback must be used for exactly one fold: link indices are the call
26
+ * count.
27
+ *
28
+ * @param opts.rootKeys - ES256 keys and KS256 addresses, from genesis or
29
+ * caller-known log keys.
30
+ * @param opts.attestations - ERC-1271 attestations for KS256 contract
31
+ * roots; each certificate takes the one with its digest and signature
32
+ * hash ({@link selectKs256Attestation}).
33
+ * @param opts.forestChainId - The forest genesis chain id every attestation
34
+ * must carry; without it a contract root fails
35
+ * `ks256_attestation_chain_unbound`.
36
+ */
37
+ export declare function makeCheckpointLinkVerifier(opts: {
38
+ rootKeys: readonly RootVerifyKey[];
39
+ attestations?: readonly Ks256Attestation[] | undefined;
40
+ forestChainId?: bigint | undefined;
41
+ }): CheckpointLinkVerifier;
42
+ /**
43
+ * The size bound univocity enforces on a checkpoint, judged from signed
44
+ * data alone: with `mmrIndex = signedSize > 0 ? signedSize - 1 : 0`, reject
45
+ * `mmrIndex < mmrStart || mmrIndex > mmrEnd` (`delegationVerifier.sol`,
46
+ * `_Univocity.sol` passes `claimedSize > 0 ? claimedSize - 1 : 0`).
47
+ *
48
+ * @returns `delegation_out_of_range` on either side of the window.
49
+ */
50
+ export declare function checkCheckpointLinkSizeBound(constraints: DelegationConstraints, signedSize: bigint): {
51
+ ok: true;
52
+ } | {
53
+ ok: false;
54
+ reason: "delegation_out_of_range";
55
+ };
56
+ /**
57
+ * Expiry, judged only AFTER a receipt matched the chain, when its leaf's
58
+ * idtimestamp and mmrIndex are bound by the leaf hash and inclusion path
59
+ * rather than being caller claims. A link whose signed size covers the leaf
60
+ * (`signedSize > leaf.mmrIndex`) was signed after the leaf was sequenced, so
61
+ * a leaf time past `expiresAt` proves signing past the key's lifetime. A
62
+ * link that does not cover the leaf says nothing about its time. Never
63
+ * wall-clock; no `issuedAt` bound (a certificate is legitimately signed a
64
+ * few seconds after the leaf it first covers).
65
+ *
66
+ * @param records - From {@link makeCheckpointLinkVerifier}.
67
+ * @param leaf.idtimestamp - The bound leaf's snowflake idtimestamp.
68
+ * @returns The first offending link as `at`.
69
+ */
70
+ export declare function checkCheckpointLinkExpiry(records: readonly CheckpointLinkRecord[], leaf: {
71
+ mmrIndex: bigint;
72
+ idtimestamp: bigint;
73
+ }): {
74
+ ok: true;
75
+ } | {
76
+ ok: false;
77
+ reason: "delegation_expired";
78
+ at: number;
79
+ };
80
+ //# sourceMappingURL=checkpoint-link-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"checkpoint-link-verifier.d.ts","sourceRoot":"","sources":["../src/checkpoint-link-verifier.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EAErB,MAAM,6BAA6B,CAAC;AACrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAGL,KAAK,qBAAqB,EAC3B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGhF,4DAA4D;AAC5D,MAAM,MAAM,sBAAsB,GAAG;IACnC,4DAA4D;IAC5D,eAAe,EAAE,CACf,eAAe,EAAE,UAAU,EAC3B,eAAe,EAAE,UAAU,KACxB,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,wDAAwD;IACxD,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,wEAAwE;IACxE,OAAO,IAAI,qBAAqB,GAAG,SAAS,CAAC;CAC9C,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;IACnC,YAAY,CAAC,EAAE,SAAS,gBAAgB,EAAE,GAAG,SAAS,CAAC;IACvD,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACpC,GAAG,sBAAsB,CAkHzB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,qBAAqB,EAClC,UAAU,EAAE,MAAM,GACjB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,yBAAyB,CAAA;CAAE,CAMjE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,SAAS,oBAAoB,EAAE,EACxC,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CASxE"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Per-link trust for a retained checkpoint chain: the `verifySignature`
3
+ * callback {@link verifyCheckpointChain} takes, plus the delegation limits
4
+ * each link's label-1000 certificate imposes. Lifted from mcp-verify 0.6.1
5
+ * (`src/core/verify-shared.ts`) so mcp-verify and forestrie-cli share one
6
+ * copy; see mcp-verify `docs/trust-roots.md` ("Delegation limits under
7
+ * checkpoint-chain").
8
+ *
9
+ * The limits mirror univocity's on-chain check
10
+ * ([delegationVerifier.sol](https://github.com/forestrie/univocity/blob/main/src/checkpoints/lib/delegationVerifier.sol)):
11
+ * a checkpoint of claimed size `S` is anchored only when its delegation
12
+ * covers `mmrIndex = S > 0 ? S - 1 : 0`, i.e. `mmrStart <= mmrIndex <=
13
+ * mmrEnd`. Here `S` is the checkpoint's SIGNED tree size (label -65933), so
14
+ * a link the contract would refuse is refused offline too
15
+ * (`delegation_out_of_range`). Expiry needs a leaf the receipt binds and is
16
+ * judged after the receipt matched ({@link checkCheckpointLinkExpiry}).
17
+ *
18
+ * Pure over bytes; no network (canopy rule C1).
19
+ */
20
+ import { verifyCoseSign1WithParsedKey } from "@forestrie/encoding";
21
+ import { checkpointConsistencyProof } from "./checkpoint-chain.js";
22
+ import { idtimestampToUnixSeconds, resolveDelegatedVerifyKey, } from "./resolve-delegated-verify-key.js";
23
+ import { isParsedKs256RootKey } from "./root-verify-key.js";
24
+ import { resolveWithKs256Attestations } from "./select-ks256-attestation.js";
25
+ /**
26
+ * Build the per-link signature verifier for {@link verifyCheckpointChain}.
27
+ *
28
+ * For each checkpoint, in fold order: resolve its label-1000 certificate
29
+ * under `rootKeys` (with the attestation made for that certificate, if any),
30
+ * verify the COSE signature over the FOLDED accumulator (the detached
31
+ * payload the fold computes, never one read from the checkpoint; ADR-0046),
32
+ * then enforce the certificate's size bound against the signed size. KS256
33
+ * roots vouch only for certificates; only ES256 roots may verify a
34
+ * checkpoint directly.
35
+ *
36
+ * The callback must be used for exactly one fold: link indices are the call
37
+ * count.
38
+ *
39
+ * @param opts.rootKeys - ES256 keys and KS256 addresses, from genesis or
40
+ * caller-known log keys.
41
+ * @param opts.attestations - ERC-1271 attestations for KS256 contract
42
+ * roots; each certificate takes the one with its digest and signature
43
+ * hash ({@link selectKs256Attestation}).
44
+ * @param opts.forestChainId - The forest genesis chain id every attestation
45
+ * must carry; without it a contract root fails
46
+ * `ks256_attestation_chain_unbound`.
47
+ */
48
+ export function makeCheckpointLinkVerifier(opts) {
49
+ const rootKeys = [...opts.rootKeys];
50
+ const attestations = opts.attestations ?? [];
51
+ const es256Roots = rootKeys.filter((k) => !isParsedKs256RootKey(k));
52
+ const records = [];
53
+ let failure;
54
+ let calls = 0;
55
+ const verifySignature = async (checkpointBytes, detachedPayload) => {
56
+ const index = calls++;
57
+ const chosen = await resolveWithKs256Attestations(checkpointBytes, rootKeys, attestations, opts.forestChainId);
58
+ const resolution = chosen.resolution;
59
+ const fail = (reason) => {
60
+ failure = { index, reason };
61
+ if (chosen.selected !== undefined) {
62
+ failure.attestationIndex = chosen.selected;
63
+ }
64
+ if (chosen.rejected.length > 0) {
65
+ failure.attestationsRejected = chosen.rejected;
66
+ }
67
+ return false;
68
+ };
69
+ if (resolution.kind === "broken") {
70
+ return fail(resolution.reason ?? "delegation_invalid");
71
+ }
72
+ let signedSize;
73
+ try {
74
+ signedSize = checkpointConsistencyProof(checkpointBytes).signedTreeSize2;
75
+ }
76
+ catch {
77
+ // The fold read the same label moments ago; fail closed.
78
+ return fail("proof_malformed");
79
+ }
80
+ const candidates = [
81
+ ...(resolution.kind === "resolved"
82
+ ? [{ key: resolution.delegatedKey, delegated: true }]
83
+ : []),
84
+ ...es256Roots.map((key) => ({ key, delegated: false })),
85
+ ];
86
+ let signer;
87
+ for (const c of candidates) {
88
+ if (await verifyCoseSign1WithParsedKey(checkpointBytes, c.key, {
89
+ logPrefix: "checkpoint-chain",
90
+ detachedPayload,
91
+ })) {
92
+ signer = c;
93
+ break;
94
+ }
95
+ }
96
+ if (signer === undefined)
97
+ return fail("signature_invalid");
98
+ // A resolved certificate's limits bind the link even when a root key
99
+ // verified it directly: stricter than univocity, which would not accept
100
+ // a root signature alongside a certificate at all.
101
+ const constraints = resolution.kind === "resolved" ? resolution.constraints : null;
102
+ if (constraints !== null) {
103
+ const bound = checkCheckpointLinkSizeBound(constraints, signedSize);
104
+ if (!bound.ok)
105
+ return fail(bound.reason);
106
+ }
107
+ let verifiedBy = "es256-root";
108
+ if (signer.delegated) {
109
+ const viaEs256 = await resolveDelegatedVerifyKey(checkpointBytes, es256Roots);
110
+ verifiedBy = viaEs256.kind === "resolved" ? "delegated" : "ks256-root";
111
+ }
112
+ // Only a selected attestation can carry a certificate (a diagnostic
113
+ // stand-in never matches), so "needed" implies "selected".
114
+ const attestationNeeded = chosen.selected !== undefined &&
115
+ resolution.kind === "resolved" &&
116
+ (await resolveDelegatedVerifyKey(checkpointBytes, rootKeys)).kind ===
117
+ "broken";
118
+ const record = {
119
+ index,
120
+ signedSize,
121
+ constraints,
122
+ verifiedBy,
123
+ attestationNeeded,
124
+ };
125
+ if (chosen.rejected.length > 0) {
126
+ record.attestationsRejected = chosen.rejected;
127
+ }
128
+ if (chosen.selected !== undefined) {
129
+ record.attestationIndex = chosen.selected;
130
+ if (attestationNeeded) {
131
+ record.attestationUsed = bytesToHex(attestations[chosen.selected].digest);
132
+ }
133
+ }
134
+ records.push(record);
135
+ return true;
136
+ };
137
+ return { verifySignature, records, failure: () => failure };
138
+ }
139
+ /**
140
+ * The size bound univocity enforces on a checkpoint, judged from signed
141
+ * data alone: with `mmrIndex = signedSize > 0 ? signedSize - 1 : 0`, reject
142
+ * `mmrIndex < mmrStart || mmrIndex > mmrEnd` (`delegationVerifier.sol`,
143
+ * `_Univocity.sol` passes `claimedSize > 0 ? claimedSize - 1 : 0`).
144
+ *
145
+ * @returns `delegation_out_of_range` on either side of the window.
146
+ */
147
+ export function checkCheckpointLinkSizeBound(constraints, signedSize) {
148
+ const mmrIndex = signedSize > 0n ? signedSize - 1n : 0n;
149
+ if (mmrIndex < constraints.mmrStart || mmrIndex > constraints.mmrEnd) {
150
+ return { ok: false, reason: "delegation_out_of_range" };
151
+ }
152
+ return { ok: true };
153
+ }
154
+ /**
155
+ * Expiry, judged only AFTER a receipt matched the chain, when its leaf's
156
+ * idtimestamp and mmrIndex are bound by the leaf hash and inclusion path
157
+ * rather than being caller claims. A link whose signed size covers the leaf
158
+ * (`signedSize > leaf.mmrIndex`) was signed after the leaf was sequenced, so
159
+ * a leaf time past `expiresAt` proves signing past the key's lifetime. A
160
+ * link that does not cover the leaf says nothing about its time. Never
161
+ * wall-clock; no `issuedAt` bound (a certificate is legitimately signed a
162
+ * few seconds after the leaf it first covers).
163
+ *
164
+ * @param records - From {@link makeCheckpointLinkVerifier}.
165
+ * @param leaf.idtimestamp - The bound leaf's snowflake idtimestamp.
166
+ * @returns The first offending link as `at`.
167
+ */
168
+ export function checkCheckpointLinkExpiry(records, leaf) {
169
+ const leafSeconds = idtimestampToUnixSeconds(leaf.idtimestamp);
170
+ for (const r of records) {
171
+ if (r.constraints === null)
172
+ continue;
173
+ if (r.signedSize > leaf.mmrIndex && leafSeconds > r.constraints.expiresAt) {
174
+ return { ok: false, reason: "delegation_expired", at: r.index };
175
+ }
176
+ }
177
+ return { ok: true };
178
+ }
179
+ function bytesToHex(bytes) {
180
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
181
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The zero-based MMR index of the node an inclusion path of `pathLength`
3
+ * siblings reaches from `leafMmrIndex`: the peak the path claims to prove.
4
+ */
5
+ export declare function inclusionWalkEndIndex(leafMmrIndex: bigint, pathLength: number): bigint;
6
+ //# sourceMappingURL=inclusion-walk-end.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inclusion-walk-end.d.ts","sourceRoot":"","sources":["../src/inclusion-walk-end.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,GACjB,MAAM,CAeR"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Where an MMR inclusion walk ends. A leaf hash
3
+ * (`SHA-256(idtimestamp ‖ inner)`) does not commit the leaf's position, and
4
+ * a path of length 0 commits nothing more, so matching a recomputed peak by
5
+ * VALUE alone leaves the receipt's claimed `mmrIndex` unbound. Requiring the
6
+ * matched accumulator peak to sit at the node index the walk reaches binds
7
+ * it: interior nodes commit their position (`H(pos ‖ left ‖ right)`), and
8
+ * an empty path must start at the peak itself.
9
+ *
10
+ * Mirrors the position arithmetic of `calculateRoot` in
11
+ * [@forestrie/merklelog](https://github.com/forestrie/canopy/blob/main/packages/merklelog/src/mmr/algorithms.ts)
12
+ * (go-merklelog `mmr/proof.go`), without hashing.
13
+ */
14
+ import { indexHeight } from "@forestrie/merklelog";
15
+ /**
16
+ * The zero-based MMR index of the node an inclusion path of `pathLength`
17
+ * siblings reaches from `leafMmrIndex`: the peak the path claims to prove.
18
+ */
19
+ export function inclusionWalkEndIndex(leafMmrIndex, pathLength) {
20
+ let height = indexHeight(leafMmrIndex);
21
+ // 1-based position, as calculateRoot tracks it.
22
+ let pos = leafMmrIndex + 1n;
23
+ for (let i = 0; i < pathLength; i++) {
24
+ // `indexHeight(pos)` is the height of the node AFTER the current one:
25
+ // higher means the current node is a right child.
26
+ if (indexHeight(pos) > height) {
27
+ pos += 1n;
28
+ }
29
+ else {
30
+ pos += 2n << BigInt(height);
31
+ }
32
+ height += 1;
33
+ }
34
+ return pos - 1n;
35
+ }
package/dist/index.d.ts CHANGED
@@ -110,4 +110,18 @@ export { univocityLeafHash } from "./leaf-commitment.js";
110
110
  * snapshot over RPC, or proof-path-extend a stale one via massif nodes).
111
111
  */
112
112
  export { assertSnapshotBinding, decodeKnownAccumulator, encodeKnownAccumulator, verifyReceiptOfflineAgainstKnownAccumulator, type KnownAccumulator, } from "./known-accumulator.js";
113
+ /**
114
+ * Checkpoint-chain trust root with per-link delegation limits and several
115
+ * ERC-1271 attestations (owner ruling D8, receipt-verify 3.2.0): the one
116
+ * shared copy mcp-verify and forestrie-cli use. The high-level verifier
117
+ * folds, matches and judges expiry; the pieces serve callers that match
118
+ * peaks their own way.
119
+ */
120
+ export { verifyReceiptAgainstCheckpointChain } from "./verify-receipt-against-checkpoint-chain.js";
121
+ export type { VerifyReceiptAgainstCheckpointChainInput } from "./verify-receipt-against-checkpoint-chain.js";
122
+ export type { CheckpointChainReceiptOutcome, CheckpointChainReceiptStage, } from "./checkpoint-chain-receipt-outcome.js";
123
+ export { checkCheckpointLinkExpiry, checkCheckpointLinkSizeBound, makeCheckpointLinkVerifier, type CheckpointLinkVerifier, } from "./checkpoint-link-verifier.js";
124
+ export type { CheckpointLinkFailure, CheckpointLinkRecord, CheckpointLinkVerifiedBy, } from "./checkpoint-link-record.js";
125
+ export { selectKs256Attestation } from "./select-ks256-attestation.js";
126
+ export { summarizeKs256AttestationUsage, type Ks256AttestationUnusedReason, type Ks256AttestationUsage, } from "./ks256-attestation-usage.js";
113
127
  //# sourceMappingURL=index.d.ts.map
@@ -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
@@ -89,3 +89,14 @@ export { univocityLeafHash } from "./leaf-commitment.js";
89
89
  * snapshot over RPC, or proof-path-extend a stale one via massif nodes).
90
90
  */
91
91
  export { assertSnapshotBinding, decodeKnownAccumulator, encodeKnownAccumulator, verifyReceiptOfflineAgainstKnownAccumulator, } from "./known-accumulator.js";
92
+ /**
93
+ * Checkpoint-chain trust root with per-link delegation limits and several
94
+ * ERC-1271 attestations (owner ruling D8, receipt-verify 3.2.0): the one
95
+ * shared copy mcp-verify and forestrie-cli use. The high-level verifier
96
+ * folds, matches and judges expiry; the pieces serve callers that match
97
+ * peaks their own way.
98
+ */
99
+ export { verifyReceiptAgainstCheckpointChain } from "./verify-receipt-against-checkpoint-chain.js";
100
+ export { checkCheckpointLinkExpiry, checkCheckpointLinkSizeBound, makeCheckpointLinkVerifier, } from "./checkpoint-link-verifier.js";
101
+ export { selectKs256Attestation } from "./select-ks256-attestation.js";
102
+ export { summarizeKs256AttestationUsage, } from "./ks256-attestation-usage.js";
@@ -1 +1 @@
1
- {"version":3,"file":"known-accumulator.d.ts","sourceRoot":"","sources":["../src/known-accumulator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAWH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAsCtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,SAAS,EAAE,UAAU,CAAC;IACtB,gEAAgE;IAChE,KAAK,EAAE,UAAU,CAAC;IAClB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,SAAS,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF,yEAAyE;AACzE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,UAAU,CAa7E;AAgBD,+DAA+D;AAC/D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,gBAAgB,CAgC1E;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACnE,IAAI,CAiBN;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,2CAA2C,CAAC,KAAK,EAAE;IACvE,WAAW,EAAE,UAAU,CAAC;IACxB,cAAc,EAAE,UAAU,CAAC;IAC3B,uEAAuE;IACvE,KAAK,EAAE,UAAU,CAAC;IAClB,2DAA2D;IAC3D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAiD/B"}
1
+ {"version":3,"file":"known-accumulator.d.ts","sourceRoot":"","sources":["../src/known-accumulator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAgBH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAsCtE,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,SAAS,EAAE,UAAU,CAAC;IACtB,gEAAgE;IAChE,KAAK,EAAE,UAAU,CAAC;IAClB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,qEAAqE;IACrE,SAAS,EAAE,UAAU,CAAC;CACvB,CAAC;AAEF,yEAAyE;AACzE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,UAAU,CAa7E;AAgBD,+DAA+D;AAC/D,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,UAAU,GAAG,gBAAgB,CAgC1E;AAMD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACnE,IAAI,CAiBN;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,2CAA2C,CAAC,KAAK,EAAE;IACvE,WAAW,EAAE,UAAU,CAAC;IACxB,cAAc,EAAE,UAAU,CAAC;IAC3B,uEAAuE;IACvE,KAAK,EAAE,UAAU,CAAC;IAClB,2DAA2D;IAC3D,WAAW,EAAE,UAAU,EAAE,CAAC;IAC1B,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAkE/B"}
@@ -29,8 +29,9 @@
29
29
  * the operator trust this anchor exists to remove.
30
30
  */
31
31
  import { decodeCborDeterministic, encodeCborDeterministic, } from "@forestrie/encoding";
32
- import { calculateRoot } from "@forestrie/merklelog";
32
+ import { calculateRoot, peakMMRIndexes, } from "@forestrie/merklelog";
33
33
  import { normalizeHexAddress } from "@forestrie/chain-rpc";
34
+ import { inclusionWalkEndIndex } from "./inclusion-walk-end.js";
34
35
  import { parseReceipt } from "./parse-receipt.js";
35
36
  import { univocityLeafHash } from "./leaf-commitment.js";
36
37
  import { SubtleHasher } from "./subtle-hasher.js";
@@ -190,18 +191,33 @@ export async function verifyReceiptOfflineAgainstKnownAccumulator(input) {
190
191
  reason: "receipt_newer_than_known_accumulator",
191
192
  };
192
193
  }
194
+ // The leaf (idtimestamp ‖ inner) and inclusion path ALWAYS decide the
195
+ // peak. An attached payload is only a claim of what that peak is: taken on
196
+ // its own, any receipt attaching a real anchored peak would "verify" for
197
+ // any payload and idtimestamp. Same stage and reason as the main offline
198
+ // path's inclusion check (verifySignatureAndInclusion).
193
199
  let recomputedPeak;
194
- if (parsed.explicitPeak !== null) {
195
- recomputedPeak = parsed.explicitPeak;
196
- }
197
- else {
198
- const hasher = new SubtleHasher();
200
+ try {
199
201
  const leafHash = await univocityLeafHash(input.idtimestampBe8, input.inner);
200
- recomputedPeak = await calculateRoot(hasher, leafHash, parsed.proof, leafIdx);
202
+ recomputedPeak = await calculateRoot(new SubtleHasher(), leafHash, parsed.proof, leafIdx);
203
+ }
204
+ catch {
205
+ return { ok: false, stage: "inclusion", reason: "inclusion_failed" };
206
+ }
207
+ if (parsed.explicitPeak !== null &&
208
+ !bytesEqual(parsed.explicitPeak, recomputedPeak)) {
209
+ return { ok: false, stage: "inclusion", reason: "inclusion_failed" };
201
210
  }
202
211
  // 2. Exact peak match — receipt state is a snapshot-covered accumulator.
203
- for (const peak of input.accumulator) {
204
- if (bytesEqual(recomputedPeak, peak)) {
212
+ // The peak must also sit at the node the inclusion walk reaches (contract
213
+ // order is ascending MMR index, as peakMMRIndexes lists them): a leaf hash
214
+ // does not commit its position, so a value match alone would leave the
215
+ // mmrIndex the freshness check above relies on a caller claim.
216
+ const peakIndex = inclusionWalkEndIndex(leafIdx, parsed.proof.path.length);
217
+ const p = peakMMRIndexes(input.size - 1n).indexOf(peakIndex);
218
+ if (p >= 0) {
219
+ const anchored = input.accumulator[p];
220
+ if (anchored !== undefined && bytesEqual(recomputedPeak, anchored)) {
205
221
  return { ok: true, stage: "binding" };
206
222
  }
207
223
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Accounting for a LIST of ERC-1271 attestations over a checkpoint chain
3
+ * (owner ruling D8): every supplied attestation is reported as used or as
4
+ * unused with a reason, never silently ignored.
5
+ */
6
+ import type { Ks256Attestation } from "./ks256-attestation.js";
7
+ import type { CheckpointLinkFailure, CheckpointLinkRecord } from "./checkpoint-link-record.js";
8
+ /**
9
+ * Why a supplied attestation contributed nothing:
10
+ * - `no_matching_certificate`: no link's certificate has its digest and
11
+ * signature hash.
12
+ * - `not_needed`: it matched a certificate that verified without it
13
+ * (ecrecover, or an ES256 root also vouched).
14
+ * - `duplicate`: another supplied attestation for the same certificate
15
+ * (same digest and signature hash) was used, selected or rejected first,
16
+ * so this one was never tried.
17
+ */
18
+ export type Ks256AttestationUnusedReason = "no_matching_certificate" | "not_needed" | "duplicate";
19
+ /**
20
+ * Which supplied attestations carried which links. An attestation may be
21
+ * in both `used` and `rejected` (used on earlier links, refused on a later
22
+ * one); it is in `unused` only when it is in neither.
23
+ */
24
+ export type Ks256AttestationUsage = {
25
+ used: {
26
+ index: number;
27
+ digestHex: string;
28
+ links: number[];
29
+ }[];
30
+ /** Tried for a link's certificate and failed its own checks there. */
31
+ rejected: {
32
+ index: number;
33
+ digestHex: string;
34
+ links: number[];
35
+ }[];
36
+ unused: {
37
+ index: number;
38
+ digestHex: string;
39
+ reason: Ks256AttestationUnusedReason;
40
+ }[];
41
+ };
42
+ /**
43
+ * Classify each supplied attestation against the per-link records.
44
+ *
45
+ * @param attestations - The list the verifier was given, in order.
46
+ * @param records - Records of the links that verified.
47
+ * @param failure - The link the verifier refused, if any.
48
+ */
49
+ export declare function summarizeKs256AttestationUsage(attestations: readonly Ks256Attestation[], records: readonly CheckpointLinkRecord[], failure?: CheckpointLinkFailure): Ks256AttestationUsage;
50
+ //# sourceMappingURL=ks256-attestation-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ks256-attestation-usage.d.ts","sourceRoot":"","sources":["../src/ks256-attestation-usage.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;GASG;AACH,MAAM,MAAM,4BAA4B,GACpC,yBAAyB,GACzB,YAAY,GACZ,WAAW,CAAC;AAEhB;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAC9D,sEAAsE;IACtE,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAC;IAClE,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,4BAA4B,CAAC;KACtC,EAAE,CAAC;CACL,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,8BAA8B,CAC5C,YAAY,EAAE,SAAS,gBAAgB,EAAE,EACzC,OAAO,EAAE,SAAS,oBAAoB,EAAE,EACxC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,qBAAqB,CAsCvB"}