This page covers the proof chain exported by
GET /v1/proofs/export
(bundle schema atlasent.proof-bundle.v1). It is a separate chain from the
audit-chain export, which uses a different hash construction (a
zero-hash genesis rather than the GENESIS sentinel below) and is verified
with the atlasent-audit-verify CLI. For that export, see
How to verify your audit chain. Do not apply one chain’s
algorithm to the other’s export.What a proof is
Every call to/v1/evaluate produces a row
in the per-org proofs chain. Each row is signed and linked to the
previous row. The row carries the fields needed to reconstruct what
was decided and why.
The chain is per-org: each organization has its own independent
sequence, serialized by an advisory lock at insert time.
Algorithms
Chain hash
For each new proof:previous_audit_hash is the prior row’s audit_hash for the same
org, or the literal GENESIS for the first row. The | byte is
included as a domain separator.
The envelope hashed is the full proof minus signature and
signing_key_id:
Signature
The same canonical envelope above is signed with an Ed25519 key. The operator-visible artifact issignature (base64url) plus a
signing_key_id to disambiguate during key rotation.
Public keys are bundled with every proof export under signing_keys[]
as PEM, with created_at and retired_at for rotation history.
When verifying, try keys in order; a single match is sufficient.
Retired keys remain valid for proofs issued before their retired_at.
Proof bundles
GET /v1/proofs/export returns a self-contained bundle:
Verification recipe
For each proof in order ofcreated_at:
- Re-canonicalize the envelope — the proof minus
signatureandsigning_key_id— using JCS sorted-key JSON. - Recompute the chain hash:
expected = SHA256(previous_audit_hash || "|" || canonical). The first proof usesprevious_audit_hash = "GENESIS". Compare to the storedaudit_hash. - Check the link: this proof’s
previous_audit_hashmust equal the prior proof’saudit_hash. - Verify the signature: Ed25519-verify
signatureagainstcanonicalusing the public key matchingsigning_key_idfromsigning_keys[]. Try other keys if the hint misses (rotation). - Sanity-check the payload:
payload_hashmust match^sha256:[0-9a-f]{64}$;policy_versionmust be non-empty.
valid only if every proof passes every step.
Server-side verifyProof response
When the engine runs the same checks via POST /v1/proofs/{id}/verify,
the response uses the canonical verification envelope shared by
/v1/permits/{id}/verify and /v1/audit/verify. Pin to valid and
evidence.* for new code; the legacy proof_id / status / checks
/ failure_reasons fields are preserved at the top level for
backward compatibility.
Reference verifier
You can implement the recipe above in any language with an Ed25519 library and a JCS canonicalizer. The server-sidePOST /v1/proofs/{id}/verify endpoint runs the same checks and is useful
for cross-checking your own implementation.
Known limits
These are documented openly so pilot customers can plan around them:- v2 proof schema is in flight. Field ordering for v2 envelopes will be locked at the v2-stable cut. v1 proofs and the algorithm on this page are stable.
- Genesis sentinel is a literal string.
"GENESIS", not a zero hash. Verifiers must special-case it.
Threat model in one paragraph
A reader who holds a proof bundle and any historical public key fromsigning_keys[] can detect: any modification to a decision after the
fact (signature break), any reordering or deletion of decisions
(chain-link break), any forgery without the private key (signature
break), and any drift in the policy version that produced a decision
(envelope break). The reader does not need to trust the AtlaSent API
or database at the moment of verification.
Related
- Audit chain — the append-only model.
- Quickstart → Export the audit bundle — the operator command.
- Governed close operations — end-to-end pilot workflow that uses this proof.