@sanning/proof 0.19.0 → 0.20.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 (98) hide show
  1. package/README.md +30 -29
  2. package/dist/agent-record.d.ts +62 -0
  3. package/dist/agent-record.js +527 -0
  4. package/dist/agent-schemas.generated.d.ts +15 -0
  5. package/dist/agent-schemas.generated.js +5759 -0
  6. package/dist/case-file.d.ts +32 -89
  7. package/dist/case-file.js +41 -93
  8. package/dist/case.d.ts +343 -810
  9. package/dist/case.js +693 -1151
  10. package/dist/chain.d.ts +19 -47
  11. package/dist/chain.js +78 -205
  12. package/dist/claims.d.ts +211 -0
  13. package/dist/claims.js +1081 -0
  14. package/dist/cli.d.ts +45 -68
  15. package/dist/cli.js +618 -1022
  16. package/dist/compose.js +23 -65
  17. package/dist/crypto.d.ts +7 -8
  18. package/dist/crypto.js +60 -147
  19. package/dist/declaration.d.ts +68 -194
  20. package/dist/declaration.js +175 -518
  21. package/dist/environment.d.ts +9 -30
  22. package/dist/environment.js +17 -66
  23. package/dist/evidence.d.ts +119 -160
  24. package/dist/evidence.js +744 -1103
  25. package/dist/exit-codes.d.ts +11 -27
  26. package/dist/exit-codes.js +24 -59
  27. package/dist/file-match.d.ts +33 -79
  28. package/dist/file-match.js +33 -69
  29. package/dist/grading.d.ts +98 -135
  30. package/dist/grading.js +224 -380
  31. package/dist/handoff.d.ts +62 -160
  32. package/dist/handoff.js +153 -393
  33. package/dist/index.d.ts +14 -10
  34. package/dist/index.js +60 -167
  35. package/dist/json-schema.d.ts +29 -0
  36. package/dist/json-schema.js +298 -0
  37. package/dist/merkle.js +16 -28
  38. package/dist/message-parts.d.ts +56 -212
  39. package/dist/message-parts.js +142 -502
  40. package/dist/side-content.d.ts +11 -22
  41. package/dist/side-content.js +22 -65
  42. package/dist/types.d.ts +17 -0
  43. package/dist/types.js +19 -5
  44. package/dist/verifier.d.ts +40 -114
  45. package/dist/verifier.js +196 -535
  46. package/dist/witness-cache.d.ts +24 -45
  47. package/dist/witness-cache.js +38 -81
  48. package/dist/witness-lookup.d.ts +36 -57
  49. package/dist/witness-lookup.js +89 -139
  50. package/dist/zip-reader.d.ts +18 -33
  51. package/dist/zip-reader.js +19 -34
  52. package/package.json +11 -3
  53. package/src/agent-record.ts +608 -0
  54. package/src/agent-schemas.generated.ts +5764 -0
  55. package/src/case-file.ts +45 -109
  56. package/src/case.ts +848 -1322
  57. package/src/chain.ts +85 -224
  58. package/src/claims.ts +1253 -0
  59. package/src/cli.ts +714 -1176
  60. package/src/compose.ts +33 -79
  61. package/src/crypto.ts +60 -147
  62. package/src/declaration.ts +219 -656
  63. package/src/environment.ts +17 -65
  64. package/src/evidence.ts +958 -1381
  65. package/src/exit-codes.ts +24 -63
  66. package/src/file-match.ts +40 -89
  67. package/src/grading.ts +287 -421
  68. package/src/handoff.ts +175 -427
  69. package/src/index.ts +132 -177
  70. package/src/json-schema.ts +295 -0
  71. package/src/merkle.ts +16 -28
  72. package/src/message-parts.ts +155 -533
  73. package/src/side-content.ts +25 -68
  74. package/src/types.ts +55 -36
  75. package/src/verifier.ts +202 -526
  76. package/src/witness-cache.ts +45 -94
  77. package/src/witness-lookup.ts +107 -177
  78. package/src/zip-reader.ts +20 -38
  79. package/vectors/README.md +52 -34
  80. package/vectors/agent-record.json +2175 -0
  81. package/vectors/case-file-index.json +1 -1
  82. package/vectors/case-split.json +2 -2
  83. package/vectors/chain-reading.json +3 -3
  84. package/vectors/file-match.json +285 -148
  85. package/vectors/jcs-reader.json +6 -6
  86. package/vectors/pack-grading.json +102 -25
  87. package/vectors/sandbox-evidence.json +597 -580
  88. package/vectors/stamp-on-chain.json +18 -18
  89. package/vectors/witness-cache.json +49 -49
  90. package/dist/agent-proof.d.ts +0 -28
  91. package/dist/agent-proof.js +0 -226
  92. package/dist/run-adapters.d.ts +0 -10
  93. package/dist/run-adapters.js +0 -35
  94. package/dist/run.d.ts +0 -209
  95. package/dist/run.js +0 -1405
  96. package/src/agent-proof.ts +0 -264
  97. package/src/run-adapters.ts +0 -44
  98. package/src/run.ts +0 -1598
@@ -1,24 +1,11 @@
1
1
  // The public-record (witness) lookup the CLI runs with `--gateways`
2
- // (evidence-bundle.md §5.5.6, v1.20, A24, D7): re-fetch each pack's witness on
3
- // the public record and report what it said. Moved here from `cli.ts`
4
- // (proof#200) so the CLI and a consumer that is not the CLI, the verify page,
5
- // a browser app, call the SAME implementation rather than each keeping its
6
- // own copy that can drift.
2
+ // (evidence-bundle.md §5.5.6): re-fetch each pack's witness on the public record
3
+ // and report what it said. The CLI and non-CLI callers (the verify page, a
4
+ // browser app) share this one implementation.
7
5
  //
8
- // Pure by the kernel's own rule (specs/architecture.md §3): the network call
9
- // is the one exception this module exists to hold, and it never runs unless a
10
- // caller hands in a gateway to ask. `options.fetchImpl` is the seam: where a
11
- // caller supplies one (a browser page's own `fetch`, or a test's fake), that
12
- // is the ONE this module calls; where none is given, it falls back to the
13
- // ambient global `fetch`, the same fallback `cli.ts` already applied at its
14
- // own call site before this module existed.
15
- //
16
- // `onChainHalf` and `witnessLine` are the CLI's own building blocks,
17
- // UNCHANGED (proof#200 is additive: the CLI's `case` output and exit codes are
18
- // byte-for-byte what they were). `lookupPublicRecord` is the new export:
19
- // one pack (or several) in, one four-state result out, with a pack's
20
- // `witness-cache.json` read as part of the pack rather than as a side file a
21
- // caller must remember to also pass in.
6
+ // The network call is the one exception to the kernel's purity (specs/architecture.md §3).
7
+ // It never runs unless a caller hands in a gateway, and `options.fetchImpl`
8
+ // is the seam: the caller's `fetch` if given, else the ambient global.
22
9
  import { SIMULATED_WITNESS_KIND, verifyEvidenceBundle } from "./evidence.js";
23
10
  import { WITNESS_NOT_LOOKED_UP, renderWitnessLine } from "./case.js";
24
11
  import { checkWitnessCache, readWitnessCache, renderWitnessTime, } from "./witness-cache.js";
@@ -35,23 +22,60 @@ function witnessTimeRow(t) {
35
22
  })),
36
23
  };
37
24
  }
25
+ /** One pack's witnesses as a lookup left them: how many, and each one's rollup. */
26
+ export function onChainRow(ref, result) {
27
+ const witnesses = [...result.checkpoints, ...(result.stamps ?? [])];
28
+ const row = {
29
+ ref,
30
+ witnesses: witnesses.length,
31
+ confirmed: 0,
32
+ mismatched: 0,
33
+ unreachable: 0,
34
+ not_checked: 0,
35
+ };
36
+ for (const w of witnesses) {
37
+ const rollup = w.onChain?.rollup ?? null;
38
+ if (rollup === "confirm")
39
+ row.confirmed++;
40
+ else if (rollup === "mismatch")
41
+ row.mismatched++;
42
+ else if (rollup === "unreachable")
43
+ row.unreachable++;
44
+ else
45
+ row.not_checked++;
46
+ }
47
+ const simulated = witnesses.filter((w) => w.anchorRef?.kind === SIMULATED_WITNESS_KIND).length;
48
+ if (simulated > 0)
49
+ row.simulated = simulated;
50
+ const answered = new Set();
51
+ for (const w of witnesses) {
52
+ for (const outcome of w.onChain?.perGateway ?? []) {
53
+ if (outcome.outcome === "confirm" || outcome.outcome === "mismatch") {
54
+ answered.add(outcome.gateway);
55
+ }
56
+ }
57
+ }
58
+ return { row, answered };
59
+ }
38
60
  /**
39
- * Each pack's witnesses, re-fetched through `verifyEvidenceBundle`. Outside
40
- * the case reading on purpose: `readCase` makes no network call, and neither
41
- * does the verify page's case reading, so the case verdict stays the one both
42
- * surfaces compute. This adds the half the reading names as not checked.
43
- *
44
- * Moved from `cli.ts` unchanged, apart from taking `fetchImpl` directly
45
- * instead of a `Cli` stream (`io.fetchImpl ?? fetch`, the same fallback the
46
- * CLI already applied at its own call site).
61
+ * `differs` where a lookup found other bytes or another time, `not_settled` where one
62
+ * did not settle, `confirmed` otherwise. Only `differs` moves a verdict.
47
63
  */
48
- export async function onChainHalf(packs, gateways, options = {},
64
+ export function onChainOutcome(rows) {
65
+ if (rows.some((r) => r.mismatched > 0 || (r.witness_times ?? []).some((t) => t.outcome === "differs"))) {
66
+ return "differs";
67
+ }
68
+ if (rows.some((r) => r.witnesses === 0 || r.unreachable > 0 || r.not_checked > 0)) {
69
+ return "not_settled";
70
+ }
71
+ return "confirmed";
72
+ }
49
73
  /**
50
- * Where given, receives for each pack the `stamp_ref` of every stamp whose
51
- * witness this lookup confirmed: the run reading evaluates an enclave token
52
- * at a stamp's interval end only there.
74
+ * Each pack's witnesses, re-fetched through `verifyEvidenceBundle`. Outside the
75
+ * case reading on purpose: `readCase` makes no network call, so the case verdict
76
+ * is the same on every surface. This adds the half the reading names as not checked.
53
77
  */
54
- witnessed) {
78
+ export async function onChainHalf(packs, gateways, options = {}) {
55
79
  if (gateways.length === 0)
56
80
  return { checked: false, gateways: [], packs: [], verdict: "not_checked" };
57
81
  const fetchImpl = options.fetchImpl ?? fetch;
@@ -59,44 +83,14 @@ witnessed) {
59
83
  const answered = new Set();
60
84
  for (const [ref, bundle, cached] of packs) {
61
85
  const result = await verifyEvidenceBundle(bundle, { gateways: [...gateways], fetchImpl });
62
- const witnesses = [...result.checkpoints, ...(result.stamps ?? [])];
63
- const row = {
64
- ref,
65
- witnesses: witnesses.length,
66
- confirmed: 0,
67
- mismatched: 0,
68
- unreachable: 0,
69
- not_checked: 0,
70
- };
71
- witnessed?.push((result.stamps ?? []).filter((st) => st.ok && st.onChain?.rollup === "confirm").map((st) => st.stampRef));
72
- for (const w of witnesses) {
73
- const rollup = w.onChain?.rollup ?? null;
74
- if (rollup === "confirm")
75
- row.confirmed++;
76
- else if (rollup === "mismatch")
77
- row.mismatched++;
78
- else if (rollup === "unreachable")
79
- row.unreachable++;
80
- else
81
- row.not_checked++;
82
- }
83
- const simulated = witnesses.filter((w) => w.anchorRef?.kind === SIMULATED_WITNESS_KIND).length;
84
- if (simulated > 0)
85
- row.simulated = simulated;
86
- // The witness times the pack's witness-cache.json gives, looked up where
87
- // it gives any (5.6.6, v1.25, A30). A pack with no cache gets no key and
88
- // no request.
86
+ const { row, answered: heard } = onChainRow(ref, result);
87
+ for (const g of heard)
88
+ answered.add(g);
89
+ // The witness times the pack's witness-cache.json gives (5.6.6). A pack with no cache gets no key and no request.
89
90
  const times = await checkWitnessCache(cached, gateways, fetchImpl);
90
91
  if (times.length > 0)
91
92
  row.witness_times = times.map(witnessTimeRow);
92
93
  rows.push(row);
93
- for (const w of witnesses) {
94
- for (const outcome of w.onChain?.perGateway ?? []) {
95
- if (outcome.outcome === "confirm" || outcome.outcome === "mismatch") {
96
- answered.add(outcome.gateway);
97
- }
98
- }
99
- }
100
94
  for (const t of times) {
101
95
  for (const a of t.answers) {
102
96
  if (a.outcome === "matches" || a.outcome === "differs")
@@ -104,11 +98,7 @@ witnessed) {
104
98
  }
105
99
  }
106
100
  }
107
- const verdict = rows.some((r) => r.mismatched > 0 || (r.witness_times ?? []).some((t) => t.outcome === "differs"))
108
- ? "failed"
109
- : rows.some((r) => r.witnesses === 0 || r.unreachable > 0 || r.not_checked > 0)
110
- ? "undetermined"
111
- : "confirmed";
101
+ const verdict = onChainOutcome(rows);
112
102
  return {
113
103
  answered: gateways.filter((g) => answered.has(g)),
114
104
  checked: true,
@@ -124,8 +114,7 @@ function distinctTimes(times) {
124
114
  seen.set(`${h}:${t}`, [h, t]);
125
115
  return [...seen.values()].sort((a, b) => a[1] - b[1] || a[0] - b[0]);
126
116
  }
127
- // Only the keys that carry something: a line with no cached or recorded time
128
- // is the line 0.13.0 printed, field for field.
117
+ // Only the keys that carry something.
129
118
  function withTimes(line, cached, recorded) {
130
119
  return {
131
120
  ...line,
@@ -134,15 +123,11 @@ function withTimes(line, cached, recorded) {
134
123
  };
135
124
  }
136
125
  /**
137
- * `cached` is every witness time the packs cache (5.6.6, v1.25, A30). Where no
138
- * lookup ran, each is printed labelled not checked. Where one ran, each time
139
- * the public record matched is printed as the record's, each time no lookup
140
- * settled stays labelled not checked, and a time the record contradicts is the
141
- * reason the line reads `differs`.
142
- *
143
- * Moved from `cli.ts` unchanged: same inputs, same three states
144
- * (`WitnessState`), same rendering. The CLI's `case` command still calls this
145
- * exact function, so its witness line is unchanged.
126
+ * `cached` is every witness time the packs cache (5.6.6). Where no lookup ran,
127
+ * each is printed labelled not checked. Where one ran, each time the public
128
+ * record matched is printed as the record's, each time no lookup settled stays
129
+ * labelled not checked, and a time the record contradicts makes the line read
130
+ * `differs`.
146
131
  */
147
132
  export function witnessLine(onChain, cached = []) {
148
133
  if (!onChain.checked) {
@@ -172,7 +157,7 @@ export function witnessLine(onChain, cached = []) {
172
157
  if (!contradicted.includes(reason))
173
158
  contradicted.push(reason);
174
159
  }
175
- if (onChain.verdict === "failed") {
160
+ if (onChain.verdict === "differs") {
176
161
  return withTimes({ state: "differs", reasons: contradicted, services }, unsettled, recorded);
177
162
  }
178
163
  if (onChain.verdict === "confirmed") {
@@ -190,34 +175,22 @@ export function witnessLine(onChain, cached = []) {
190
175
  return withTimes({ state: "not_checked", reasons, services }, unsettled, recorded);
191
176
  }
192
177
  /**
193
- * The public-record lookup the CLI runs with `--gateways`, exported so a
194
- * consumer that is not the CLI can run the SAME lookup rather than copy it
195
- * (proof#200; the copy this closes is verify-page#70's `src/lib/verify.ts`).
178
+ * The public-record lookup the CLI runs with `--gateways`, for non-CLI callers.
196
179
  *
197
- * `packs` is every pack in the drop being read; `gateways` is empty for an
198
- * offline reading, which never makes a network call and always answers
199
- * `not_checked` or `no_public_record`. `options.fetchImpl` is the one seam a
200
- * network call goes through, a browser page supplies its own `fetchImpl`;
201
- * with none given this falls back to the ambient global `fetch`, so a
202
- * browser page and a test can each supply their own and Node's `fetch` is
203
- * never assumed where a caller passed something else.
180
+ * `packs` is every pack in the drop; `gateways` is empty for an offline reading,
181
+ * which makes no network call and answers `not_checked` or `no_public_record`.
204
182
  *
205
- * `state` is `confirmed` only about the BYTES: the public record holds what a
206
- * pack names. It says nothing about whether the pack itself verifies (its
207
- * wrapper signature, its `body_hash`), and a caller must never show
208
- * `confirmed` apart from the pack's own verdict (proof#201).
183
+ * `state` is `confirmed` only about the bytes: the public record holds what a
184
+ * pack names. It says nothing about whether the pack itself verifies, so a
185
+ * caller must never show `confirmed` apart from the pack's own verdict.
209
186
  *
210
- * Never throws. Every input, including a malformed pack, a non-string
211
- * gateway, or a `fetchImpl` that itself misbehaves, is read as untrusted, and
212
- * anything this function or what it calls cannot make sense of settles as
213
- * `not_checked` rather than escape as an exception.
187
+ * Never throws. Every input (a malformed pack, a non-string gateway, a
188
+ * misbehaving `fetchImpl`) is untrusted, and anything it cannot make sense of
189
+ * settles as `not_checked`.
214
190
  */
215
191
  export async function lookupPublicRecord(packs, gateways, options = {}) {
216
192
  try {
217
- // Nothing to look up: zero packs means the CALLER handed in nothing to
218
- // check, which is not the same claim as "the packs it handed in name no
219
- // witness" (`no_public_record`, below). Checked first, and unconditionally,
220
- // so a caller than mishandles an empty drop never reads it as confirmed.
193
+ // Zero packs is the caller handing in nothing, not packs that name no witness (`no_public_record`). Checked first so an empty drop never reads as confirmed.
221
194
  if (packs.length === 0) {
222
195
  const reasons = [...WITNESS_NOT_LOOKED_UP.reasons];
223
196
  return {
@@ -235,23 +208,12 @@ export async function lookupPublicRecord(packs, gateways, options = {}) {
235
208
  ]);
236
209
  const onChain = await onChainHalfForWitnessCount(rows, gateways, options);
237
210
  const line = witnessLine(onChain.half, caches.flat());
238
- // "No public record yet": every pack this lookup was handed names ZERO
239
- // witnesses, so there is nothing the public record could confirm or
240
- // contradict. This holds independently of whether a lookup ran, an
241
- // offline reading of a fresh, unstamped pack is exactly as much "nothing to
242
- // check yet" as an online one, and it is computed from the SAME witness
243
- // count `onChainHalf` derives from `verifyEvidenceBundle`'s own parse, never
244
- // a second reading of the bundle.
245
- //
246
- // A pack that does NOT verify (a broken wrapper signature, `bundle: null`,
247
- // garbage, an unrecognised body type) also names zero witnesses, by
248
- // construction: `verifyEvidenceBundle` never reports a checkpoint or a
249
- // stamp for a bundle it could not authenticate. That is not "nothing to
250
- // check yet", it is a pack this lookup could not read, so `no_public_record`
251
- // is reserved for a pack whose wrapper genuinely verifies
252
- // (`signatureOk && bodyHashOk && status !== "failed"`) and that carries no
253
- // cached witness time either (a cache entry names something to check).
254
- // Anything else that would otherwise qualify reads `not_checked` instead.
211
+ // `no_public_record`: every pack names zero witnesses, so nothing could be
212
+ // confirmed or contradicted. Reserved for packs whose wrapper verifies
213
+ // (`signatureOk && bodyHashOk && status !== "failed"`) and that carry no
214
+ // cached witness time: a pack that fails to verify also names zero
215
+ // witnesses, and that is a pack this lookup could not read, so it reads
216
+ // `not_checked`.
255
217
  const noWitnesses = onChain.witnessCounts.every((n) => n === 0);
256
218
  const everyPackVerifies = onChain.verifies.every((v) => v);
257
219
  const noCacheRead = caches.every((c) => c.length === 0);
@@ -262,19 +224,13 @@ export async function lookupPublicRecord(packs, gateways, options = {}) {
262
224
  reasons: line.reasons,
263
225
  services: line.services,
264
226
  ...(line.cached ? { cached: line.cached } : {}),
265
- // `recorded` is a claim about the public record confirming a cached
266
- // time; it never rides alongside anything but a `confirmed` verdict, so
267
- // a pack whose wrapper does not verify can never surface a cached time
268
- // as though the record had confirmed it.
227
+ // `recorded` rides only with a `confirmed` verdict, so a pack whose wrapper does not verify never surfaces a cached time as record-confirmed.
269
228
  ...(state === "confirmed" && line.recorded ? { recorded: line.recorded } : {}),
270
229
  rendered: renderWitnessLine({ ...line, state: state === "no_public_record" ? "not_checked" : state }),
271
230
  };
272
231
  }
273
232
  catch (e) {
274
- // Untrusted input all the way down: a gateway's answer, a pack's shape, a
275
- // caller's `fetchImpl`. Anything this function or what it calls cannot
276
- // make sense of settles `not_checked` rather than throw out of an export a
277
- // browser page calls directly.
233
+ // Untrusted input all the way down (a gateway's answer, a pack's shape, a `fetchImpl`): settle `not_checked` rather than throw out of an export a page calls directly.
278
234
  const reason = e instanceof Error ? `the lookup did not settle: ${e.message}` : "the lookup did not settle";
279
235
  const reasons = [reason];
280
236
  return {
@@ -285,15 +241,9 @@ export async function lookupPublicRecord(packs, gateways, options = {}) {
285
241
  };
286
242
  }
287
243
  }
288
- // `onChainHalf` short-circuits to `{ checked: false, packs: [] }` with no
289
- // gateways, which is right for the CLI (nothing to print per pack) but leaves
290
- // `lookupPublicRecord` unable to tell "no gateways" from "no witnesses" for an
291
- // OFFLINE reading. This wrapper reads each pack's witness count, and whether
292
- // its wrapper verifies, the same way `onChainHalf` does, via
293
- // `verifyEvidenceBundle`: an OFFLINE parse (`gateways: []`), so it costs no
294
- // extra request whether or not a lookup runs, and `verifies` is never itself
295
- // a statement about the public record, only about the pack's own signature
296
- // and `body_hash`.
244
+ // Reads each pack's witness count and whether its wrapper verifies, via an
245
+ // offline `verifyEvidenceBundle` parse (`gateways: []`), so it costs no request.
246
+ // `verifies` is about the pack's own signature and `body_hash`, never the public record.
297
247
  async function onChainHalfForWitnessCount(packs, gateways, options) {
298
248
  const witnessCounts = [];
299
249
  const verifies = [];
@@ -1,57 +1,42 @@
1
1
  /**
2
2
  * The one ZIP reader `case` uses, for a case file named on the command line.
3
3
  *
4
- * The TypeScript half of `src/sanning_proof/zip_reader.py`. Node has no ZIP
5
- * reader, and Python's `zipfile` is not used, so the two kernels read one
6
- * archive by one rule rather than by two libraries that disagree on the edges:
7
- * a central directory that contradicts a local header, a name that is not
8
- * UTF-8, data after the end of a deflate stream. Where either library would
9
- * have made a choice, this module refuses instead.
4
+ * Node has no ZIP reader. This one refuses where two libraries would disagree
5
+ * (evidence-bundle.md 5.6.1). It reads the central directory and each entry
6
+ * through its local header, stored and deflate only.
10
7
  *
11
- * What it reads (evidence-bundle.md 5.6.1): the central directory, and each
12
- * entry's data through its local header. Stored and deflate only. What it
13
- * REFUSES, as a whole archive: no end-of-central-directory record, ZIP64, more
14
- * than one disk, an entry the central directory places outside the archive, a
15
- * local header that does not match its central entry, a size or CRC-32 that
16
- * does not match the data, two entries with one name, a name that is not
17
- * UTF-8, more than MAX_ZIP_ENTRIES entries or MAX_ZIP_BYTES bytes in all, and
18
- * a name nested deeper than MAX_ZIP_DEPTH folders. What it REPORTS and never
19
- * reads, entry by entry: an encrypted entry, a symbolic link, a method other
20
- * than stored or deflate, and a name that is absolute, holds a backslash or a
21
- * NUL, or has an empty, `.` or `..` segment.
8
+ * It refuses the whole archive for: no end-of-central-directory record, ZIP64,
9
+ * more than one disk, an entry placed outside the archive, a local header that
10
+ * contradicts its central entry, a size or CRC-32 that does not match, two
11
+ * entries with one name, a name that is not UTF-8, more than MAX_ZIP_ENTRIES
12
+ * entries or MAX_ZIP_BYTES bytes, or a name nested deeper than MAX_ZIP_DEPTH.
13
+ * It reports without reading, per entry: an encrypted entry, a symbolic link,
14
+ * another method, and a name that is absolute, holds a backslash or NUL, or has
15
+ * an empty, `.` or `..` segment.
22
16
  *
23
- * Pure: bytes in, entries out. The caller turns them into the tree `case`
24
- * splits.
17
+ * Pure: bytes in, entries out.
25
18
  */
26
- /** More entries than this refuse the archive: the walk's own bound on names. */
19
+ /** More entries than this refuse the archive. */
27
20
  export declare const MAX_ZIP_ENTRIES = 50000;
28
21
  /** More uncompressed bytes than this, across every entry, refuse the archive. */
29
22
  export declare const MAX_ZIP_BYTES: number;
30
23
  /**
31
24
  * An archive file larger than this is refused before it is read: the
32
- * uncompressed cap, and room for the headers, the central directory and the
33
- * deflate framing of an archive that holds that much. The caller checks the
34
- * size on disk, so the archive is never read whole to find out it is too big.
25
+ * uncompressed cap plus room for headers and framing. The caller checks the
26
+ * size on disk.
35
27
  */
36
28
  export declare const MAX_ZIP_ARCHIVE_BYTES: number;
37
29
  /** A name nested deeper than this many folders refuses the archive. */
38
30
  export declare const MAX_ZIP_DEPTH = 32;
39
- /** The archive cannot be read as a case file. `message` is the whole report, identical in both kernels. */
31
+ /** The archive cannot be read as a case file. `message` is the whole report. */
40
32
  export declare class ZipReadError extends Error {
41
33
  constructor(message: string);
42
34
  }
43
- /**
44
- * One file entry. `refused` is why the entry is reported and not read, or
45
- * `null`; `data` is its bytes where it was read.
46
- */
35
+ /** One file entry. `refused` is why it is reported and not read, or `null`; `data` is set where it was read. */
47
36
  export interface ZipEntry {
48
37
  name: string;
49
38
  refused: string | null;
50
39
  data: Uint8Array | null;
51
40
  }
52
- /**
53
- * Every FILE entry of a ZIP archive, in central-directory order. A directory
54
- * entry (a name ending in `/`) is not a file and is not returned. Throws
55
- * `ZipReadError`.
56
- */
41
+ /** Every file entry of a ZIP archive, in central-directory order (directory entries are skipped). Throws `ZipReadError`. */
57
42
  export declare function readZip(data: Uint8Array): ZipEntry[];
@@ -1,39 +1,31 @@
1
1
  /**
2
2
  * The one ZIP reader `case` uses, for a case file named on the command line.
3
3
  *
4
- * The TypeScript half of `src/sanning_proof/zip_reader.py`. Node has no ZIP
5
- * reader, and Python's `zipfile` is not used, so the two kernels read one
6
- * archive by one rule rather than by two libraries that disagree on the edges:
7
- * a central directory that contradicts a local header, a name that is not
8
- * UTF-8, data after the end of a deflate stream. Where either library would
9
- * have made a choice, this module refuses instead.
4
+ * Node has no ZIP reader. This one refuses where two libraries would disagree
5
+ * (evidence-bundle.md 5.6.1). It reads the central directory and each entry
6
+ * through its local header, stored and deflate only.
10
7
  *
11
- * What it reads (evidence-bundle.md 5.6.1): the central directory, and each
12
- * entry's data through its local header. Stored and deflate only. What it
13
- * REFUSES, as a whole archive: no end-of-central-directory record, ZIP64, more
14
- * than one disk, an entry the central directory places outside the archive, a
15
- * local header that does not match its central entry, a size or CRC-32 that
16
- * does not match the data, two entries with one name, a name that is not
17
- * UTF-8, more than MAX_ZIP_ENTRIES entries or MAX_ZIP_BYTES bytes in all, and
18
- * a name nested deeper than MAX_ZIP_DEPTH folders. What it REPORTS and never
19
- * reads, entry by entry: an encrypted entry, a symbolic link, a method other
20
- * than stored or deflate, and a name that is absolute, holds a backslash or a
21
- * NUL, or has an empty, `.` or `..` segment.
8
+ * It refuses the whole archive for: no end-of-central-directory record, ZIP64,
9
+ * more than one disk, an entry placed outside the archive, a local header that
10
+ * contradicts its central entry, a size or CRC-32 that does not match, two
11
+ * entries with one name, a name that is not UTF-8, more than MAX_ZIP_ENTRIES
12
+ * entries or MAX_ZIP_BYTES bytes, or a name nested deeper than MAX_ZIP_DEPTH.
13
+ * It reports without reading, per entry: an encrypted entry, a symbolic link,
14
+ * another method, and a name that is absolute, holds a backslash or NUL, or has
15
+ * an empty, `.` or `..` segment.
22
16
  *
23
- * Pure: bytes in, entries out. The caller turns them into the tree `case`
24
- * splits.
17
+ * Pure: bytes in, entries out.
25
18
  */
26
19
  import { inflateRawSync } from "node:zlib";
27
20
  import { utf8Strict } from "./verifier.js";
28
- /** More entries than this refuse the archive: the walk's own bound on names. */
21
+ /** More entries than this refuse the archive. */
29
22
  export const MAX_ZIP_ENTRIES = 50_000;
30
23
  /** More uncompressed bytes than this, across every entry, refuse the archive. */
31
24
  export const MAX_ZIP_BYTES = 2 ** 30;
32
25
  /**
33
26
  * An archive file larger than this is refused before it is read: the
34
- * uncompressed cap, and room for the headers, the central directory and the
35
- * deflate framing of an archive that holds that much. The caller checks the
36
- * size on disk, so the archive is never read whole to find out it is too big.
27
+ * uncompressed cap plus room for headers and framing. The caller checks the
28
+ * size on disk.
37
29
  */
38
30
  export const MAX_ZIP_ARCHIVE_BYTES = MAX_ZIP_BYTES + 256 * 2 ** 20;
39
31
  /** A name nested deeper than this many folders refuses the archive. */
@@ -44,7 +36,7 @@ const LOCAL = 0x04034b50;
44
36
  const S_IFMT = 0o170000;
45
37
  const S_IFLNK = 0o120000;
46
38
  const UNIX = 3;
47
- /** The archive cannot be read as a case file. `message` is the whole report, identical in both kernels. */
39
+ /** The archive cannot be read as a case file. `message` is the whole report. */
48
40
  export class ZipReadError extends Error {
49
41
  constructor(message) {
50
42
  super(message);
@@ -89,18 +81,13 @@ function sameBytes(a, b) {
89
81
  return false;
90
82
  return true;
91
83
  }
92
- /**
93
- * Every FILE entry of a ZIP archive, in central-directory order. A directory
94
- * entry (a name ending in `/`) is not a file and is not returned. Throws
95
- * `ZipReadError`.
96
- */
84
+ /** Every file entry of a ZIP archive, in central-directory order (directory entries are skipped). Throws `ZipReadError`. */
97
85
  export function readZip(data) {
98
86
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
99
87
  const n = data.length;
100
88
  const u32 = (at) => view.getUint32(at, true);
101
89
  const u16 = (at) => view.getUint16(at, true);
102
- // The end-of-central-directory record: 22 bytes plus a comment of at most
103
- // 65535, searched backwards so the LAST signature wins.
90
+ // 22 bytes plus a comment of at most 65535, searched backwards so the last signature wins.
104
91
  let eocd = -1;
105
92
  const lowest = Math.max(0, n - 22 - 0xffff);
106
93
  for (let at = n - 22; at >= lowest; at--) {
@@ -202,9 +189,7 @@ export function readZip(data) {
202
189
  body = packed;
203
190
  }
204
191
  else {
205
- // `info: true` hands back the engine, whose `bytesWritten` is how many
206
- // input bytes the stream consumed. Anything left over is data after the
207
- // end of the deflate stream, which Python's `unused_data` refuses too.
192
+ // Bytes left over after the end of the deflate stream are refused.
208
193
  let result;
209
194
  try {
210
195
  result = inflateRawSync(packed, {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sanning/proof",
3
- "version": "0.19.0",
4
- "description": "TypeScript kernel of the Sanning verification stack: RFC 8785 (JCS) canonicalization, SHA-256, and Ed25519 verification for Verifiable Event Envelopes. Full-family verifier (inline + external commitment); accepts sanning.agent/v1 and sanning.events/v2. Conformance-gated byte-for-byte against the test-vectors corpus.",
3
+ "version": "0.20.0",
4
+ "description": "TypeScript kernel of the Sanning verification stack: RFC 8785 (JCS) canonicalization, SHA-256, and Ed25519 verification for Verifiable Event Envelopes. Full-family verifier (inline + external commitment); accepts sanning.events/v2. Conformance-gated byte-for-byte against the test-vectors corpus.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
@@ -52,5 +52,13 @@
52
52
  "merkle",
53
53
  "rfc9162"
54
54
  ],
55
- "homepage": "https://docs.sanning.io"
55
+ "repository": {
56
+ "type": "git",
57
+ "url": "git+https://github.com/sanning-io/proof.git",
58
+ "directory": "ts"
59
+ },
60
+ "homepage": "https://github.com/sanning-io/proof#readme",
61
+ "bugs": {
62
+ "url": "https://github.com/sanning-io/proof/issues"
63
+ }
56
64
  }