@sanning/proof 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/case-file.js +2 -1
- package/dist/case.js +11 -4
- package/dist/chain.d.ts +28 -0
- package/dist/chain.js +61 -4
- package/dist/cli.js +226 -33
- package/dist/crypto.d.ts +17 -0
- package/dist/crypto.js +116 -16
- package/dist/declaration.d.ts +43 -4
- package/dist/declaration.js +198 -14
- package/dist/environment.d.ts +42 -0
- package/dist/environment.js +136 -0
- package/dist/evidence.d.ts +6 -0
- package/dist/evidence.js +32 -1
- package/dist/exit-codes.d.ts +18 -0
- package/dist/exit-codes.js +25 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +21 -5
- package/dist/run-adapters.d.ts +10 -0
- package/dist/run-adapters.js +35 -0
- package/dist/run.d.ts +209 -0
- package/dist/run.js +1405 -0
- package/dist/verifier.d.ts +43 -0
- package/dist/verifier.js +113 -6
- package/dist/witness-lookup.d.ts +13 -1
- package/dist/witness-lookup.js +12 -2
- package/package.json +2 -2
- package/src/case-file.ts +2 -1
- package/src/case.ts +11 -4
- package/src/chain.ts +81 -4
- package/src/cli.ts +259 -28
- package/src/crypto.ts +125 -16
- package/src/declaration.ts +242 -12
- package/src/environment.ts +145 -0
- package/src/evidence.ts +29 -1
- package/src/exit-codes.ts +28 -0
- package/src/index.ts +31 -1
- package/src/run-adapters.ts +44 -0
- package/src/run.ts +1598 -0
- package/src/verifier.ts +102 -5
- package/src/witness-lookup.ts +18 -1
- package/vectors/README.md +37 -0
- package/vectors/sandbox-evidence.json +3161 -0
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ npx @sanning/proof verify trace-bundle.json
|
|
|
63
63
|
npx @sanning/proof verify trace-bundle.json --gateways https://arweave.net,https://permagate.io
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
It prints a per-event verdict, then the PACK verdict, and exits by the one table every verb uses: `0` verified · `1` a real failure (bad signature / tamper / broken inclusion) · `2` usage or malformed · `3` undetermined (`partial`, `pending`, `incomplete` or `not_found`: a gateway that could not be reached, a stamp no witness published, or a pack the grading rule calls `incomplete`) · `4` mixed
|
|
66
|
+
It prints a per-event verdict, then the PACK verdict, and exits by the one table every verb uses: `0` verified · `1` a real failure (bad signature / tamper / broken inclusion) · `2` usage or malformed · `3` undetermined (`partial`, `pending`, `incomplete` or `not_found`: a gateway that could not be reached, a stamp no witness published, or a pack the grading rule calls `incomplete`) · `4` mixed · `5` sandbox: every check passed, and a record is signed `environment: "dev"`. Since 0.13.0 `partial` exits `3` whatever caused it; it exited `0` unless a gateway was unreachable. A pack holding a record signed `dev` never prints `VERIFIED`: the headline is `SANDBOX`, the line `Sandbox evidence (environment dev): not production evidence` follows it, `--json` carries `environment_marker` (`dev` or `mixed`), and a run that would exit `0` exits `5`. So does a pack whose records are all `dev` whose only unchecked witness, with `--gateways`, is `simulated`; a failure is still `1`, and a production or mixed pack with a simulated witness is still `3`. A production pack prints what it did before. The headline is the pack verdict, the same one `gradePack` gives the browser verify page: a record nobody hashed leaves the pack incomplete even when the signed bundle itself is `verified`, and both verdicts are printed when they differ. Only `arweave` witnesses are re-fetched: a checkpoint or stamp whose `anchor_ref.kind` is anything else is not checked, and with gateways passed the run answers `partial`, exit `3`. A sandbox plane's `simulated` witness is never fetched and never counted as checked on a chain: where it is the only witness, the verdict line says nothing was looked up and `--json` writes `"on_chain_checked": false`. The producer's asserted `verdict` is shown but **never trusted**, the displayed verdict is always recomputed from the body. The same CLI also verifies the agent's `sanning.agent.proof/v1` inclusion bundle (it sniffs `spec_version`), so one command covers anchor + agent.
|
|
67
67
|
|
|
68
68
|
> The `verify` CLI is live in `@sanning/proof` ≥ 0.2.2, `npx @sanning/proof verify <bundle> [--gateways <url,url>]` (the gateway list was a second positional argument until 0.13.0). `verifyEvidenceBundle` is the programmatic API.
|
|
69
69
|
|
|
@@ -226,7 +226,7 @@ The walk is bounded and follows no link. A symbolic link is never followed: it i
|
|
|
226
226
|
|
|
227
227
|
Known differences from the verify page: the page opens a dropped `.zip`; the page drops a leading byte-order mark from `bundle.json`, which `verify`'s reader refuses; the page reads whatever the browser hands it for a link, a bad name or a very deep tree; and the page loses a record named `__proto__`, which this command keeps.
|
|
228
228
|
|
|
229
|
-
The first line is the case verdict, then the kernel's summary and every exception through `renderException`, then the packs, the files outside them, and the on-chain half. Exit codes: `0` verified · `1` failed · `2` malformed, usage, an input that cannot be read as a case, or an unexpected error in the command · `3` incomplete or partial · `4` mixed (more than one outcome: read the exceptions). This is the one table every verb uses (`EXIT_CODES`, exported from the package root), and `verify` exits `3` on `partial` since 0.13.0.
|
|
229
|
+
The first line is the case verdict, then the kernel's summary and every exception through `renderException`, then the packs, the files outside them, and the on-chain half. Exit codes: `0` verified · `1` failed · `2` malformed, usage, an input that cannot be read as a case, or an unexpected error in the command · `3` incomplete or partial · `4` mixed (more than one outcome: read the exceptions) · `5` sandbox (it would be `0`, and a pack holds a record signed `environment: "dev"`, or every record is `dev` and only simulated witnesses are unchecked; the title is then `Sandbox`, never `Verified`). This is the one table every verb uses (`EXIT_CODES`, exported from the package root), and `verify` exits `3` on `partial` since 0.13.0.
|
|
230
230
|
|
|
231
231
|
A case reading establishes that every pack verified offline, that every record every pack supplied was read in one pile under envelope-spec §4.1's hand-off rules, and that a hand-off record with no counterpart is always named. It does **not** establish that the case is complete, that a party which presented no pack under the named key recorded nothing, that two organizations are independent, or that any witness was re-fetched on chain: the output says `on-chain NOT CHECKED` unless you pass `--gateways <url,url>`. With gateways, each pack's witnesses are re-fetched pack by pack and reported beside the reading, which they never change; a witness that does not match exits `1`, and an on-chain half that could not be settled turns `0` into `3`.
|
|
232
232
|
|
package/dist/case-file.js
CHANGED
|
@@ -82,7 +82,8 @@ export function howToVerifyText(name) {
|
|
|
82
82
|
` sanning-proof case ${name}\n` +
|
|
83
83
|
"\n" +
|
|
84
84
|
"Exit codes: 0 verified, 1 failed, 2 not readable or a usage error,\n" +
|
|
85
|
-
"3 could not be settled (partial, pending, incomplete, not found), 4 mixed
|
|
85
|
+
"3 could not be settled (partial, pending, incomplete, not found), 4 mixed,\n" +
|
|
86
|
+
"5 sandbox (records signed environment dev, not production evidence).\n" +
|
|
86
87
|
"\n" +
|
|
87
88
|
"To also look each witness up on the public record, add --gateways <url,url>.\n" +
|
|
88
89
|
"The lookup is reported on its own line and never changes the verdict word.\n" +
|
package/dist/case.js
CHANGED
|
@@ -241,7 +241,8 @@
|
|
|
241
241
|
* sequence. Narrowing any of it here with `handoffs[]` is precisely the move
|
|
242
242
|
* that would reopen proof#137's door.
|
|
243
243
|
*/
|
|
244
|
-
import { DECLARATION_VERSION,
|
|
244
|
+
import { DECLARATION_VERSION, DECLARATION_VERSIONS, checkDeclaration, declarationClaims, packRecords, } from "./declaration.js";
|
|
245
|
+
import { readChainLinks } from "./chain.js";
|
|
245
246
|
import { NOT_A_HANDOFF_FINDING, normalizedSigningKey, readHandoffs, recordIsMalformed, } from "./handoff.js";
|
|
246
247
|
import { coSignaturesPresent, resolveTraceEvents, verifyEvidenceBundle } from "./evidence.js";
|
|
247
248
|
import { accountDisclosure, completenessNotes, gradePack, } from "./grading.js";
|
|
@@ -1152,10 +1153,14 @@ export async function readCase(packs) {
|
|
|
1152
1153
|
grade = signed.status === "verified" ? "malformed" : signed.status;
|
|
1153
1154
|
}
|
|
1154
1155
|
else {
|
|
1155
|
-
|
|
1156
|
+
// Rule 11 reads the chain links of the same pack, and the reader reads
|
|
1157
|
+
// only the signed body, so the bare check below takes the same reading
|
|
1158
|
+
// (evidence-bundle.md 5.4.2 v1.36, A45).
|
|
1159
|
+
const chainLinks = await readChainLinks(pack.bundle);
|
|
1160
|
+
check = checkDeclaration(pack.bundle, pack.content, chainLinks);
|
|
1156
1161
|
const completeness = completenessNotes(pack.bundle);
|
|
1157
1162
|
full = gradePack(verified, accountDisclosure(pack.bundle, pack.content ?? {}), completeness, check);
|
|
1158
|
-
const bareGrade = gradePack(signed, accountDisclosure(pack.bundle, {}), completeness, checkDeclaration(pack.bundle, null));
|
|
1163
|
+
const bareGrade = gradePack(signed, accountDisclosure(pack.bundle, {}), completeness, checkDeclaration(pack.bundle, null, chainLinks));
|
|
1159
1164
|
// NO `failed` HERE RESTS ON BYTES NOBODY SIGNED. The signed grade decides
|
|
1160
1165
|
// where it is `failed` or `malformed`; a failure only the side map
|
|
1161
1166
|
// produced is `incomplete`; otherwise the full grade.
|
|
@@ -1619,7 +1624,9 @@ function declarationVersionOf(bundle) {
|
|
|
1619
1624
|
return null;
|
|
1620
1625
|
}
|
|
1621
1626
|
const version = declaration["declaration_version"];
|
|
1622
|
-
return
|
|
1627
|
+
return typeof version === "string" && DECLARATION_VERSIONS.includes(version)
|
|
1628
|
+
? version
|
|
1629
|
+
: null;
|
|
1623
1630
|
}
|
|
1624
1631
|
// ---- Presentation (optional) ------------------------------------------------
|
|
1625
1632
|
/**
|
package/dist/chain.d.ts
CHANGED
|
@@ -79,6 +79,34 @@ export interface ChainReading {
|
|
|
79
79
|
chains: ChainSummary[];
|
|
80
80
|
findings: ChainFinding[];
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* One record's place in a chain, as the reader grouped it, for a check that
|
|
84
|
+
* needs the links themselves rather than the findings: rule 11 of
|
|
85
|
+
* evidence-bundle.md 5.4.2, which holds a claimed chain to every link. The
|
|
86
|
+
* twin of Python's `ChainLink`.
|
|
87
|
+
*/
|
|
88
|
+
export interface ChainLink {
|
|
89
|
+
eventId: string;
|
|
90
|
+
publicKey: string;
|
|
91
|
+
chainKey: string;
|
|
92
|
+
source: ChainSource;
|
|
93
|
+
namespace?: string;
|
|
94
|
+
/** The SHA-256 of the record's committed bytes, or its event id for an adapter chain. */
|
|
95
|
+
selfId: string;
|
|
96
|
+
/** The pointer the record carries, or `null` where it begins a chain. */
|
|
97
|
+
prev: string | null;
|
|
98
|
+
seq: number | null;
|
|
99
|
+
/** Position in `body.events`. */
|
|
100
|
+
order: number;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Every record the chain reader places in a chain, in bundle order, with the
|
|
104
|
+
* identity and pointer it read: the grouping `readChains` reads, over every
|
|
105
|
+
* record an envelope binds, `event_bytes` or the inline `payload` where no
|
|
106
|
+
* `event_bytes` travel, with the identity the SHA-256 of those bytes, exactly
|
|
107
|
+
* as the envelope verifier binds `payload_hash`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function readChainLinks(bundle: unknown): Promise<ChainLink[]>;
|
|
82
110
|
/**
|
|
83
111
|
* Follow every chain the bundle's records describe, and report the links that
|
|
84
112
|
* do not resolve within it.
|
package/dist/chain.js
CHANGED
|
@@ -49,12 +49,13 @@
|
|
|
49
49
|
//
|
|
50
50
|
// (2) is a transitional accommodation, not the design. `metadata` is
|
|
51
51
|
// caller-owned and the profile says a verifier MUST NOT read it for
|
|
52
|
-
// verification — which is exactly why nothing
|
|
52
|
+
// verification — which is exactly why nothing read through it feeds a
|
|
53
|
+
// verdict, a claimed chain included (rule 11 reads linkage (1) only). The fix is
|
|
53
54
|
// for every adapter to set the record's own `previous_hash` (filed separately
|
|
54
55
|
// against `anchor`); when it lands, the namespace table below stops mattering
|
|
55
56
|
// and can go.
|
|
56
57
|
import { hexToBytes, sha256Hex } from "./crypto.js";
|
|
57
|
-
import { jcsParse } from "./verifier.js";
|
|
58
|
+
import { inlinePayloadBytes, jcsParse } from "./verifier.js";
|
|
58
59
|
import { resolveTraceEvents } from "./evidence.js";
|
|
59
60
|
/**
|
|
60
61
|
* The one sentence a surface should print beside these findings, so the reader
|
|
@@ -186,7 +187,33 @@ function adapterLinkage(record) {
|
|
|
186
187
|
* nothing and means a relabelled record DANGLES instead of quietly resolving.
|
|
187
188
|
* That is why this function is async.
|
|
188
189
|
*/
|
|
189
|
-
|
|
190
|
+
/**
|
|
191
|
+
* The record an envelope binds, for rule 11's links: `event_bytes` where the
|
|
192
|
+
* entry carries them, else the inline `payload` read through
|
|
193
|
+
* `inlinePayloadBytes`, the bytes `payload_hash` binds. Its identity is the
|
|
194
|
+
* SHA-256 of whichever bytes it is read from. Python's `_bound_record`.
|
|
195
|
+
*/
|
|
196
|
+
function boundRecord(entry) {
|
|
197
|
+
const eventBytes = entry?.event_bytes;
|
|
198
|
+
if (typeof eventBytes === "string" && eventBytes.length > 0)
|
|
199
|
+
return decodeRecord(eventBytes);
|
|
200
|
+
const envelope = entry?.envelope;
|
|
201
|
+
if (typeof envelope !== "object" || envelope === null || Array.isArray(envelope))
|
|
202
|
+
return null;
|
|
203
|
+
if (!("payload" in envelope))
|
|
204
|
+
return null;
|
|
205
|
+
try {
|
|
206
|
+
const bytes = inlinePayloadBytes(envelope);
|
|
207
|
+
const record = jcsParse(bytes);
|
|
208
|
+
if (record === null || typeof record !== "object" || Array.isArray(record))
|
|
209
|
+
return null;
|
|
210
|
+
return { record, bytes };
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function groupChains(bundle, inline = false) {
|
|
190
217
|
const groups = new Map();
|
|
191
218
|
const events = eventsOf(bundle);
|
|
192
219
|
for (let order = 0; order < events.length; order++) {
|
|
@@ -197,7 +224,11 @@ async function groupChains(bundle) {
|
|
|
197
224
|
// event id an adapter pointer has nothing to resolve against.
|
|
198
225
|
if (eventId === null || publicKey === null)
|
|
199
226
|
continue;
|
|
200
|
-
|
|
227
|
+
// `inline` (rule 11's links only): a record carried as the envelope's
|
|
228
|
+
// inline `payload` with no `event_bytes` is read too. The chain reading
|
|
229
|
+
// itself keeps reading `event_bytes` alone, so its findings and its rule
|
|
230
|
+
// table do not move.
|
|
231
|
+
const decoded = inline ? boundRecord(entry) : decodeRecord(entry?.event_bytes);
|
|
201
232
|
if (decoded === null)
|
|
202
233
|
continue;
|
|
203
234
|
const { record, bytes } = decoded;
|
|
@@ -346,6 +377,32 @@ function findingsFor(group) {
|
|
|
346
377
|
}
|
|
347
378
|
return findings;
|
|
348
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Every record the chain reader places in a chain, in bundle order, with the
|
|
382
|
+
* identity and pointer it read: the grouping `readChains` reads, over every
|
|
383
|
+
* record an envelope binds, `event_bytes` or the inline `payload` where no
|
|
384
|
+
* `event_bytes` travel, with the identity the SHA-256 of those bytes, exactly
|
|
385
|
+
* as the envelope verifier binds `payload_hash`.
|
|
386
|
+
*/
|
|
387
|
+
export async function readChainLinks(bundle) {
|
|
388
|
+
const links = [];
|
|
389
|
+
for (const g of await groupChains(bundle, true)) {
|
|
390
|
+
for (const m of g.members) {
|
|
391
|
+
links.push({
|
|
392
|
+
eventId: m.eventId,
|
|
393
|
+
publicKey: g.publicKey,
|
|
394
|
+
chainKey: g.chainKey,
|
|
395
|
+
source: g.source,
|
|
396
|
+
...(g.namespace !== undefined ? { namespace: g.namespace } : {}),
|
|
397
|
+
selfId: m.selfId,
|
|
398
|
+
prev: m.prev,
|
|
399
|
+
seq: m.seq,
|
|
400
|
+
order: m.order,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return links.sort((a, b) => a.order - b.order);
|
|
405
|
+
}
|
|
349
406
|
/**
|
|
350
407
|
* Follow every chain the bundle's records describe, and report the links that
|
|
351
408
|
* do not resolve within it.
|