@forestrie/forestrie-cli 0.9.1 → 0.11.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.
@@ -46,12 +46,13 @@ export declare const HEADER_LABELS: ReadonlyMap<number, LabelInfo>;
46
46
  /** COSE algorithm names (RFC 9053 + forestrie private-use). */
47
47
  export declare const ALG_NAMES: ReadonlyMap<number, string>;
48
48
  /**
49
- * Verifiable data structure ids (draft-ietf-cose-merkle-tree-proofs
50
- * registry). 3 is NOT a registered codepoint: draft-bryce-cose-merkle-
51
- * mountain-range-proofs requests TBD_1 (registration pending), and 3 is
52
- * only the value our test fixtures use — production emitters do not emit
53
- * header 395 at all. Render it as the draft's unregistered codepoint,
54
- * never as registry fact (F7, plan-2607-14 W1.5).
49
+ * Verifiable data structure ids (the COSE receipts registry, RFC 9942).
50
+ * 3 is NOT a registered codepoint: draft-bryce-cose-receipts-mmr-profile
51
+ * requests TBD_1 (registration pending). The Go sealer emits `395: 3` on
52
+ * every checkpoint and peak receipt; the TypeScript exporters do not, so a
53
+ * verifier must not require it. Render it as the draft's unregistered
54
+ * codepoint, never as registry fact. The display strings are the protocol
55
+ * registry's (spec/label-registry.md).
55
56
  */
56
57
  export declare const VDS_NAMES: ReadonlyMap<number, string>;
57
58
  /** CWT claim keys (RFC 8392 §3.1, cnf per RFC 8747). */
@@ -1 +1 @@
1
- {"version":3,"file":"decode-receipt-labels.d.ts","sourceRoot":"","sources":["../../src/lib/decode-receipt-labels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,6CAA6C;AAC7C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,uFAAuF;AACvF,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,8CAA8C;AAC9C,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAC1C,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,SAAS,CAAC;AAC/C;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,SAAS,CAAC;AAExC,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,gDAAgD;AAChD,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAuEvD,CAAC;AAEH,+DAA+D;AAC/D,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAWhD,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAIhD,CAAC;AAEH,wDAAwD;AACxD,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAStD,CAAC;AAEH,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAO3D,CAAC;AAEH,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAGvD,CAAC;AAEH,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE/D"}
1
+ {"version":3,"file":"decode-receipt-labels.d.ts","sourceRoot":"","sources":["../../src/lib/decode-receipt-labels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,6CAA6C;AAC7C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,uFAAuF;AACvF,eAAO,MAAM,SAAS,MAAM,CAAC;AAC7B,8CAA8C;AAC9C,eAAO,MAAM,gBAAgB,KAAK,CAAC;AACnC,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAC1C,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,SAAS,CAAC;AAC/C;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,SAAS,CAAC;AAExC,8CAA8C;AAC9C,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,gDAAgD;AAChD,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAEzC,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,CAuEvD,CAAC;AAEH,+DAA+D;AAC/D,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAWhD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAIhD,CAAC;AAEH,wDAAwD;AACxD,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAStD,CAAC;AAEH,yEAAyE;AACzE,eAAO,MAAM,oBAAoB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAO3D,CAAC;AAEH,mEAAmE;AACnE,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAGvD,CAAC;AAEH,4EAA4E;AAC5E,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAE/D"}
@@ -47,11 +47,11 @@ export const HEADER_LABELS = new Map([
47
47
  [CWT_CLAIMS_LABEL, { name: "CWT claims", note: "RFC 9597" }],
48
48
  [
49
49
  VDS_LABEL,
50
- { name: "verifiable data structure", note: "COSE receipts (draft)" },
50
+ { name: "verifiable data structure", note: "COSE receipts (RFC 9942)" },
51
51
  ],
52
52
  [
53
53
  VERIFIABLE_PROOFS_LABEL,
54
- { name: "verifiable proofs", note: "COSE receipts (draft)" },
54
+ { name: "verifiable proofs", note: "COSE receipts (RFC 9942)" },
55
55
  ],
56
56
  [
57
57
  DELEGATION_CERT_LABEL,
@@ -115,17 +115,18 @@ export const ALG_NAMES = new Map([
115
115
  [-65799, "KS256 (secp256k1 + Keccak-256, forestrie private-use)"],
116
116
  [
117
117
  -65800,
118
- "ES256-WebAuthn (forestrie private-use; delegation proofs and " +
119
- "certificates only, never checkpoint-signing)",
118
+ "ES256-WebAuthn (forestrie private-use; delegation proofs, certificates " +
119
+ "and session-key endorsements, never checkpoint-signing)",
120
120
  ],
121
121
  ]);
122
122
  /**
123
- * Verifiable data structure ids (draft-ietf-cose-merkle-tree-proofs
124
- * registry). 3 is NOT a registered codepoint: draft-bryce-cose-merkle-
125
- * mountain-range-proofs requests TBD_1 (registration pending), and 3 is
126
- * only the value our test fixtures use — production emitters do not emit
127
- * header 395 at all. Render it as the draft's unregistered codepoint,
128
- * never as registry fact (F7, plan-2607-14 W1.5).
123
+ * Verifiable data structure ids (the COSE receipts registry, RFC 9942).
124
+ * 3 is NOT a registered codepoint: draft-bryce-cose-receipts-mmr-profile
125
+ * requests TBD_1 (registration pending). The Go sealer emits `395: 3` on
126
+ * every checkpoint and peak receipt; the TypeScript exporters do not, so a
127
+ * verifier must not require it. Render it as the draft's unregistered
128
+ * codepoint, never as registry fact. The display strings are the protocol
129
+ * registry's (spec/label-registry.md).
129
130
  */
130
131
  export const VDS_NAMES = new Map([
131
132
  [1, "RFC9162_SHA256 (Certificate Transparency)"],
@@ -1 +1 @@
1
- {"version":3,"file":"decode-receipt-labels.js","sourceRoot":"","sources":["../../src/lib/decode-receipt-labels.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,6CAA6C;AAC7C,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AAEjC,iFAAiF;AACjF,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,uFAAuF;AACvF,MAAM,CAAC,MAAM,SAAS,GAAG,GAAG,CAAC;AAC7B,8CAA8C;AAC9C,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AACnC,gFAAgF;AAChF,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,CAAC;AAC1C,oFAAoF;AACpF,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,KAAK,CAAC;AAC/C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAK,CAAC;AAExC,8CAA8C;AAC9C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,CAAC;AACvC,gDAAgD;AAChD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,CAAC;AAQzC,6EAA6E;AAC7E,MAAM,CAAC,MAAM,aAAa,GAAmC,IAAI,GAAG,CAAC;IACnE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACpB,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IACrB,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;IAC7B,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;IACpB,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACnB,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC;IAC3B,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC;IAC5D;QACE,SAAS;QACT,EAAE,IAAI,EAAE,2BAA2B,EAAE,IAAI,EAAE,uBAAuB,EAAE;KACrE;IACD;QACE,uBAAuB;QACvB,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,uBAAuB,EAAE;KAC7D;IACD;QACE,qBAAqB;QACrB;YACE,IAAI,EAAE,wBAAwB;YAC9B,IAAI,EAAE,6DAA6D;SACpE;KACF;IACD,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;IAChE,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;IACvE;QACE,CAAC,KAAK;QACN;YACE,IAAI,EAAE,6BAA6B;YACnC,IAAI,EACF,oEAAoE;gBACpE,8DAA8D;SACjE;KACF;IACD;QACE,CAAC,KAAK;QACN;YACE,IAAI,EAAE,yBAAyB;YAC/B,IAAI,EACF,iEAAiE;gBACjE,0DAA0D;SAC7D;KACF;IACD;QACE,wBAAwB;QACxB;YACE,IAAI,EAAE,0BAA0B;YAChC,IAAI,EAAE,qDAAqD;SAC5D;KACF;IACD;QACE,iBAAiB;QACjB;YACE,IAAI,EAAE,aAAa;YACnB,IAAI,EACF,oEAAoE;gBACpE,mBAAmB;SACtB;KACF;IACD;QACE,CAAC,KAAK;QACN;YACE,IAAI,EAAE,2BAA2B;YACjC,IAAI,EAAE,wDAAwD;SAC/D;KACF;IACD,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;IAC3E,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;IACtE,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;IAC7D,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;IACvE,CAAC,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,uBAAuB,EAAE,CAAC;CACnE,CAAC,CAAC;AAEH,+DAA+D;AAC/D,MAAM,CAAC,MAAM,SAAS,GAAgC,IAAI,GAAG,CAAC;IAC5D,CAAC,CAAC,CAAC,EAAE,+BAA+B,CAAC;IACrC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC;IACb,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC;IACd,CAAC,CAAC,EAAE,EAAE,OAAO,CAAC;IACd,CAAC,CAAC,KAAK,EAAE,uDAAuD,CAAC;IACjE;QACE,CAAC,KAAK;QACN,+DAA+D;YAC7D,8CAA8C;KACjD;CACF,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAgC,IAAI,GAAG,CAAC;IAC5D,CAAC,CAAC,EAAE,2CAA2C,CAAC;IAChD,CAAC,CAAC,EAAE,mBAAmB,CAAC;IACxB,CAAC,CAAC,EAAE,0CAA0C,CAAC;CAChD,CAAC,CAAC;AAEH,wDAAwD;AACxD,MAAM,CAAC,MAAM,eAAe,GAAgC,IAAI,GAAG,CAAC;IAClE,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,oCAAoC,CAAC;CAC1C,CAAC,CAAC;AAEH,yEAAyE;AACzE,MAAM,CAAC,MAAM,oBAAoB,GAAgC,IAAI,GAAG,CAAC;IACvE,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,EAAE,KAAK,CAAC;IACV,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;IACX,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;IACT,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC;CACV,CAAC,CAAC;AAEH,mEAAmE;AACnE,MAAM,CAAC,MAAM,gBAAgB,GAAgC,IAAI,GAAG,CAAC;IACnE,CAAC,oBAAoB,EAAE,kBAAkB,CAAC;IAC1C,CAAC,sBAAsB,EAAE,oBAAoB,CAAC;CAC/C,CAAC,CAAC;AAEH,4EAA4E;AAC5E,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC;AAC1C,CAAC"}
1
+ {"version":3,"file":"decode-receipt-labels.js","sourceRoot":"","sources":["../../src/lib/decode-receipt-labels.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forestrie/forestrie-cli",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "forestrie — participant CLI for forestrie transparency logs (SCITT / COSE receipts)",
5
5
  "keywords": [
6
6
  "forestrie",
@@ -66,12 +66,12 @@
66
66
  "dependencies": {
67
67
  "@forestrie/chain-rpc": "^0.3.0",
68
68
  "@forestrie/cli-kit": "^0.1.1",
69
- "@forestrie/delegation-cose": "^0.3.2",
69
+ "@forestrie/delegation-cose": "0.4.0",
70
70
  "@forestrie/deploy-core": "^0.7.1",
71
- "@forestrie/encoding": "^0.8.0",
71
+ "@forestrie/encoding": "^0.9.1",
72
72
  "@forestrie/grant-builder": "^0.5.0",
73
- "@forestrie/merklelog": "^0.4.0",
74
- "@forestrie/receipt-verify": "^2.0.0",
73
+ "@forestrie/merklelog": "^0.5.0",
74
+ "@forestrie/receipt-verify": "3.1.0",
75
75
  "@forestrie/scrapi-client": "^0.2.2",
76
76
  "citty": "^0.2.2",
77
77
  "viem": "^2.31.0"
package/src/command.ts CHANGED
@@ -45,6 +45,8 @@ export default defineForestrieCommand({
45
45
  import("./commands/verify-grant.js").then((m) => m.default),
46
46
  "fetch-accumulator": () =>
47
47
  import("./commands/fetch-accumulator.js").then((m) => m.default),
48
+ "fetch-ks256-attestation": () =>
49
+ import("./commands/fetch-ks256-attestation.js").then((m) => m.default),
48
50
  "create-consistency-proof": () =>
49
51
  import("./commands/create-consistency-proof.js").then((m) => m.default),
50
52
  },
@@ -0,0 +1,52 @@
1
+ import { defineCommandRunner, defineForestrieCommand } from "../commoncli.js";
2
+ import { runFetchKs256Attestation } from "../main/fetch-ks256-attestation.js";
3
+ import { parseFetchKs256AttestationOptions } from "../options/fetch-ks256-attestation.js";
4
+
5
+ export default defineForestrieCommand({
6
+ meta: {
7
+ name: "fetch-ks256-attestation",
8
+ description:
9
+ "Ask a KS256 contract root (a Safe) over ERC-1271 whether it signs the receipt's delegation certificate, and cache the block-bound answer as a --ks256-attestation artifact (CBOR binding chainId/address/digest/signature hash/block) — verify then accepts the contract-rooted receipt offline as valid under the contract's signers at that block. Auditable: anyone with RPC can re-run the call at that block. An EOA root needs no attestation (verify recovers it)",
10
+ },
11
+ args: {
12
+ receipt: {
13
+ type: "string",
14
+ description: "Receipt carrying the KS256 delegation certificate (label 1000)",
15
+ valueHint: "path",
16
+ required: true,
17
+ },
18
+ root: {
19
+ type: "string",
20
+ description:
21
+ "Contract root address (0x + 40 hex); overrides the genesis root",
22
+ valueHint: "address",
23
+ },
24
+ genesis: {
25
+ type: "string",
26
+ description:
27
+ "Forest genesis (genesis.cbor): supplies the KS256 root unless --root is given, and the chain id the RPC must match",
28
+ valueHint: "path",
29
+ },
30
+ "rpc-url": {
31
+ type: "string",
32
+ description: "JSON-RPC endpoint (default: ${env} → RPC_URL)",
33
+ valueHint: "url",
34
+ },
35
+ block: {
36
+ type: "string",
37
+ description:
38
+ "Block number to pin the calls to, or 'latest' (default) — needs an archive node for old blocks",
39
+ valueHint: "number|latest",
40
+ },
41
+ out: {
42
+ type: "string",
43
+ description: "Output path for the attestation CBOR",
44
+ valueHint: "path",
45
+ required: true,
46
+ },
47
+ },
48
+ run: defineCommandRunner(
49
+ parseFetchKs256AttestationOptions,
50
+ runFetchKs256Attestation,
51
+ ),
52
+ });
@@ -18,8 +18,14 @@ export default defineForestrieCommand({
18
18
  "known-log-key": {
19
19
  type: "string",
20
20
  description:
21
- "Caller-known log OWNER key (the delegation issuer), base64 x||y (64 bytes) (env KNOWN_LOG_KEY). Offline signer-provenance anchor that replaces --genesis; asserts (does not prove) the key-to-log binding — --genesis derives it from the grant chain, while the accumulator anchors (--known-accumulator / --rpc-url) supply split-view (freshness is a weaker, separate axis bearing only on coverage). See https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md",
22
- valueHint: "base64",
21
+ "Caller-known log OWNER key (the delegation issuer): base64 x||y (64-byte ES256 key) or a 20-byte KS256 address (0x + 40 hex, or base64) (env KNOWN_LOG_KEY). Offline signer-provenance anchor that replaces --genesis (give --genesis too and it only supplies the forest chain id an attestation must match); asserts (does not prove) the key-to-log binding — --genesis roots it in the forest genesis document's bootstrap key (the root log, or a delegation directly under it), while the accumulator roots (--known-accumulator / --rpc-url) supply split-view (freshness is a weaker, separate axis bearing only on coverage). See https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md",
22
+ valueHint: "base64|0xaddress",
23
+ },
24
+ "ks256-attestation": {
25
+ type: "string",
26
+ description:
27
+ "ERC-1271 attestation (CBOR from `forestrie fetch-ks256-attestation`) for a KS256 contract root such as a Safe: the delegation certificate then verifies offline as valid under the contract's signers at the attested block, not as a signature by one key. Needs the forest chain id from --genesis. verify never calls the chain",
28
+ valueHint: "path",
23
29
  },
24
30
  receipt: {
25
31
  type: "string",
@@ -18,8 +18,14 @@ export default defineForestrieCommand({
18
18
  "known-log-key": {
19
19
  type: "string",
20
20
  description:
21
- "Caller-known log OWNER key (the delegation issuer), base64 x||y (64 bytes) (env KNOWN_LOG_KEY). Offline signer-provenance anchor that replaces --genesis; asserts (does not prove) the key-to-log binding — --genesis derives it from the grant chain, while the accumulator anchors (--known-accumulator / --rpc-url) supply split-view (freshness is a weaker, separate axis bearing only on coverage). See https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md",
22
- valueHint: "base64",
21
+ "Caller-known log OWNER key (the delegation issuer): base64 x||y (64-byte ES256 key) or a 20-byte KS256 address (0x + 40 hex, or base64) (env KNOWN_LOG_KEY). Offline signer-provenance anchor that replaces --genesis (give --genesis too and it only supplies the forest chain id an attestation must match); asserts (does not prove) the key-to-log binding — --genesis roots it in the forest genesis document's bootstrap key (the root log, or a delegation directly under it), while the accumulator roots (--known-accumulator / --rpc-url) supply split-view (freshness is a weaker, separate axis bearing only on coverage). See https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md",
22
+ valueHint: "base64|0xaddress",
23
+ },
24
+ "ks256-attestation": {
25
+ type: "string",
26
+ description:
27
+ "ERC-1271 attestation (CBOR from `forestrie fetch-ks256-attestation`) for a KS256 contract root such as a Safe: the delegation certificate then verifies offline as valid under the contract's signers at the attested block, not as a signature by one key. Needs the forest chain id from --genesis. verify never calls the chain",
28
+ valueHint: "path",
23
29
  },
24
30
  receipt: {
25
31
  type: "string",
@@ -31,6 +31,7 @@ import {
31
31
  computeCheckpointAccumulator,
32
32
  checkpointConsistencyProof,
33
33
  type CheckpointConsistencyProof,
34
+ type DecodedConsistencyProof,
34
35
  } from "@forestrie/receipt-verify";
35
36
  import { fetchPublishedCheckpoints } from "./verify-eventscan.js";
36
37
  import {
@@ -56,21 +57,38 @@ export type CheckpointSeal =
56
57
  | { kind: "sth"; checkpointBytes: Uint8Array };
57
58
 
58
59
  /**
59
- * Fold-relevant fields of a consistency proof, shared by calldata-decoded
60
- * proofs (`CalldataConsistencyProof` — no per-proof signed size; on-chain
61
- * calldata carries no protected header) and sth-decoded
62
- * `CheckpointConsistencyProof` (which adds `signedTreeSize2`, cross-checked
63
- * against the checkpoint's protected header at decode time). Neither
64
- * `computeCheckpointAccumulator` nor `freshenReceipt` reads `signedTreeSize2`
65
- * — only the trusted base SIZE (passed separately, never off the proof) and
66
- * `treeSize1`/`treeSize2`/`paths`/`rightPeaks` matter to the fold — so this
67
- * provider carries the narrower shape and backfills `signedTreeSize2` with
68
- * `treeSize2` only where receipt-verify's stricter parameter type demands it.
60
+ * One fold step: a single consistency proof, shared by calldata-decoded
61
+ * proofs (`CalldataConsistencyProof` — on-chain calldata carries no protected
62
+ * header, so no signed size) and `.sth`-decoded proofs. receipt-verify
63
+ * decodes an `.sth` to a `CheckpointConsistencyProof` CHAIN (`proofs[]`, one
64
+ * or more relayed steps under the head signature, ADR-0066 D2); this provider
65
+ * keeps one link per step, so a link's proof is the single-step
66
+ * `DecodedConsistencyProof`, and {@link singleProofChain} wraps it back into
67
+ * the chain shape where receipt-verify's parameter type demands it. The fold
68
+ * never reads `signedTreeSize2` — only the trusted base SIZE (passed
69
+ * separately, never off the proof) and `treeSize1`/`treeSize2`/`paths`/
70
+ * `rightPeaks` matter — so it is backfilled with `treeSize2`.
69
71
  */
70
- export type FoldableConsistencyProof = Pick<
71
- CheckpointConsistencyProof,
72
- "treeSize1" | "treeSize2" | "paths" | "rightPeaks"
73
- >;
72
+ export type FoldableConsistencyProof = DecodedConsistencyProof;
73
+
74
+ /** Wrap one fold step as a one-proof chain for receipt-verify's chain-typed
75
+ * parameters. `signedTreeSize2` is the checkpoint's SIGNED size when the
76
+ * step is the last one a sealed `.sth` relays (so `freshenReceipt`'s
77
+ * signed-vs-declared guard stays live for it), and is backfilled from
78
+ * `treeSize2` for steps that carry no signature of their own: calldata
79
+ * proofs, and the earlier steps a checkpoint relays (see
80
+ * {@link FoldableConsistencyProof}). */
81
+ export function singleProofChain(
82
+ p: FoldableConsistencyProof,
83
+ signedTreeSize2: bigint = p.treeSize2,
84
+ ): CheckpointConsistencyProof {
85
+ return {
86
+ proofs: [p],
87
+ treeSize1: p.treeSize1,
88
+ treeSize2: p.treeSize2,
89
+ signedTreeSize2,
90
+ };
91
+ }
74
92
 
75
93
  /** One folded link: the accumulator committed at `treeSize2`. */
76
94
  export type CheckpointLink = {
@@ -82,6 +100,10 @@ export type CheckpointLink = {
82
100
  * (the tile-free climb material). Retained so a path-extending consumer
83
101
  * (freshen) gets it without a re-decode; folding consumers ignore it. */
84
102
  proof: FoldableConsistencyProof;
103
+ /** The SIGNED tree-size-2 covering this link, when it is the last step of a
104
+ * sealed `.sth` (cross-checked against `proof.treeSize2` at decode). Absent
105
+ * for calldata links and for the earlier steps a checkpoint relays. */
106
+ signedTreeSize2?: bigint;
85
107
  /** The signature material to verify this link's accumulator, when directly
86
108
  * signed (see {@link CheckpointSeal}). The provider does NOT verify it. */
87
109
  seal?: CheckpointSeal;
@@ -103,7 +125,9 @@ export async function foldProofChain(
103
125
  accumulatorFrom?: Uint8Array[];
104
126
  accumulatorFromSize?: bigint;
105
127
  seals?: readonly (CheckpointSeal | undefined)[];
106
- sourceRefs?: readonly string[];
128
+ sourceRefs?: readonly (string | undefined)[];
129
+ /** Per-step signed tree-size-2, for the steps that have one. */
130
+ signedSizes?: readonly (bigint | undefined)[];
107
131
  } = {},
108
132
  ): Promise<CheckpointLink[]> {
109
133
  let accumulator = opts.accumulatorFrom ?? [];
@@ -126,13 +150,13 @@ export async function foldProofChain(
126
150
  `checkpoint chain is not contiguous at link ${i}: base ${p.treeSize1} != expected ${expectedBase}`,
127
151
  );
128
152
  }
129
- // receipt-verify 2.0.0: the trusted base size is a parameter, never read
130
- // off the proof (ADR-0066 D5.4) — `expectedBase` is exactly that, already
131
- // checked against `p.treeSize1` above. `signedTreeSize2` is backfilled
132
- // from `treeSize2` only to satisfy the parameter type; the fold does not
133
- // read it (see `FoldableConsistencyProof`'s doc comment above).
153
+ // The trusted base size is a parameter, never read off the proof
154
+ // (ADR-0066 D5.4) — `expectedBase` is exactly that, already checked
155
+ // against `p.treeSize1` above. One link is one step, folded as a
156
+ // one-proof chain (see `singleProofChain`).
157
+ const signed = opts.signedSizes?.[i];
134
158
  accumulator = await computeCheckpointAccumulator(
135
- { ...p, signedTreeSize2: p.treeSize2 },
159
+ singleProofChain(p, signed),
136
160
  accumulator,
137
161
  expectedBase,
138
162
  );
@@ -142,6 +166,7 @@ export async function foldProofChain(
142
166
  accumulator,
143
167
  proof: p,
144
168
  };
169
+ if (signed !== undefined) link.signedTreeSize2 = signed;
145
170
  const seal = opts.seals?.[i];
146
171
  if (seal !== undefined) link.seal = seal;
147
172
  const ref = opts.sourceRefs?.[i];
@@ -153,9 +178,17 @@ export async function foldProofChain(
153
178
  }
154
179
 
155
180
  /**
156
- * Retained-`.sth` provider: decode each checkpoint's embedded consistency proof
157
- * and fold. `checkpoints` are the raw `.sth` bytes in ascending massif order;
158
- * `sourceRefs` optionally names them (e.g. filenames) for narration.
181
+ * Retained-`.sth` provider: decode each checkpoint's embedded consistency
182
+ * proofs and fold. `checkpoints` are the raw `.sth` bytes in ascending massif
183
+ * order; `sourceRefs` optionally names them (e.g. filenames) for narration.
184
+ * A checkpoint may relay several proofs under one signature (ADR-0066 D2);
185
+ * each becomes its own link, and only the LAST link of a checkpoint carries
186
+ * that checkpoint's seal, since the signature covers only the head size.
187
+ * A log's initialising proof `[0, n, [], peaks]` (empty consistency-paths:
188
+ * the empty tree has no peaks) is a valid first step only; `foldProofChain`
189
+ * binds each step's tree-size-1 to the size reached, so it verifies only
190
+ * against the empty tree. A checkpoint with no proof at all is malformed and
191
+ * the decode throws.
159
192
  */
160
193
  export async function sthCheckpointChain(
161
194
  checkpoints: readonly Uint8Array[],
@@ -165,20 +198,30 @@ export async function sthCheckpointChain(
165
198
  sourceRefs?: readonly string[];
166
199
  } = {},
167
200
  ): Promise<CheckpointLink[]> {
168
- const proofs = checkpoints.map((bytes) => checkpointConsistencyProof(bytes));
169
- const seals: CheckpointSeal[] = checkpoints.map((bytes) => ({
170
- kind: "sth",
171
- checkpointBytes: bytes,
172
- }));
201
+ const proofs: FoldableConsistencyProof[] = [];
202
+ const seals: (CheckpointSeal | undefined)[] = [];
203
+ const sourceRefs: (string | undefined)[] = [];
204
+ const signedSizes: (bigint | undefined)[] = [];
205
+ checkpoints.forEach((bytes, i) => {
206
+ const chain = checkpointConsistencyProof(bytes);
207
+ chain.proofs.forEach((step, j) => {
208
+ const last = j === chain.proofs.length - 1;
209
+ proofs.push(step);
210
+ seals.push(last ? { kind: "sth", checkpointBytes: bytes } : undefined);
211
+ signedSizes.push(last ? chain.signedTreeSize2 : undefined);
212
+ sourceRefs.push(opts.sourceRefs?.[i]);
213
+ });
214
+ });
173
215
  return foldProofChain(proofs, {
174
216
  seals,
217
+ signedSizes,
175
218
  ...(opts.accumulatorFrom !== undefined
176
219
  ? { accumulatorFrom: opts.accumulatorFrom }
177
220
  : {}),
178
221
  ...(opts.accumulatorFromSize !== undefined
179
222
  ? { accumulatorFromSize: opts.accumulatorFromSize }
180
223
  : {}),
181
- ...(opts.sourceRefs !== undefined ? { sourceRefs: opts.sourceRefs } : {}),
224
+ ...(opts.sourceRefs !== undefined ? { sourceRefs } : {}),
182
225
  });
183
226
  }
184
227
 
@@ -38,6 +38,7 @@ export type CompleteGrantReason =
38
38
  | "checkpoint_parse_failed"
39
39
  | "checkpoint_missing_peak_receipts"
40
40
  | "checkpoint_missing_sealed_size"
41
+ | "checkpoint_consistency_invalid"
41
42
  | "checkpoint_does_not_cover_leaf"
42
43
  | "leaf_not_in_massif"
43
44
  | "derive_failed";
@@ -9,6 +9,8 @@
9
9
  * the log exists — no operator onboard token, authority flows down the log
10
10
  * hierarchy (ADR-0053). Node/Bun HTTP only.
11
11
  */
12
+ import { responseProblemDetail } from "./http-problem.js";
13
+
12
14
 
13
15
  /** Prepare (child public-root pre-registration) failure. */
14
16
  export class PrepareLogError extends Error {
@@ -48,7 +50,7 @@ export async function prepareChildLog(
48
50
  headers: { Authorization: `Forestrie-Grant ${params.grantBase64}` },
49
51
  });
50
52
  if (!res.ok) {
51
- const preview = (await res.text().catch(() => "")).slice(0, 300);
53
+ const preview = await responseProblemDetail(res, 300);
52
54
  throw new PrepareLogError(
53
55
  `prepare failed: HTTP ${res.status} ${preview}`,
54
56
  res.status,
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  buildReceiptOffline,
3
+ checkpointConsistencyProof,
3
4
  computeAccumulatorPeak,
4
5
  openMassifNodeStore,
5
6
  parseCheckpoint,
@@ -27,6 +28,7 @@ export type CreateReceiptReason =
27
28
  | "checkpoint_parse_failed"
28
29
  | "checkpoint_missing_peak_receipts"
29
30
  | "checkpoint_missing_sealed_size"
31
+ | "checkpoint_consistency_invalid"
30
32
  | "checkpoint_does_not_cover_leaf"
31
33
  | "leaf_not_in_massif"
32
34
  | "derive_failed";
@@ -123,6 +125,20 @@ export async function deriveCheckpointReceipt(input: {
123
125
  "checkpoint carries no consistency proof (cannot determine sealed size)",
124
126
  );
125
127
  }
128
+ // The embedded consistency proofs must agree with the signed size: the
129
+ // last proof's tree-size-2 is the size the signature covers (ADR-0066 D1,
130
+ // D2). A checkpoint whose proofs contradict its signed size is malformed,
131
+ // and this builder refuses it rather than deriving from the signed half
132
+ // alone. (parseCheckpoint only requires that a proof be present.)
133
+ try {
134
+ checkpointConsistencyProof(checkpointBytes);
135
+ } catch (err) {
136
+ fail(
137
+ "parse",
138
+ "checkpoint_consistency_invalid",
139
+ `checkpoint consistency proofs do not agree with its signed size: ${errorMessage(err)}`,
140
+ );
141
+ }
126
142
 
127
143
  let store: MassifNodeStore;
128
144
  try {
@@ -57,11 +57,11 @@ export const HEADER_LABELS: ReadonlyMap<number, LabelInfo> = new Map([
57
57
  [CWT_CLAIMS_LABEL, { name: "CWT claims", note: "RFC 9597" }],
58
58
  [
59
59
  VDS_LABEL,
60
- { name: "verifiable data structure", note: "COSE receipts (draft)" },
60
+ { name: "verifiable data structure", note: "COSE receipts (RFC 9942)" },
61
61
  ],
62
62
  [
63
63
  VERIFIABLE_PROOFS_LABEL,
64
- { name: "verifiable proofs", note: "COSE receipts (draft)" },
64
+ { name: "verifiable proofs", note: "COSE receipts (RFC 9942)" },
65
65
  ],
66
66
  [
67
67
  DELEGATION_CERT_LABEL,
@@ -129,18 +129,19 @@ export const ALG_NAMES: ReadonlyMap<number, string> = new Map([
129
129
  [-65799, "KS256 (secp256k1 + Keccak-256, forestrie private-use)"],
130
130
  [
131
131
  -65800,
132
- "ES256-WebAuthn (forestrie private-use; delegation proofs and " +
133
- "certificates only, never checkpoint-signing)",
132
+ "ES256-WebAuthn (forestrie private-use; delegation proofs, certificates " +
133
+ "and session-key endorsements, never checkpoint-signing)",
134
134
  ],
135
135
  ]);
136
136
 
137
137
  /**
138
- * Verifiable data structure ids (draft-ietf-cose-merkle-tree-proofs
139
- * registry). 3 is NOT a registered codepoint: draft-bryce-cose-merkle-
140
- * mountain-range-proofs requests TBD_1 (registration pending), and 3 is
141
- * only the value our test fixtures use — production emitters do not emit
142
- * header 395 at all. Render it as the draft's unregistered codepoint,
143
- * never as registry fact (F7, plan-2607-14 W1.5).
138
+ * Verifiable data structure ids (the COSE receipts registry, RFC 9942).
139
+ * 3 is NOT a registered codepoint: draft-bryce-cose-receipts-mmr-profile
140
+ * requests TBD_1 (registration pending). The Go sealer emits `395: 3` on
141
+ * every checkpoint and peak receipt; the TypeScript exporters do not, so a
142
+ * verifier must not require it. Render it as the draft's unregistered
143
+ * codepoint, never as registry fact. The display strings are the protocol
144
+ * registry's (spec/label-registry.md).
144
145
  */
145
146
  export const VDS_NAMES: ReadonlyMap<number, string> = new Map([
146
147
  [1, "RFC9162_SHA256 (Certificate Transparency)"],
@@ -33,6 +33,8 @@ import {
33
33
  verifyDelegateKeyVoucher,
34
34
  } from "./delegate-voucher.js";
35
35
 
36
+ import { responseProblemDetail } from "./http-problem.js";
37
+
36
38
  /** Fixed inclusive MMR start for a horizon delegation lease. */
37
39
  const MMR_START = 0;
38
40
 
@@ -170,7 +172,7 @@ export async function runDelegateFlow(
170
172
  const pendingUrl = `${params.coordinatorUrl}/api/logs/${params.logId}/pending-delegation`;
171
173
  const pendingRes = await fetchImpl(pendingUrl);
172
174
  if (!pendingRes.ok) {
173
- const preview = (await pendingRes.text()).slice(0, 200);
175
+ const preview = await responseProblemDetail(pendingRes, 200);
174
176
  throw new DelegateFlowError(
175
177
  `pending-delegation fetch failed: HTTP ${pendingRes.status} ${preview}`,
176
178
  pendingRes.status,
@@ -270,7 +272,7 @@ export async function runDelegateFlow(
270
272
  }),
271
273
  });
272
274
  if (!submitRes.ok) {
273
- const preview = (await submitRes.text()).slice(0, 200);
275
+ const preview = await responseProblemDetail(submitRes, 200);
274
276
  throw new DelegateFlowError(
275
277
  `certificate submit failed: HTTP ${submitRes.status} ${preview}`,
276
278
  submitRes.status,
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The human-readable reason a canopy / coordinator request was rejected
3
+ * (FOR-579).
4
+ *
5
+ * canopy-api answers with RFC 9457 problem details encoded as CBOR (served
6
+ * as `application/cbor` on most routes, `application/problem+cbor` on the
7
+ * SCRAPI ones); the delegation coordinator uses `application/problem+json`.
8
+ * Reading such a body with `response.text()` and printing it yields
9
+ * unreadable bytes, which is how a `400` from a strict CBOR decoder hid its
10
+ * cause (`Invalid CBOR body`) through four qualification runs on
11
+ * 2026-09-20. This decodes whichever form arrived and falls back to the
12
+ * text body. It never throws.
13
+ */
14
+ import { decodeProblemDetailsBytes } from "@forestrie/scrapi-client";
15
+
16
+ /** Longest string returned; problem bodies are short, this is a guard. */
17
+ export const MAX_PROBLEM_DETAIL = 512;
18
+
19
+ function describe(problem: {
20
+ title?: string;
21
+ detail?: string;
22
+ }): string | undefined {
23
+ const { title, detail } = problem;
24
+ if (detail && title) return `${title}: ${detail}`;
25
+ return detail ?? title;
26
+ }
27
+
28
+ export async function responseProblemDetail(
29
+ response: Response,
30
+ max = MAX_PROBLEM_DETAIL,
31
+ ): Promise<string> {
32
+ const contentType = (response.headers.get("content-type") ?? "").toLowerCase();
33
+ let text = "";
34
+ try {
35
+ const bytes = new Uint8Array(await response.arrayBuffer());
36
+ if (bytes.length === 0) return "";
37
+ if (contentType.includes("cbor")) {
38
+ // No content type passed: canopy-api serves most problem bodies as
39
+ // plain application/cbor, so decode opportunistically.
40
+ const problem = decodeProblemDetailsBytes(bytes);
41
+ const described = problem && describe(problem);
42
+ if (described) return described.slice(0, max);
43
+ }
44
+ text = new TextDecoder().decode(bytes);
45
+ if (contentType.includes("json")) {
46
+ const described = describe(JSON.parse(text) as { title?: string; detail?: string });
47
+ if (described) return described.slice(0, max);
48
+ }
49
+ } catch {
50
+ // fall through to whatever text we have
51
+ }
52
+ return text.slice(0, max);
53
+ }
@@ -0,0 +1,35 @@
1
+ import {
2
+ knownLogKeyFromBytes,
3
+ type RootVerifyKey,
4
+ } from "@forestrie/receipt-verify";
5
+
6
+ /** The accepted `--known-log-key` forms, for error messages and help. */
7
+ export const KNOWN_LOG_KEY_FORMS =
8
+ "base64 x||y (64-byte ES256 public key) or a 20-byte KS256 address (0x + 40 hex, or base64)";
9
+
10
+ /**
11
+ * Parse the caller-known log OWNER key (`--known-log-key`). Forms:
12
+ *
13
+ * - base64 of 64 bytes: an ES256 public key, x||y;
14
+ * - `0x` + 40 hex, or base64 of 20 bytes: a KS256 (Ethereum) address.
15
+ *
16
+ * Length alone decides the algorithm (via `knownLogKeyFromBytes`, the same
17
+ * rule univocity applies). Either way the caller ASSERTS the key owns the log.
18
+ */
19
+ export async function parseKnownLogKey(value: string): Promise<RootVerifyKey> {
20
+ const v = value.trim();
21
+ const bytes = /^0x[0-9a-fA-F]{40}$/.test(v)
22
+ ? new Uint8Array(Buffer.from(v.slice(2), "hex"))
23
+ : new Uint8Array(Buffer.from(v, "base64"));
24
+ if (bytes.length !== 64 && bytes.length !== 20) {
25
+ throw new Error(
26
+ `--known-log-key must be ${KNOWN_LOG_KEY_FORMS}; decoded ${bytes.length} bytes`,
27
+ );
28
+ }
29
+ return knownLogKeyFromBytes(bytes);
30
+ }
31
+
32
+ /** True for a KS256 root (a `ParsedKs256RootKey`, not a WebCrypto key). */
33
+ export function isKs256Root(key: RootVerifyKey): boolean {
34
+ return !(key instanceof CryptoKey);
35
+ }