Commit Graph
4 Commits
Author SHA1 Message Date
CodexandClaude Opus 5 c31c1796ae feat(attesto3): let a verifier client say what it did not check
Sprint 12's own evidence named this the feature's largest risk: not a broken
proof, but a correct proof rendered as "AI generated: true". The SDKs had no
result surface at all — only cryptographic primitives — so nothing stopped a
consumer from reading a predicate result however it liked.

All three now carry `inspect_predicate_result`, and the rule that shapes it is
that a client without ristretto255 arithmetic cannot verify a range proof and
must say so. `verified_here.zk_predicate` is always `not_checked`; the issuer's
own verification block is carried separately under `reported_by_issuer`. An SDK
that merged the two would be passing the prover's word through as though it had
confirmed it, which is the failure the whole construction exists to prevent.
Inclusion, which is SHA-256, is reported as genuinely checked when the caller
checked it.

A result is refused outright if it omits one of the three non-claims or carries
a field a consumer could render as a verdict — at any nesting depth, since
`predicate.confidence` misleads exactly as well as a top-level one.

The corpus is generated by calling the real evaluator, so the fixture cannot
drift from the implementation, and a contract compares rather than regenerates.

Extending the coverage contract to a second corpus surfaced a third one:
`provenance-envelope-v0.1` matched nothing. It turned out to be guarded a
different but equally strict way — its own contract pins an explicit inventory —
so the contract now models both shapes. "Checked somewhere else" and "checked by
nobody" can no longer look the same, and a new corpus fails until one model or
the other covers it.

Python 100, Go ok, TypeScript 115, Local Vault 375.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 18:17:14 +02:00
CodexandClaude Opus 5 496d622671 feat(attesto3): make every SDK check every vector it is able to check
Twelve of twenty-five provenance vectors were consumed by no SDK. From outside
the repository that looked exactly like full coverage, which is the problem: a
corpus proves nothing about an implementation that never loads it.

Vectors now declare what they require. `sha256` vectors use SHA-256 and
canonical JSON, which all three SDKs have, so an unconsumed one is a gap and
fails a contract. `ristretto255` vectors need curve scalar arithmetic no SDK
carries; those are a declared boundary with a stated reason rather than a silent
skip, so the exemption cannot spread by habit.

Coverage went from 13/17 reachable in each SDK to 17/17. Closing the four gaps
surfaced a real verifier weakness: `capsule_root` receives digests, so by then a
role is no longer visible, and a tree carrying `evidence_root` twice with
`vault_identity_commitment` missing folds to a root all three SDKs accepted.
Each gains `ordered_top_leaf_digests`, which requires each of the six roles
exactly once, and the safe path is now the easy one.

Two findings of my own drift:

* The Go corpus-typing test accepted only `valid` and `invalid`, so it had been
  failing since the Sprint 1 recovery added vectors carrying `differs` and
  `rejected`. I updated Python's typing test then and not Go's, and no gate
  caught it because the SDK parity suites are not in the sprint gates. Fixed,
  and both Go and TypeScript now also require the capability declaration.
* TypeScript's strict indexing caught that a missing randomizer would have
  reached the hash as the string "undefined". Both halves are now checked.

Python 88, Go ok, TypeScript 107, Local Vault 375, edge 116, backend 1404.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 16:36:13 +02:00
CodexandClaude Opus 5 f80b28c3b5 feat(attesto3): register the REVIEW-02 disclosure v2 and ZK range domains
Sprint 1 recovery, first item. §5.2 of REVIEW-02 adds three domains the registry
did not carry: attesto.disclosure.v2, attesto.zk.range.v1.statement and
attesto.zk.range.v1.transcript.

They are not in the attesto.provenance.v1. namespace, and that is deliberate:
disclosure v2 and the ZK range protocol are separate protocols with their own
versions, so a preimage space is named after the protocol that owns it rather
than the one it happens to travel with.

That namespace difference meant the parity contract could not see them at all —
its pattern matched attesto.provenance.v1.* only, so three new domains would have
been silently unguarded. The pattern now names each protocol explicitly rather
than loosening to a prefix wildcard: a looser first attempt also matched prose
that mentions a namespace without a terminal segment and reported it as an
unknown domain.

All four registry locations updated together with the golden vector, and all
three SDK parity suites plus the contract are green. The Go failure message was
also corrected: it printed "rust=21 go=21" while failing on a third hardcoded
expectation it never named.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-21 10:01:24 +02:00
CodexandClaude Fable 5 9e6ae6277a feat(attesto3): Sprint 1 — pinned attesto-edge core + 3-language parity
Establishes the single normative cryptographic authority for the provenance
lane, and freezes the boundary and Merkle semantics before any ingestion path
exists to depend on them.

New crate edge/ (attesto-edge)
- domains.rs — the closed 18-domain v1 registry. Unknown domains are errors,
  never a fallback: a generic attesto.provenance.v1.commitment would let two
  unrelated objects share a preimage space, which is what domain separation
  exists to prevent.
- canonical.rs — conforming ATTESTO-CANONICAL-JSON-001, not a second
  serializer. Floats and integers past 2^53-1 are refused with their JSON path.
- commitment.rs — randomized, domain-separated commitments. Legacy Proofstream
  commitments stay deterministic; provenance values are low-entropy, so
  claim_type = "c2pa_manifest_valid" hashed deterministically is a dictionary
  lookup and a deterministic asset digest links a file across events. Debug for
  Randomizer prints <redacted>: it is C1 and Debug output reaches logs.
- merkle.rs — the two-level capsule forest. A claim leaf cannot verify against
  evidence_root on two independent grounds: subtrees fold under different node
  domains, and the top leaf binds leaf_role. Odd nodes are promoted, never
  duplicated, matching the rule inclusion.json already pins for Proofstream.
- boundary.rs — derives nothing. It shapes a request for attesto-nova, reusing
  the existing event-payload 16 KiB size class so Nova's closed
  boundary_max_len() allowlist needs no new entry. On-wire artifact is N10.R
  redacted.
- main.rs — NDJSON surface (handshake, canonicalize, commit, capsule-root,
  boundary-derive, self-test), the transport the backend already speaks.

Poseidon is deliberately absent. It stays in proofs/nova, reached through that
crate's public boundary API, so there remains exactly one Poseidon authority.
The only Nova change is making CIRCUIT_ID and PROVER_VERSION pub so the edge
handshake can report the prover it wraps; its 40 tests are unchanged.

Test-only randomizers are gated behind the `test-vectors` cargo feature and
compiled out of release builds. A caller who can choose the randomizer can make
production commitments deterministic — that is not a debug convenience, it is
the vulnerability. A release build refuses one and reports
accepts_caller_randomizers: false in its handshake.

Conformance
- golden-vectors/provenance-v0.1-dev/ — 14 Rust-authored vectors, 9 valid and 5
  invalid. CI regenerates them and requires git diff --exit-code, so the
  committed corpus cannot drift from what the normative core produces.
- Python (sdk/python/src/attesto/provenance.py, 19 tests) and Go
  (sdk/go/provenance.go, 14 tests) reproduce every valid vector and refuse every
  invalid one. Both reuse their existing canonical-JSON primitives rather than
  forking a second implementation.
- provenance_domain_registry_contract.py pins Rust = spec = Python = Go =
  vector, and that no registry declares the forbidden fallback. It reads each
  declaration block rather than whole files, so the negative test cases that
  must name the fallback do not trip it.

TypeScript parity is still owed and Sprint 1's DoD is not fully closed: the
sdk/typescript build break recorded in the Sprint 0 baseline makes its whole
suite unrunnable.

Also fixes a Sprint 0 guard found by the guard itself: the naming lint scanned
only tracked files, so new work read green until it was committed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 13:58:00 +02:00