Offline verification
A verifier that holds no socket, no path and no clock — it checks only what you hand it, and it never answers with a boolean.
The claim is that a complete result or refusal can be checked after every SCSA
service has been stopped. scsa-verify is the thing that makes the claim
checkable — by a customer, an auditor, or a regulator with no reason to trust the
people who built the system.
It is written as the adversary's tool, not ours. Three consequences follow, and they explain most of the design.
1. It cannot reach anything you did not hand it
verify takes a set of inputs, every field of which is a byte slice. There is
no path, no socket, no clock and no ambient configuration in the signature, so
a reviewer does not have to take a promise on faith — the type says what the
function can see.
Freshness is evaluated at the envelope's own decided_at, which is the only
defensible reference point. An auditor opening an archive in 2031 is not asking
whether the evidence is fresh today; they are asking whether it was fresh when the
decision was taken.
2. It never answers with a boolean
"Invalid" tells a relying party nothing they can act on. The verifier returns a verdict: a list of named claims, each one of
| Status | Meaning |
|---|---|
Verified | The claim was checked and holds. |
Failed | The claim was checked and does not hold — with the reason. |
Unevaluated | The object needed to check it was never supplied. |
The one confusion the crate exists to prevent is Unevaluated reading as
Verified. So there is no is_ok(), no boolean on the verdict, and the overall
summary cannot be Verified while any claim is unevaluated.
This matters more than it sounds. Most verification bugs in the wild are not "checked and got the wrong answer" — they are "never checked, and the caller assumed otherwise."
3. A refusal is a product, not an error
An envelope carrying Refused is verified exactly the way a completed one is, and
a clean result is reported as verified, flagged as a verified refusal.
"No permitted operation completes unilaterally" is only a claim a customer can check if the denial is portable evidence — and evidence a tool reports as a failure is not portable evidence.
A refusal is invited to name the object it refused over, because the naming form is the only one an operator can act on: it identifies which appraisal expired.
What gets checked
Given an envelope and whatever supporting objects you have:
- The envelope's canonical encoding round-trips byte for byte. If re-encoding the decoded object does not reproduce your input, the signature covers different bytes than you are holding.
- The signature verifies against the named authority, under the correct domain separation for its object type and schema version.
x_wrapandx_authare each bound to every field they claim to bind, and neither is presented in the other's place.- A
Completedoutcome carries a transparency inclusion proof, and the proof checks against the stated root at the stated size. - Consistency between checkpoints, where you supply two.
- The quorum actually met its threshold and its diversity floor, counting only execution shares — human approvals and attestations are not counted as shares.
- Evidence was inside its freshness window at
decided_at. - The constitution digest the decision was taken under is the one you expected.
Anything you did not supply comes back Unevaluated, by name, so you can see the
hole rather than infer its absence.
Getting the bytes
GET /v1/operations/{intent_digest}/bundlereturns a canonical EvidenceBundle in audit scope — everything needed to
check the decision, without seat contributions.
GET /v1/operations/{intent_digest}/recipient-bundlereturns the bundle including contributions, for the party the operation was performed for.
Archive the bundle. It is self-contained: verification years later needs the bundle and the verifier, and nothing else that is still running.
Cross-implementation status
Three implementations of the canonical encoding exist: the Rust reference, a TypeScript verifier, and a JavaScript implementation written from the published wire-format specification alone. The third reproduces 134 of 134 test vectors emitted by the reference — every object digest, digest construction, primitive, canonical-CBOR reject and specified ambiguity — with zero disagreements.
That establishes the specification is sufficient to implement against: an engineer with the spec and no access to the reference produces byte-identical results. It does not establish independence — all three implementations were written by the same team, so a shared misreading of RFC 8949 would be reproduced faithfully by all of them. Treat cross-implementation agreement with an outside party as still unproven.