Skip to main content
This page is the canonical reference for operators, auditors, and pilot customers who need to confirm — without trusting AtlaSent at the moment of verification — that a stored decision was issued, in the order claimed, and has not been altered.
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 is signature (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:
A bundle is durable: an auditor can archive the JSON today and re-verify it years later using only the embedded public key(s) and the algorithm in this document. No call back to AtlaSent is required.

Verification recipe

For each proof in order of created_at:
  1. Re-canonicalize the envelope — the proof minus signature and signing_key_id — using JCS sorted-key JSON.
  2. Recompute the chain hash: expected = SHA256(previous_audit_hash || "|" || canonical). The first proof uses previous_audit_hash = "GENESIS". Compare to the stored audit_hash.
  3. Check the link: this proof’s previous_audit_hash must equal the prior proof’s audit_hash.
  4. Verify the signature: Ed25519-verify signature against canonical using the public key matching signing_key_id from signing_keys[]. Try other keys if the hint misses (rotation).
  5. Sanity-check the payload: payload_hash must match ^sha256:[0-9a-f]{64}$; policy_version must be non-empty.
A bundle is 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-side POST /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:
execution_hash is not yet populated. The field exists in the schema for the eventual binding of what was actually executed to the issuing permit, but the binding happens outside the proof chain today. The integrity of the decision is unaffected.
  • 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 from signing_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.