Skip to content
Evidence Formats SCEV/1.0 No real unit recorded yet 31 Aug 2026
Sovereign Circuit · Evidence formats

Five artifacts. Every byte accounted for.

An evidence claim you cannot re-derive is a story. This page specifies the five artifacts this program produces, byte layout and domain tag for each, and the exact command that recomputes them from the artifact alone — no server, no account, no trust in whoever handed you the file. If a recomputation does not match, the artifact is void. That is the only authority any of these have.

Artifacts
5
Hash
SHA-256
Canonical form
RFC 8785
Domain tags
6
1 · Why formats, not a database

The artifact has to survive the company that made it.

The usual way to record a hardware transaction is a row in someone's system. That row is true for exactly as long as the system exists, the account works, and nobody with write access wants it to say something else. It cannot be checked by the counterparty, cannot be checked in five years, and cannot be checked at all if the company is gone.

So the record here is a file, and the file carries its own proof. Everything is SHA-256 over a canonically serialised body with a domain-separation tag in front of it. Canonical serialisation is RFC 8785 JSON Canonicalization Scheme, so that two implementations in two languages produce the same bytes for the same object and therefore the same hash (RFC 8785). The hash is FIPS 180-4 SHA-256 (FIPS 180-4).

Why the tag matters

Every hash in this program is SHA256(tag || JCS(body)), never SHA256(JCS(body)). Without a tag, a body that happens to be valid in two contexts produces one hash that means two things, and an attacker can present a value computed for one purpose as if it were computed for another. The tag makes each hash usable in exactly one place. There are six of them and they are listed in full below.

Domain separation tags
TagCoversDefined in
SCGR1|rubric|the grading source itself — pins which rubric a grade came fromgrading.py
SCGR1|obs|one party's observation sheet plus its hiding noncestation.py
SCGR1|grade|the derived grade bound to the rubric and the merged sheetgrading.py
SCDA1|commit|a sealed price with its round, unit, party and roleattestation.py
SCDA1|record|one entry in the append-only chainattestation.py
SCST1|policy|the settlement policy, frozen before any revealstation.py
2 · The shape of all five

One rule, applied five times.

# every artifact in this program
digest = SHA256( TAG || JCS(body) )

# JCS: keys sorted by UTF-16 code unit, no whitespace,
# integers only — no floats anywhere in any body
JCS({"b":2,"a":1})  ->  {"a":1,"b":2}

Two constraints do more work than they look like they should. No floats: every quantity is an integer — cents, basis points, percentage points, counts — because floating-point serialisation is not reproducible across languages and a canonical form that is not reproducible is not canonical. No optional fields: a body either has a key or the artifact is malformed, so two implementations cannot disagree about whether an absent key means false or means unknown.

Ordering is the third constraint and it is the one enforced at runtime rather than in the format. Artifacts 1 and 2 must both exist before artifact 3 can be created at all, and the test station lets you try to violate that and watch it refuse.

3 · The five formats

Layout, tag, and the command that checks it.

01

Observation sheet

SCGR1|obs|

What one person, or one script, saw on one machine. Sixty-six fields, all of them counts against a defined threshold or readings taken off the hardware. Each party produces one and seals it before either can see the other's — that is the commitment in phase one of the protocol.

obs      = 66 fields — 37 bool, 23 int, 4 enum, 1 list, 1 str
nonce    = 32 random bytes, hex   # hiding: without it a sheet is guessable
rubric   = "91ffd026…a336f79"    # SHA256(SCGR1|rubric| || grading source)

sheet_hash = SHA256( "SCGR1|obs|" || JCS({obs, nonce, rubric}) )

The rubric field inside the sheet is what stops a sheet from being replayed under a different rubric version. Change one threshold in grading.py and every previously sealed sheet stops verifying against the new one, loudly, which is the correct behaviour.

The nonce is not decoration. A used-laptop observation sheet has a small enough space of plausible values that an adversary holding only the hash could enumerate it. Thirty-two bytes from a CSPRNG makes that infeasible (NIST SP 800-90A r1).

Produced byeach party, independently, before reveal
Verified byStation.reveal_sheet — recomputes and compares in constant time
Voids ifthe revealed sheet does not reproduce the sealed hash
Statusformat fixed; no sheet from a real unit exists yet
02

Grade binding

SCGR1|grade|

The artifact that makes the grade a fact rather than an opinion. It binds three things that cannot afterwards be separated: which rubric was used, which merged observation sheet it was applied to, and what came out.

grade    = 11 keys exactly — f, c, s, tier, code, defect_load,
             battery_health_pct, f_label, c_label, s_label, tier_label
rubric   = rubric_hash
merged   = the conservative merge of both sheets

grade_token = SHA256( "SCGR1|grade|" || JCS({grade, rubric, merged}) )

Exactly eleven keys. Anything the interface adds for display — the human-readable reasons a grade came out the way it did, for instance — is outside the hashed body. A display field that leaks into the token would make two honest implementations produce different tokens for the same grade, and the browser grader on this site fails its own self-check if that ever happens.

The merge is conservative: pointwise worst of the two sheets. Where the parties disagree, the resulting grade is never better than either party observed. That is a deliberate asymmetry — it means the sealed-sheet mechanism cannot be gamed by optimistic reporting, only by pessimistic reporting, which costs the reporter money.

Produced byStation.publish_grade, after both reveals reconcile
Verified bygrading.py — re-derive from the two sheets, re-hash, compare
Voids ifmachine-read fields conflicted, or a judgement call moved the grade
Statuspinned by 10 published grading vectors and 4 reconciliation vectors
03

Settlement record

SCDA1|commit|

Two sealed prices and the arithmetic that turns them into one number. Each side commits first; neither can move after seeing the other. The commitment body carries enough context that it cannot be lifted into a different round, a different unit, or attributed to the other party.

protocol      = "sovereign-circuit/dual-attestation/1"
round_id      # this round only
asset_id      # this unit only
party_id      # this party only
role          = "holder" | "inspector"
amount_cents  # integer. never a float. never a string.
grade         # the published grade_token — binds price to condition
nonce         = 32 bytes, hex

commitment = SHA256( "SCDA1|commit|" || JCS(body) )

Five of those eight fields exist only to prevent replay. Strip round_id and a commitment from last month is valid today; strip role and either side can claim the other's number; strip grade and the whole ordering guarantee collapses, because a price sealed before the grade existed would still verify.

Settlement is then a pure function of the two amounts and a policy hash frozen before either reveal. Inside the tolerance band it is the midpoint, with a bonus when both sides independently verified condition. Outside the band it escalates to a published index rather than to negotiation. All rounding is half-up on integer cents, and the divisor for the spread is the lower of the two amounts, so the spread is measured against the more conservative valuation.

Produced byboth parties in phase 2, after the grade is published
Verified byverify_reveal — constant-time comparison against the published commitment
Pinned by8 published settlement vectors, replayed in your browser on the station page
Statusarithmetic verified; the policy numbers themselves are uncalibrated
04

Custody log

SCDA1|record|

An append-only chain covering everything that happened to a unit: received, graded, wiped, configured, placed, failed, replaced, recovered. Each entry commits to the previous entry's hash, so an entry cannot be inserted, removed or reordered without changing every hash after it.

prev  # entry_hash of the previous entry; genesis for the first
seq   # 0, 1, 2, … — checked against position, not trusted
body  # the event itself

entry_hash = SHA256( "SCDA1|record|" || JCS({prev, seq, body}) )

Verification walks the chain from genesis and checks three things per entry: that prev equals the running head, that seq equals the index, and that the recomputed hash matches the stored one. Any single failure fails the whole chain — there is no partial credit, because a log that is trustworthy up to entry forty is not a trustworthy log.

This is the structure behind every append-only transparency log, and the honest thing to say about the version here is what it does not yet have. There is no third-party timestamp, so the chain proves order but not date (RFC 3161 is the intended remedy). There is no Merkle inclusion proof, so a verifier must hold the whole chain rather than a proof about one entry (RFC 6962 is the intended remedy). Both are named gaps, not oversights.

Produced byChain.append, once per custody event
Verified byChain.verify — full walk from genesis
Missingtrusted timestamps; Merkle inclusion proofs; external witness
Statusimplemented and tested; no chain for a real unit exists yet
05

Evidence pack

SNEC/1.0 → SNEV/1.0

The one artifact that is not about a price. An evidence pack is what a security claim looks like when it has to be checkable: a collector runs on the machine, captures the specific state that would falsify the claim, and writes it out with a manifest. A separate verifier — written to be run by someone who does not trust the collector — reads the pack and reports pass or fail per check.

pack/
  manifest.json     # check ids, per-check verdict, collector version, hashes
  host/            # identity, OS build, patch level
  network/         # interfaces, routes, listeners, egress reachability
  service/         # bind addresses — the air-gap claim lives or dies here
  storage/         # wipe standard + certificate id, SMART readings
  tailnet/         # device approval, lock state, ACL grants in force

The verifier is deliberately a separate program by a separate path. A collector that reports its own success is a log, not evidence. This split is also how the false positive described on the doctrine page was found: the verifier was itself tested, one check was flagging a healthy configuration as a failure because of how a command formats its output, and a verifier that cries wolf is worse than no verifier at all.

Two rules are hard-coded because they are the two ways a private network accidentally becomes a public one: a service is never bound to 0.0.0.0, and the tailnet is never exposed with funnel (Tailscale Funnel — which is exactly the feature that makes a node reachable from the open internet). A pack that shows either fails, and no amount of other passing checks compensates.

Produced bythe collector, on the machine making the claim
Verified bya separate verifier — 56 checks currently passing against synthetic packs
Wipe standardNIST SP 800-88r2 clear / purge, with certificate id
Statusverifier tested; the collector has not yet run on a real node
9 · Verify one yourself

Nothing here needs our permission to check.

Two of the five formats can be verified from this page right now, with no download and no account. The rubric hash is recomputed in your browser when the grader loads, and it fails visibly if it does not match the value published in the specification. The eight settlement vectors are replayed in your browser when the station loads, against an independent JavaScript implementation of the same arithmetic — if the two implementations ever disagree, the page says so instead of hiding it.

What you cannot verify yet, and why

Every hash on this board was computed over a synthetic observation sheet. The formats are fixed, the arithmetic is pinned, the guards refuse correctly — and not one real machine has been graded by two real people. Until that happens, this page specifies a format rather than documenting a practice, and it should be read that way.

The three things that would change that, in order:

  • One unit, two graders, independently. Produces the first two real observation sheets and the first real reconciliation — including, most usefully, the first real disagreement.
  • One collector run on a real node. Turns the evidence-pack format from a specification into an artifact somebody can fail.
  • One batch of about twenty units. Turns the calibration thresholds from defensible guesses into numbers with a distribution behind them. Which thresholds →