@forestrie/forestrie-cli 0.8.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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +425 -0
  3. package/TRUST-MODEL.md +8 -0
  4. package/dist/cli.js +33811 -0
  5. package/dist/decode-receipt.d.ts +19 -0
  6. package/dist/decode-receipt.d.ts.map +1 -0
  7. package/dist/decode-receipt.js +19 -0
  8. package/dist/decode-receipt.js.map +1 -0
  9. package/dist/index.d.ts +11 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +11 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/decode-receipt-cbor.d.ts +26 -0
  14. package/dist/lib/decode-receipt-cbor.d.ts.map +1 -0
  15. package/dist/lib/decode-receipt-cbor.js +182 -0
  16. package/dist/lib/decode-receipt-cbor.js.map +1 -0
  17. package/dist/lib/decode-receipt-decode.d.ts +103 -0
  18. package/dist/lib/decode-receipt-decode.d.ts.map +1 -0
  19. package/dist/lib/decode-receipt-decode.js +237 -0
  20. package/dist/lib/decode-receipt-decode.js.map +1 -0
  21. package/dist/lib/decode-receipt-labels.d.ts +54 -0
  22. package/dist/lib/decode-receipt-labels.d.ts.map +1 -0
  23. package/dist/lib/decode-receipt-labels.js +117 -0
  24. package/dist/lib/decode-receipt-labels.js.map +1 -0
  25. package/dist/lib/decode-receipt-render.d.ts +11 -0
  26. package/dist/lib/decode-receipt-render.d.ts.map +1 -0
  27. package/dist/lib/decode-receipt-render.js +242 -0
  28. package/dist/lib/decode-receipt-render.js.map +1 -0
  29. package/package.json +83 -0
  30. package/src/cli.ts +5 -0
  31. package/src/command.ts +51 -0
  32. package/src/commands/admin-onboard-token.ts +51 -0
  33. package/src/commands/admin.ts +20 -0
  34. package/src/commands/complete-grant.ts +41 -0
  35. package/src/commands/create-consistency-proof.ts +44 -0
  36. package/src/commands/create-log.ts +95 -0
  37. package/src/commands/decode-receipt.ts +20 -0
  38. package/src/commands/delegate.ts +55 -0
  39. package/src/commands/deploy.ts +72 -0
  40. package/src/commands/fetch-accumulator.ts +49 -0
  41. package/src/commands/onboard-genesis.ts +70 -0
  42. package/src/commands/onboard-request.ts +59 -0
  43. package/src/commands/register-grant.ts +74 -0
  44. package/src/commands/register.ts +54 -0
  45. package/src/commands/resolve-receipt.ts +101 -0
  46. package/src/commands/sign-statement.ts +53 -0
  47. package/src/commands/verify-grant.ts +89 -0
  48. package/src/commands/verify.ts +92 -0
  49. package/src/commoncli.ts +34 -0
  50. package/src/decode-receipt.ts +56 -0
  51. package/src/index.ts +10 -0
  52. package/src/lib/bytes.ts +7 -0
  53. package/src/lib/checkpoint-provider.ts +297 -0
  54. package/src/lib/complete-grant-derive.ts +237 -0
  55. package/src/lib/consistency-proof.ts +271 -0
  56. package/src/lib/create-log-build.ts +168 -0
  57. package/src/lib/create-log-prepare.ts +75 -0
  58. package/src/lib/create-receipt-chain-verify.ts +267 -0
  59. package/src/lib/create-receipt-derive.ts +208 -0
  60. package/src/lib/create-receipt-inputs.ts +79 -0
  61. package/src/lib/decode-checkpoint-calldata.ts +188 -0
  62. package/src/lib/decode-receipt-cbor.ts +202 -0
  63. package/src/lib/decode-receipt-decode.ts +333 -0
  64. package/src/lib/decode-receipt-labels.ts +132 -0
  65. package/src/lib/decode-receipt-render.ts +284 -0
  66. package/src/lib/delegate-flow.ts +289 -0
  67. package/src/lib/delegate-voucher.ts +122 -0
  68. package/src/lib/deploy-artifact.ts +79 -0
  69. package/src/lib/deploy-bootstrap.ts +100 -0
  70. package/src/lib/deploy-flow.ts +184 -0
  71. package/src/lib/deploy-genesis-log-id.ts +29 -0
  72. package/src/lib/deploy-rpc.ts +40 -0
  73. package/src/lib/deployment-record.ts +65 -0
  74. package/src/lib/fsio.ts +91 -0
  75. package/src/lib/grant-flags-local.ts +19 -0
  76. package/src/lib/onboard-attestation.ts +120 -0
  77. package/src/lib/onboard-genesis-body.ts +94 -0
  78. package/src/lib/openssl-error-queue.ts +56 -0
  79. package/src/lib/register-flow.ts +292 -0
  80. package/src/lib/register-grant-build.ts +173 -0
  81. package/src/lib/register-grant-complete.ts +47 -0
  82. package/src/lib/register-grant-flow.ts +258 -0
  83. package/src/lib/resolve-receipt-freshen.ts +221 -0
  84. package/src/lib/sign-statement-build.ts +117 -0
  85. package/src/lib/sign-statement-key.ts +145 -0
  86. package/src/lib/verify-anchored.ts +326 -0
  87. package/src/lib/verify-checkpoint-chain.ts +160 -0
  88. package/src/lib/verify-eventscan.ts +214 -0
  89. package/src/lib/verify-inputs.ts +143 -0
  90. package/src/lib/verify-known-accumulator.ts +161 -0
  91. package/src/lib/verify-report.ts +265 -0
  92. package/src/lib/x402-payment.ts +115 -0
  93. package/src/main/admin-onboard-token.ts +216 -0
  94. package/src/main/complete-grant.ts +265 -0
  95. package/src/main/create-consistency-proof.ts +83 -0
  96. package/src/main/create-log.ts +347 -0
  97. package/src/main/create-receipt.ts +763 -0
  98. package/src/main/decode-receipt.ts +72 -0
  99. package/src/main/delegate.ts +141 -0
  100. package/src/main/deploy.ts +273 -0
  101. package/src/main/fetch-accumulator.ts +181 -0
  102. package/src/main/not-implemented.ts +43 -0
  103. package/src/main/onboard-genesis.ts +192 -0
  104. package/src/main/onboard-request.ts +333 -0
  105. package/src/main/register-grant.ts +270 -0
  106. package/src/main/register.ts +243 -0
  107. package/src/main/sign-statement.ts +168 -0
  108. package/src/main/verify.ts +767 -0
  109. package/src/options/admin-onboard-token.ts +48 -0
  110. package/src/options/common.ts +69 -0
  111. package/src/options/complete-grant.ts +40 -0
  112. package/src/options/create-consistency-proof.ts +63 -0
  113. package/src/options/create-log.ts +99 -0
  114. package/src/options/create-receipt.ts +243 -0
  115. package/src/options/decode-receipt.ts +25 -0
  116. package/src/options/delegate.ts +93 -0
  117. package/src/options/deploy.ts +105 -0
  118. package/src/options/fetch-accumulator.ts +62 -0
  119. package/src/options/onboard-genesis.ts +74 -0
  120. package/src/options/onboard-request.ts +57 -0
  121. package/src/options/register-grant.ts +72 -0
  122. package/src/options/register.ts +71 -0
  123. package/src/options/sign-statement.ts +64 -0
  124. package/src/options/verify.ts +250 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Robin Bryce
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,425 @@
1
+ # forestrie-cli
2
+
3
+ `forestrie` — the **participant CLI** for forestrie transparency logs
4
+ (SCITT / COSE receipts). Ships two ways — an npm package that runs on Node
5
+ (`npx @forestrie/forestrie-cli`) and a dependency-free static binary on
6
+ every GitHub release — both hosting the subcommands a participant needs to
7
+ deploy, sign, register, and verify against a forestrie log:
8
+
9
+ | Subcommand | What it does |
10
+ |---|---|
11
+ | `deploy` | Deploy a univocity instance (ES256 bootstrap is the paved path) |
12
+ | `sign-statement` | Produce a SCITT signed statement (plain COSE Sign1 with CWT claims) |
13
+ | `register` | Register a signed statement via SCRAPI, download the receipt |
14
+ | `register-grant` | Authorize a signer for a child/data log (one grant per signer) |
15
+ | `complete-grant` | Self-create the `Forestrie-Grant` header from a checkpoint |
16
+ | `resolve-receipt` | Produce or freshen a COSE receipt — from tiles, or tile-free from a `.sth`/calldata chain (alias: `create-receipt`) |
17
+ | `decode-receipt` | Decode a COSE receipt — it is just COSE: Sign1 + MMR inclusion |
18
+ | `verify` | Verify a receipt offline — the same closer for every other subcommand |
19
+ | `fetch-accumulator` | Cache the on-chain accumulator as a `--known-accumulator` snapshot |
20
+
21
+ **Status:** subcommands not yet implemented declare their real argument
22
+ surface and parse it, then exit non-zero with a structured `not_implemented`
23
+ error. `--json` emits that report as JSON on stdout. Implementations land
24
+ per-subcommand.
25
+
26
+ ## Install from npm
27
+
28
+ ```bash
29
+ npx @forestrie/forestrie-cli --help # no install
30
+ npm install -g @forestrie/forestrie-cli # then: forestrie --help
31
+ forestrie verify --help
32
+ ```
33
+
34
+ **Runtime:** the npm package is a **Node** program — Node **>= 20.10** and
35
+ nothing else. Bun is the development and static-binary toolchain, not a
36
+ runtime requirement: nothing under `src/` uses a Bun-only API, and
37
+ `dist/cli.js` is built with `--target node`. If you would rather have no
38
+ runtime at all, take the static binary below.
39
+
40
+ ## Install from a release
41
+
42
+ Every [GitHub release](https://github.com/forestrie/forestrie-cli/releases)
43
+ attaches prebuilt static binaries — `forestrie-darwin-arm64` and
44
+ `forestrie-linux-x64` — each with a `.sha256` sidecar. No runtime is
45
+ needed; just download, verify, and run:
46
+
47
+ ```bash
48
+ target=darwin-arm64 # or: linux-x64
49
+ base=https://github.com/forestrie/forestrie-cli/releases/latest/download
50
+ curl -fsSLO "${base}/forestrie-${target}"
51
+ curl -fsSLO "${base}/forestrie-${target}.sha256"
52
+ shasum -a 256 -c "forestrie-${target}.sha256"
53
+ chmod +x "forestrie-${target}"
54
+ ./forestrie-${target} --help
55
+ ```
56
+
57
+ Releases are cut by pushing a `v*` tag; the workflow fails closed unless
58
+ the tag matches the `package.json` version
59
+ ([release.yml](https://github.com/forestrie/forestrie-cli/blob/main/.github/workflows/release.yml)).
60
+ The same tag publishes the npm package
61
+ ([publish.yml](https://github.com/forestrie/forestrie-cli/blob/main/.github/workflows/publish.yml)),
62
+ with npm trusted publishing and provenance attestation.
63
+
64
+ ## Install from source
65
+
66
+ Requires [Bun](https://bun.sh) (pinned in `mise.toml`; `mise install`
67
+ works). All dependencies install **tokenless** from public npmjs — no
68
+ registry auth, no `.npmrc`:
69
+
70
+ ```bash
71
+ bun install
72
+ bun run typecheck && bun test
73
+ bun run dev -- --help # run from source
74
+ ```
75
+
76
+ ## Build the static binary
77
+
78
+ ```bash
79
+ bun run build:binary # host platform → dist/forestrie
80
+ bun run build:binary:linux-x64 # → dist/forestrie-linux-x64
81
+ bun run build:binary:darwin-arm64
82
+ ./dist/forestrie --help
83
+ ```
84
+
85
+ ## Conventions
86
+
87
+ - Human output by default; `--json` for machine-readable output on stdout.
88
+ - `-v` / `--verbosity` per `@forestrie/cli-kit` reporting (`-1` silences
89
+ stderr feedback; stdout stays pipeable).
90
+ - Env fallbacks: `FORESTRIE_BASE_URL`, `RPC_URL`, `GRANT_B64`,
91
+ `OWNER_ADDRESS`, `DEPLOYER_KEY`, `FORESTRIE_CONFIG`; any string flag also
92
+ accepts `${env:VAR}` / `${env}` whole-value templates.
93
+ - **ES256 is the paved path end-to-end**; KS256 appears only in the
94
+ Safe / ERC-1271 aside.
95
+
96
+ ## Usage
97
+
98
+ Minimal example per subcommand. `$FORESTRIE_BASE_URL`, `$RPC_URL`, and
99
+ `$GRANT_B64` are the env fallbacks from [Conventions](#conventions).
100
+
101
+ ### `deploy`
102
+
103
+ ```bash
104
+ forestrie deploy \
105
+ --bootstrap-alg es256 \
106
+ --bootstrap-es256-generate --bootstrap-es256-pem-out bootstrap.es256.pem \
107
+ --owner-address 0xYourDeployer \
108
+ --rpc-url "$RPC_URL" \
109
+ --out deployment.json
110
+ ```
111
+
112
+ ```
113
+ ImutableUnivocity deployed at: 0xAbC…123
114
+ genesisLogId: 0f9a1c7e-…-…
115
+ chainId: 84532 txHash: 0x9f…21
116
+ wrote deployment record to deployment.json
117
+ ```
118
+
119
+ ### `sign-statement`
120
+
121
+ ```bash
122
+ forestrie sign-statement \
123
+ --key alice.es256.pem \
124
+ --payload statement.json --content-type application/json \
125
+ --out statement.cose
126
+ ```
127
+
128
+ ```
129
+ signed statement: plain COSE Sign1 (ES256)
130
+ kid: 241115ab754013fcbf2e88544a369009d5d7de7f54497ad640ef28ad6237392c
131
+ iss: 241115ab754013fcbf2e88544a369009d5d7de7f54497ad640ef28ad6237392c
132
+ sub: sha-256:9c3f8a5d0e6b17c4a2f1d8e05b6c93a7714f2e8d90ab35c6e1d47f80b92c5a13
133
+ payload: 45 bytes (application/json)
134
+ statement: 253 bytes -> statement.cose
135
+ ```
136
+
137
+ Statements carry SCITT CWT claims (protected COSE header label 15,
138
+ covered by the signature). Defaults keep signing zero-config and
139
+ deterministic; every claim is settable:
140
+
141
+ - `--iss <string-or-uri>` — issuer (CWT claim 1). Default: the
142
+ lowercase hex kid. The keyword `ckt` derives the RFC 9679 COSE Key
143
+ Thumbprint URI (`urn:ietf:params:oauth:ckt:sha-256:…`) from the
144
+ signing key — which also means a literal issuer named `ckt` is not
145
+ expressible.
146
+ - `--sub <string-or-uri>` — subject the statement speaks about (CWT
147
+ claim 2). Default: `sha-256:<hex>` of the payload bytes.
148
+ - `--iat now|<unix-seconds>` — issued-at (CWT claim 6). Omitted by
149
+ default so repeated signing of the same payload is byte-identical.
150
+
151
+ Empty `--iss`/`--sub` values are rejected rather than silently
152
+ replaced with the defaults.
153
+
154
+ ### `register`
155
+
156
+ ```bash
157
+ forestrie register \
158
+ --base-url "$FORESTRIE_BASE_URL" \
159
+ --log-id "$LOG_ID" \
160
+ --statement statement.cose \
161
+ --grant-b64 "$GRANT_B64" \
162
+ --out receipt.cbor
163
+ ```
164
+
165
+ ```
166
+ entryId: 0202020202020202…0001
167
+ statusUrl: https://.../register/.../entries/…
168
+ receiptUrl: …/receipt
169
+ wrote receipt (612 bytes) to receipt.cbor
170
+ ```
171
+
172
+ ### `register-grant`
173
+
174
+ ```bash
175
+ forestrie register-grant \
176
+ --base-url "$FORESTRIE_BASE_URL" \
177
+ --owner-log "$OWNER_LOG_ID" --data-log "$DATA_LOG_ID" \
178
+ --sign-with bootstrap.es256.pem \
179
+ --out-b64 grant.b64
180
+ ```
181
+
182
+ ```
183
+ ownerLog: 0f9a1c7e-…-… (grant leaf)
184
+ dataLog: 8c2e4b…-… (authorized)
185
+ signer: 04a91f…
186
+ entryId: …
187
+ wrote completed grant base64 to grant.b64
188
+ ```
189
+
190
+ ### `complete-grant`
191
+
192
+ ```bash
193
+ forestrie complete-grant \
194
+ --grant grant.b64 \
195
+ --checkpoint checkpoint.sth --massif massif.log \
196
+ --out-b64 grant.completed.b64
197
+ ```
198
+
199
+ ```
200
+ complete-grant: leaf — mmrIndex 0 (recovered from massif)
201
+ complete-grant: entry id — 01010101010101010000000000000000 (idtimestamp from massif)
202
+ complete-grant: proof — 1 node(s) to peak 1/2
203
+ complete-grant: checkpoint — sealed size 4, delegation cert copied: no
204
+ complete-grant: receipt — 118 bytes attached (header 396)
205
+ ```
206
+
207
+ Self-creates the `Forestrie-Grant` header from a checkpoint, without an
208
+ operator round-trip: it locates the grant's leaf in the local `--massif` blob
209
+ by its commitment hash (recovering the `mmrIndex` and the sequenced
210
+ idtimestamp), rebuilds the inclusion receipt against the checkpoint's pre-signed
211
+ peak, and attaches the receipt + idtimestamp — the same completed bearer
212
+ `register-grant` produces online. `--massif` is required (it carries the leaf);
213
+ `--idtimestamp <hex|path>` is a fallback for the rare massif with no index
214
+ region.
215
+
216
+ ### `resolve-receipt`
217
+
218
+ One receipt producer (SCRAPI §2.4 "Resolve Receipt"; `create-receipt` is a
219
+ kept alias). The **source is chosen by the flags present**, not a `--mode`;
220
+ passing more than one source, or an under-specified one, errors with guidance.
221
+
222
+ ```bash
223
+ # from tiles: rebuild the leaf→peak path from a massif and a checkpoint
224
+ forestrie resolve-receipt \
225
+ --massif massif.log --checkpoint checkpoint.sth \
226
+ --mmr-index 0 \
227
+ --out receipt.cbor
228
+ ```
229
+
230
+ ```
231
+ resolve-receipt: massif — index 0 (height 3, mmr indexes 0..3)
232
+ resolve-receipt: leaf — mmrIndex 0 (from --mmr-index)
233
+ resolve-receipt: checkpoint — sealed size 4, 2 peak(s)
234
+ resolve-receipt: proof — 1 node(s) to peak 1/2 (mmrIndex 2)
235
+ resolve-receipt: receipt — 144 bytes -> receipt.cbor
236
+ ```
237
+
238
+ Add `--univocity <address> --log-id <id> --rpc-url $RPC_URL` for a
239
+ chain-anchored (report-only) verification instead of a checkpoint-based
240
+ receipt.
241
+
242
+ #### Freshen a stale receipt (tile-free)
243
+
244
+ When log growth buries the peak a receipt commits to, **freshen** re-anchors
245
+ it to the current sealed state without tiles. `resolve-receipt --receipt
246
+ <stale>` plus a tile-free source extends the receipt's old inclusion path to
247
+ the latest peak and re-emits it. The leaf value is recomputed exactly as
248
+ `verify` does, from the same leaf inputs: `--payload <statement>` for statement
249
+ receipts, or `--committed-grant`/`--committed-grant-file` for grant receipts
250
+ (both with `--entry-id`).
251
+
252
+ ```bash
253
+ # a STATEMENT receipt from a retained .sth chain (emitted under the chain's tail)
254
+ forestrie resolve-receipt \
255
+ --receipt stale.cbor --checkpoint-chain ./checkpoints/ \
256
+ --payload statement.cose --entry-id <hex> \
257
+ --in-place
258
+
259
+ # a GRANT receipt, same chain source
260
+ forestrie resolve-receipt \
261
+ --receipt stale.cbor --checkpoint-chain ./checkpoints/ \
262
+ --committed-grant-file grant.cbor --entry-id <hex> \
263
+ --in-place
264
+
265
+ # from on-chain publishCheckpoint calldata; calldata carries no peak receipts,
266
+ # so the latest .sth is supplied for emission, and --known-accumulator binds
267
+ # the folded state to a trusted snapshot
268
+ forestrie resolve-receipt \
269
+ --receipt stale.cbor \
270
+ --rpc-url $RPC_URL --univocity <address> --log-id <id> \
271
+ --checkpoint latest.sth --known-accumulator accumulator.cbor \
272
+ --committed-grant-file grant.cbor --entry-id <hex> \
273
+ --out fresh.cbor
274
+ ```
275
+
276
+ `--in-place` rewrites the `--receipt` file (mutually exclusive with `--out`;
277
+ crash-safe — written to a sibling temp then atomically renamed). The freshened
278
+ receipt is a native receipt that verifies with plain `verify` against the
279
+ current state, and freshen fails closed (it never emits a receipt whose
280
+ recomputed peak does not match the folded latest accumulator).
281
+
282
+ #### Bind the freshened state (`--known-accumulator`)
283
+
284
+ Both freshen sources fold a consistency-proof chain to the current accumulator;
285
+ they differ only in the **emission checkpoint**. `--checkpoint-chain` emits under
286
+ the chain's own tail (self-contained — reach for it by default). The calldata
287
+ source borrows a **separately-supplied** latest `--checkpoint`, so pass
288
+ **`--known-accumulator <snapshot>`** (a `fetch-accumulator` capture) to bind the
289
+ folded state to a trusted chain read: freshen asserts it **equals your snapshot
290
+ at the same size** and fails closed otherwise (this also catches a lying/stale
291
+ RPC). `--known-accumulator` works with either source.
292
+
293
+ `--known-accumulator` anchors **freshness** — that the leaf roots into the
294
+ genuine current log — which is a *different* question from who sealed the state
295
+ or whether the log's authority chains back to genesis. Freshen never re-opens
296
+ those; it re-anchors an already-issued receipt to the current accumulator. Which
297
+ anchor proves what is the trust model: see
298
+ **[the trust model](https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md#freshen-and-the-attestor)**.
299
+
300
+ ### `decode-receipt`
301
+
302
+ ```bash
303
+ forestrie decode-receipt receipt.cbor
304
+ ```
305
+
306
+ ```
307
+ COSE_Sign1 — tagged 18 (COSE_Sign1) — 304 bytes
308
+ ├─ protected: 1 (alg): -7 — ES256 4 (kid): 6c6c6c6c…
309
+ ├─ unprotected: inclusion proof — mmr index 5, path length 3
310
+ ├─ payload: detached — recomputed from the inclusion path at verify time
311
+ └─ signature: 64 bytes
312
+ ```
313
+
314
+ ### `verify`
315
+
316
+ ```bash
317
+ forestrie verify \
318
+ --genesis genesis.cbor --receipt receipt.cbor --committed-grant "$GRANT_B64"
319
+ ```
320
+
321
+ ```
322
+ verify: parse ok — receipt COSE decodes; genesis trust root loads (ES256)
323
+ verify: signature ok — checkpoint signature verifies under the genesis trust key
324
+ verify: inclusion ok — proof path recomputes the checkpoint peak
325
+ verify: binding ok — leaf binds the grant commitment at the receipt idtimestamp
326
+ PASS: receipt verified offline against the cached checkpoint
327
+ ```
328
+
329
+ Add `--univocity <address> --log-id <id> --rpc-url $RPC_URL` to check the
330
+ receipt's peak against the on-chain accumulator instead of the checkpoint
331
+ signature — no operator trust required.
332
+
333
+ #### Trust anchors (FOR-297)
334
+
335
+ `verify` takes four named trust anchors — pass whichever you hold:
336
+
337
+ - **`--known-log-key`** — a caller-known log owner key (base64 `x||y`, env
338
+ `KNOWN_LOG_KEY`); fully offline, checks the signature under a key you trust
339
+ out of band (the SCITT / SSH-known-hosts posture).
340
+ - **`--genesis`** — a cached `genesis.cbor` (the univocity-instance root
341
+ registration). Roots a receipt whose signer chains to that root owner; for a
342
+ child log deeper in the hierarchy, reaching genesis is the grant-chain walk
343
+ (still open) or the on-chain path below.
344
+ - **`--known-accumulator`** — a cached chain read (`forestrie fetch-accumulator`):
345
+ verifies the peak against the on-chain accumulator, no operator trust and no
346
+ signature needed. Never source it unauthenticated from the operator's tile
347
+ store. Older receipts extend to a newer snapshot via `--massif`.
348
+ - **`--rpc-url`** — a live chain read; same as `--known-accumulator`, current.
349
+
350
+ What each actually proves — and how forestrie separates **split-view** (is this
351
+ a single, un-forked history) from **sealing** (who signed this state) from
352
+ **authority** (does the log chain to genesis, via grants — not `genesis.cbor`)
353
+ from **attribution** (who was authorised to sign this leaf) — is the trust
354
+ model: see
355
+ **[the trust model](https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md)**.
356
+ The receipt never expires, and the anchor never needs to be current — only
357
+ trusted.
358
+
359
+ Note that **freshness is not one of the four**. It is a weaker axis that bears
360
+ only on *coverage* — whether a given accumulator snapshot reaches your leaf —
361
+ and collapsing it into split-view is the specific confusion the trust model
362
+ warns against. Split-view is the load-bearing property; currency is at most the
363
+ `--rpc-url` "as of now" delta.
364
+
365
+ ### `fetch-accumulator`
366
+
367
+ ```bash
368
+ forestrie fetch-accumulator \
369
+ --univocity "$UNIVOCITY" --log-id "$LOG_ID" --rpc-url "$RPC_URL" \
370
+ --out accumulator.cbor
371
+ ```
372
+
373
+ Reads `logState(logId)` pinned to a block and writes the
374
+ `--known-accumulator` snapshot: canonical CBOR binding
375
+ `(chainId, univocity, logId, size, blockNumber, blockHash)` — anyone with
376
+ RPC can re-run the read at that block and confirm or disprove it.
377
+
378
+ ## Use as a library: `decode-receipt`
379
+
380
+ The receipt decoder behind `forestrie decode-receipt` is exported as a
381
+ subpath so other tools can render receipts without shelling out to the CLI
382
+ or vendoring a copy:
383
+
384
+ ```ts
385
+ import {
386
+ decodeReceipt,
387
+ renderReceipt,
388
+ HEADER_LABELS,
389
+ } from "@forestrie/forestrie-cli/decode-receipt";
390
+
391
+ const decoded = decodeReceipt(receiptBytes); // Uint8Array in
392
+ console.log(renderReceipt(decoded)); // the annotated tree
393
+ console.log(JSON.stringify(decoded, null, 2)); // the same model as --json
394
+ ```
395
+
396
+ The entry point is pure: no `node:*`, no `Bun.*`, no I/O and no network, so
397
+ it runs unchanged in Node, Bun, Deno, a worker or a browser. Its only
398
+ dependencies are `@forestrie/receipt-verify` (the load-bearing receipt
399
+ parse) and `@forestrie/encoding`. Ships with `.d.ts`.
400
+
401
+ Also exported: `DecodeReceiptError` (carries the failing `stage`), the
402
+ display-model types (`DecodedReceipt`, `DecodedHeaderEntry`, `DecodedClaim`,
403
+ `Json`), the label tables (`HEADER_LABELS`, `ALG_NAMES`, `VDS_NAMES`,
404
+ `CWT_CLAIM_NAMES`, `COSE_KEY_PARAM_NAMES`, `PROOF_KIND_NAMES`,
405
+ `headerLabelInfo`) and the minimal CBOR reader (`decodeCborValue`,
406
+ `decodeCborMap`, `isCborTagged`). The bare `@forestrie/forestrie-cli`
407
+ specifier re-exports the same surface; prefer the subpath.
408
+
409
+ Decoding is **display only** — it does not check signatures or inclusion.
410
+ That is `forestrie verify` (and `@forestrie/receipt-verify` in-process).
411
+
412
+ ## Built on (published packages)
413
+
414
+ The binary is buildable from **published packages only** (plan-2607-12) —
415
+ all public on npmjs under the `@forestrie` scope, MIT licensed per-package,
416
+ installable without auth:
417
+
418
+ `@forestrie/cli-kit` · `@forestrie/deploy-core` · `@forestrie/encoding` ·
419
+ `@forestrie/grant-builder` · `@forestrie/scrapi-client` ·
420
+ `@forestrie/receipt-verify` · `@forestrie/merklelog` ·
421
+ `@forestrie/delegation-cose` · `viem` (deploy tx signing/submission)
422
+
423
+ ## License
424
+
425
+ MIT — see [LICENSE](./LICENSE).
package/TRUST-MODEL.md ADDED
@@ -0,0 +1,8 @@
1
+ # Forestrie receipt trust model — moved
2
+
3
+ Moved to `forestrie/protocol`:
4
+ **[`spec/receipt-trust-model.md`](https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md)**
5
+ (anchors preserved, including
6
+ [`#freshen-and-the-attestor`](https://github.com/forestrie/protocol/blob/main/spec/receipt-trust-model.md#freshen-and-the-attestor)).
7
+
8
+ Per-command recipes remain in [README.md](./README.md).