@sanning/proof 0.7.0 → 0.8.1
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/dist/chain.js +48 -10
- package/dist/cli.js +25 -0
- package/dist/evidence.d.ts +50 -0
- package/dist/evidence.js +442 -91
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/chain.ts +48 -8
- package/src/cli.ts +29 -0
- package/src/evidence.ts +590 -97
- package/src/index.ts +4 -0
- package/vectors/README.md +18 -0
- package/vectors/stamp-on-chain.json +229 -0
package/dist/index.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ export { ACCEPTED_SPEC_MAJORS, contentHashes, jcs, specVersionSupported, verifyE
|
|
|
2
2
|
export { bytesToHex, deriveOperatorAddress, ed25519Verify, hexToBytes, sha256Bytes, sha256Hex, utf8, verifyRsaPssSha256, } from "./crypto.js";
|
|
3
3
|
export { EMPTY_TREE_ROOT_HEX, auditPath, leafHash, merkleRoot, nodeHash, verifyInclusion, } from "./merkle.js";
|
|
4
4
|
export type { ContentRole, Envelope, Subject, VerificationResult, VerifyOptions } from "./types.js";
|
|
5
|
-
export { ANCHOR_TRACE_BODY_TYPE, ANCHOR_TRACE_BODY_TYPE_V1, ANCHOR_TRACE_BODY_TYPES, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
|
|
6
|
-
export type { AnchorTraceBody, AttestationPayload, AttestationRecord, AttestationResult, CheckpointResult, ComposeVerdictResult, DisclosedContentResult, DisclosedEventContent, DisclosureStatus, EvidenceBundle, EvidenceBundleResult, EvidenceIssuer, EvidenceStatus, EvidenceVerdict, EventResult, ExportBody, ExportResult, OnChainOutcome, OnChainResult, PerGatewayOutcome, AnchorRef, StampBody, StampEntry, StampResult, StampSubtree, StampSubtreeResult, StampTraceBody, TraceCheckpoint, TraceCheckpointV1, TraceEvent, TraceInclusion, VerdictAttestation, VerdictCheckpoint, VerdictEvent, VerdictObject, VerdictOnChain, VerdictOnChainGateway, VerifyEvidenceOptions, } from "./evidence.js";
|
|
5
|
+
export { ANCHOR_TRACE_BODY_TYPE, ANCHOR_TRACE_BODY_TYPE_V1, ANCHOR_TRACE_BODY_TYPES, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, STAMP_EVIDENCE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
|
|
6
|
+
export type { AnchorTraceBody, AttestationPayload, AttestationRecord, AttestationResult, CheckpointResult, ComposeVerdictResult, DisclosedContentResult, DisclosedEventContent, DisclosureStatus, EvidenceBundle, EvidenceBundleResult, EvidenceIssuer, EvidenceStatus, EvidenceVerdict, EventResult, ExportBody, ExportResult, OnChainOutcome, OnChainResult, PerGatewayOutcome, AnchorRef, StampBody, StampEntry, StampResult, StampSubtree, StampSubtreeResult, StampTraceBody, StampEvidenceBody, StampEvidenceEvent, StampEvidenceInclusion, TraceCheckpoint, TraceCheckpointV1, TraceEvent, TraceInclusion, VerdictAttestation, VerdictCheckpoint, VerdictEvent, VerdictObject, VerdictOnChain, VerdictOnChainGateway, VerifyEvidenceOptions, } from "./evidence.js";
|
|
7
7
|
export { accountDisclosure, completenessNotes, gradeCauses, gradePack, renderCause, signedLeafCount, } from "./grading.js";
|
|
8
8
|
export type { CompletenessNote, DisclosureAccount, GradeInput, PackGrade, PackGradeCause, PackGradeCauseKind, PackVerdict, } from "./grading.js";
|
|
9
9
|
export { ADAPTER_CHAIN_NAMESPACES, CHAIN_READING_SCOPE, readChains, renderChainFinding, } from "./chain.js";
|
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ export { EMPTY_TREE_ROOT_HEX, auditPath, leafHash, merkleRoot, nodeHash, verifyI
|
|
|
34
34
|
// files that failed verification on genuine evidence. This hands back DECODED
|
|
35
35
|
// bytes, and only for events whose verification bound them to the committed
|
|
36
36
|
// content_hash. Additive; verifyEvidenceBundle's behavior is unchanged.
|
|
37
|
-
export { ANCHOR_TRACE_BODY_TYPE, ANCHOR_TRACE_BODY_TYPE_V1, ANCHOR_TRACE_BODY_TYPES, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
|
|
37
|
+
export { ANCHOR_TRACE_BODY_TYPE, ANCHOR_TRACE_BODY_TYPE_V1, ANCHOR_TRACE_BODY_TYPES, EXPORT_BODY_TYPE, STAMP_BODY_VERSION, STAMP_TRACE_BODY_TYPE, STAMP_EVIDENCE_BODY_TYPE, disclosedContent, recomputeExportVerdict, verifyEvidenceBundle, } from "./evidence.js";
|
|
38
38
|
// Pack grading (src/grading.ts): what a verification SURFACE may claim about a
|
|
39
39
|
// pack — the signed bundle PLUS whatever disclosed bytes travelled beside it.
|
|
40
40
|
//
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanning/proof",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
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, sanning.mlflow/v1, sanning.events/v1. Conformance-gated byte-for-byte against the test-vectors corpus.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
package/src/chain.ts
CHANGED
|
@@ -236,7 +236,15 @@ function adapterLinkage(
|
|
|
236
236
|
// here: nothing to resolve is a beginning.
|
|
237
237
|
const prev = str(fields.prev_event_id);
|
|
238
238
|
const seqRaw = fields.seq;
|
|
239
|
-
|
|
239
|
+
// SAFE integer, not merely integral. Beyond 2^53 a JSON number does not
|
|
240
|
+
// survive `JSON.parse` intact — `9007199254740993` arrives as
|
|
241
|
+
// `...992` — while Python's parser keeps it exactly, so the two kernels
|
|
242
|
+
// read DIFFERENT sequence numbers off identical bytes and report gaps of
|
|
243
|
+
// different widths. Measured at 2^53+1: a `chain-gap` count of
|
|
244
|
+
// 9007199254740990 here against 9007199254740992 there (proof#49). A value
|
|
245
|
+
// this kernel cannot represent is one it cannot read, which is the same
|
|
246
|
+
// answer it gives every other unreadable field: no sequence, take no part.
|
|
247
|
+
const seq = Number.isSafeInteger(seqRaw) ? (seqRaw as number) : null;
|
|
240
248
|
return { namespace, chainKey, prev, seq };
|
|
241
249
|
}
|
|
242
250
|
return null;
|
|
@@ -322,6 +330,20 @@ function byChainOrder(a: Member, b: Member): number {
|
|
|
322
330
|
return a.order - b.order;
|
|
323
331
|
}
|
|
324
332
|
|
|
333
|
+
/**
|
|
334
|
+
* The smallest slot between the lowest and highest occupied ones that no record
|
|
335
|
+
* occupies, found by walking the SORTED occupants rather than the range they
|
|
336
|
+
* span. See the call site: the range's width is chosen by whoever wrote the
|
|
337
|
+
* bundle.
|
|
338
|
+
*/
|
|
339
|
+
function firstMissingSlot(present: Set<number>): number | null {
|
|
340
|
+
const ordered = [...present].sort((a, b) => a - b);
|
|
341
|
+
for (let i = 0; i < ordered.length - 1; i++) {
|
|
342
|
+
if (ordered[i + 1]! > ordered[i]! + 1) return ordered[i]! + 1;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
|
|
325
347
|
function findingsFor(group: Group): ChainFinding[] {
|
|
326
348
|
const { members, chainKey, source, publicKey } = group;
|
|
327
349
|
const of = members.length;
|
|
@@ -383,16 +405,23 @@ function findingsFor(group: Group): ChainFinding[] {
|
|
|
383
405
|
const present = new Set(sequenced.map((m) => m.seq as number));
|
|
384
406
|
const low = Math.min(...present);
|
|
385
407
|
const high = Math.max(...present);
|
|
386
|
-
|
|
387
|
-
for (let s = low; s <=
|
|
388
|
-
|
|
389
|
-
|
|
408
|
+
// Counted, never walked. `seq` is attacker-chosen, so a bundle presenting
|
|
409
|
+
// two records at seq 0 and seq 1e15 made the old `for (let s = low; s <=
|
|
410
|
+
// high; s++)` spin for days — a verifier that HANGS on adversarial input is
|
|
411
|
+
// worse than one that raises, and this reader promises neither (proof#49).
|
|
412
|
+
// The present set is a subset of [low, high], so the absent slots are the
|
|
413
|
+
// width minus the occupants, and the first of them is found by walking the
|
|
414
|
+
// occupants in order.
|
|
415
|
+
const missingCount = high - low + 1 - present.size;
|
|
416
|
+
const firstMissing = missingCount > 0 ? firstMissingSlot(present) : null;
|
|
417
|
+
if (firstMissing !== null) {
|
|
418
|
+
const after = sequenced.filter((m) => (m.seq as number) > firstMissing).sort(byChainOrder)[0];
|
|
390
419
|
findings.push({
|
|
391
420
|
kind: "chain-gap",
|
|
392
421
|
...base,
|
|
393
|
-
count:
|
|
422
|
+
count: missingCount,
|
|
394
423
|
eventIds: after === undefined ? [] : [after.eventId],
|
|
395
|
-
firstMissingSeq:
|
|
424
|
+
firstMissingSeq: firstMissing,
|
|
396
425
|
});
|
|
397
426
|
}
|
|
398
427
|
}
|
|
@@ -411,7 +440,18 @@ function findingsFor(group: Group): ChainFinding[] {
|
|
|
411
440
|
export async function readChains(bundle: unknown): Promise<ChainReading> {
|
|
412
441
|
const groups = await groupChains(bundle);
|
|
413
442
|
const order = (g: Group) => `${g.source} ${g.namespace ?? ""} ${g.chainKey}`;
|
|
414
|
-
|
|
443
|
+
// Ordered by UTF-16 code unit, NOT `localeCompare`. A chain key is caller
|
|
444
|
+
// text, and `localeCompare` with no locale argument answers from the host's
|
|
445
|
+
// ICU collation: measured here, it puts "chain-2" AFTER "chain_1" where every
|
|
446
|
+
// code-wise comparison puts it before, and it can differ between two
|
|
447
|
+
// TypeScript installs, let alone between kernels. The Python reader sorts the
|
|
448
|
+
// same key encoded as `utf-16-be`, which is exactly this comparison — so the
|
|
449
|
+
// two kernels emit findings in one order (proof#49).
|
|
450
|
+
groups.sort((a, b) => {
|
|
451
|
+
const x = order(a);
|
|
452
|
+
const y = order(b);
|
|
453
|
+
return x < y ? -1 : x > y ? 1 : 0;
|
|
454
|
+
});
|
|
415
455
|
|
|
416
456
|
return {
|
|
417
457
|
chains: groups.map((g) => ({
|
package/src/cli.ts
CHANGED
|
@@ -697,6 +697,35 @@ function reportEvidence(
|
|
|
697
697
|
}
|
|
698
698
|
}
|
|
699
699
|
|
|
700
|
+
// Stamps (evidence-bundle.md §5.2 / §5.3). Printed rather than summarised,
|
|
701
|
+
// because for a stamp body the WITNESS is the entire independent part
|
|
702
|
+
// (§5.2.4) — and until now this reporter showed nothing about it at all, so a
|
|
703
|
+
// §5.3 pack printed a rollup line claiming an on-chain scope with no visible
|
|
704
|
+
// on-chain result anywhere above it.
|
|
705
|
+
//
|
|
706
|
+
// The on-chain column distinguishes all four states in the same three
|
|
707
|
+
// characters the checkpoint block uses, plus its absence: `✓` confirmed,
|
|
708
|
+
// `✗` mismatched, `~` asked and unreachable, and NO COLUMN AT ALL when
|
|
709
|
+
// nothing was checked. A reader must never have to infer "not checked" from
|
|
710
|
+
// a symbol that also means "could not be reached".
|
|
711
|
+
if (result.stamps && result.stamps.length > 0) {
|
|
712
|
+
io.out("");
|
|
713
|
+
io.out(paint(" Stamps", DIM));
|
|
714
|
+
for (const st of result.stamps) {
|
|
715
|
+
const witness =
|
|
716
|
+
st.anchorRef === null
|
|
717
|
+
? paint("~ no witness", Y)
|
|
718
|
+
: `witness ${st.anchorRef.kind}:${shortTx(st.anchorRef.id)}`;
|
|
719
|
+
const onChain =
|
|
720
|
+
st.onChain === null ? "" : ` on-chain ${mark(st.onChainOk)} (${st.onChain.rollup})`;
|
|
721
|
+
io.out(
|
|
722
|
+
` ${mark(st.ok)} ${shortTx(st.stampRef)} bytes ${mark(st.stampRefOk && st.bodyOk)}` +
|
|
723
|
+
` ranges ${mark(st.subtreesOk)} ${witness}${onChain}`,
|
|
724
|
+
);
|
|
725
|
+
for (const e of st.errors) io.out(paint(` - ${e}`, R));
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
700
729
|
if (result.events.length > 0) {
|
|
701
730
|
io.out("");
|
|
702
731
|
io.out(paint(" Events", DIM));
|