# AXIOM Receipt — Protocol Specification, version 1.0

> Open, MIT-licensed cryptographic primitive for **forensic-grade
> claims about AI behaviour**. Anyone may issue AXIOM receipts.
> Anyone may verify them, end-to-end, without trusting the issuer.

| | |
|---|---|
| Spec ID | `crovia.axiom_receipt.v1` |
| Status | **Stable** — production reference implementation deployed at <https://croviatrust.com/registry/> |
| First publication | 2026-04-28 |
| Reference implementation | <https://croviatrust.com/registry/v/> (browser verifier) · `scripts/axiom_proof_generator.py` (server) · `axiom/envelope.py` + `collectors/substrate_seal.py` (producer) |
| License | MIT — copy, fork, embed, profit |
| Maintainer | Crovia (croviatrust.com) — but the spec is **not** Crovia-controlled. Anyone may run a competing aggregator |

---

## 0 · Why this exists

The AI industry generates billions of model behaviours daily. None of
them are **citable evidence**. A screenshot, a quote, a claim "the
model said X on date Y" can all be fabricated, edited, or selectively
replayed. There is no portable, cryptographically grounded way to say:

> "This model exhibited (or omitted) this behaviour at this moment in
> time, and here is a proof I can verify offline against the Bitcoin
> blockchain — without trusting whoever told me about it."

AXIOM Receipt v1 is that primitive. It binds together:

1. **Content integrity** — what was observed, in canonical bytes
2. **Authorship** — who observed it (Ed25519 signature)
3. **Inclusion** — proof the observation belongs to a sealed batch
4. **Time** — proof the batch existed by a specific Bitcoin block

Each layer is verified independently. Compromise of any single layer
is detectable by anyone with a browser.

---

## 1 · Glossary

- **Envelope** — a JSON document encoding a single observation about
  an AI model or AI-related artefact. The unit of evidence.
- **Issuer** — the party producing envelopes. Holds an Ed25519 private
  key; publishes the corresponding public key in their **trust root**.
- **Trust root** — a stable, publicly served document declaring the
  issuer's identity, public key, and operational scope. Crovia's lives
  at <https://croviatrust.com/registry/canon/TRUST_ROOT.md>.
- **Seal** — a signed Merkle commitment over a batch of envelopes
  (the issuer's append-only ledger). Produces a `merkle_root` that is
  later anchored into Bitcoin.
- **Anchor** — an OpenTimestamps proof that the seal's `merkle_root`
  was committed to a Bitcoin block.
- **Receipt / proof bundle** — the static JSON document that bundles
  envelope + Merkle inclusion path + seal record + Bitcoin anchor URL,
  enabling offline end-to-end verification.

---

## 2 · Canonicalisation (mandatory, normative)

All hashing and signing operates on **canonical JSON bytes**:

```python
json.dumps(
    obj,
    sort_keys=True,
    separators=(",", ":"),
    ensure_ascii=False,
    allow_nan=False,
).encode("utf-8")
```

Equivalent JS:

```js
function canonicalise(o) {
  if (o === null || typeof o !== "object") return o;
  if (Array.isArray(o)) return o.map(canonicalise);
  const out = {};
  for (const k of Object.keys(o).sort()) out[k] = canonicalise(o[k]);
  return out;
}
function canonicalBytes(o) {
  return new TextEncoder().encode(JSON.stringify(canonicalise(o)));
}
```

Both implementations MUST agree byte-for-byte. Deviations break
verification. In particular:

- Keys are sorted lexicographically at every nesting level.
- No whitespace inside arrays / objects.
- UTF-8 emitted directly (no `\uXXXX` escapes for non-ASCII).
- `NaN` / `Infinity` / `-Infinity` are forbidden.
- Trailing `\n` is **not** part of the bytes.

Any compliant AXIOM Receipt implementation MUST include a unit test
asserting `canonical_bytes` is byte-identical between Python and JS
on a fixture containing nested objects, arrays, UTF-8 strings, and
booleans.

---

## 3 · Envelope schema (`crovia.axiom.v1`)

```json
{
  "schema": "crovia.axiom.v1",
  "axiom_type": "AX.OBS" | "AX.ABS" | "AX.NEC" | "AX.MEM" | …,
  "axiom_id": "axm_<sha256-hex of canonical body>",
  "subject": { ... },
  "object":  { ... },
  "body":    { ... },
  "decision": "POSITIVE" | "NEGATIVE" | "NEUTRAL" | "INCONCLUSIVE",
  "confidence": { "method": "deterministic" | "..." },
  "issued_at": "<RFC3339 UTC>",
  "tsa": { "rfc3161_token": "...", "authority": "..." },
  "anchors": [
    {
      "chain": "crovia.axiom_graph",
      "height": <int>,
      "merkle_proof": "<hex>",
      "root_at_anchor": "<hex>"
    }
  ],
  "zk_mode": "clear" | "blinded",
  "zk_proof": null | { ... },
  "predecessors": [ "axm_..." ],
  "signer": "<issuer-id>",
  "signature": "ed25519:<128-hex-char-signature>",
  "notes": "<optional human-readable>"
}
```

### 3.1 axiom_id derivation

```
axiom_id = "axm_" + sha256_hex( canonical_json( envelope − {signature, axiom_id, anchors} ) )
```

The `anchors` field is excluded because it changes when the envelope
is committed to a new Merkle batch; the envelope's identity must
remain stable across re-anchoring events.

`axiom_id` is exactly **68 characters**: `axm_` + 64 hex chars.

### 3.2 Envelope signature

```
signature_payload = canonical_json( envelope − {signature, axiom_id} )
signature_bytes   = Ed25519_sign(issuer_private_key, signature_payload)
envelope.signature = "ed25519:" + hex(signature_bytes)
```

The `axiom_id` is excluded from the signed bytes (it is a derived
field). The `anchors` field IS included, because the signed envelope
commits to which chain is its primary anchor.

### 3.3 axiom_type vocabulary

The vocabulary is **extensible**. Issuers MAY define their own types
under their own namespace prefix, but SHOULD interpret the following
core types consistently:

| Type     | Meaning |
|----------|---------|
| `AX.OBS` | Direct observation that the subject exists / behaves as described |
| `AX.ABS` | Forensic absence — the subject was checked and is missing or omitted |
| `AX.NEC` | Necessity / requirement — a structural claim (e.g. TPA proof obligation) |
| `AX.MEM` | Memorisation evidence — the subject reproduced training data verbatim |

---

## 4 · Seal schema (`crovia.seal.v1`)

A seal commits an append-only ledger of envelopes to a single
Merkle root:

```json
{
  "schema": "crovia.seal.v1",
  "seal_kind": "substrate_batch",
  "seal_family_version": "crovia-seal-family/1",
  "jsonl_path": "<path or URL of the source ledger>",
  "leaf_count": 97959,
  "merkle_root": "<64-hex>",
  "first_receipt_hash": "...",
  "last_receipt_hash":  "...",
  "first_collector_run_id": "...",
  "last_collector_run_id":  "...",
  "key_id":         "<16-hex of sha256(public_key_raw)[:8]>",
  "public_key_hex": "<64-hex of raw Ed25519 public bytes>",
  "signer_version": "...",
  "sealed_at":      "<RFC3339 UTC>",
  "run_id":         "<sealing run id, may be empty string>",
  "signature":      "<128-hex Ed25519>",
  "sig_algorithm":  "ed25519"
}
```

### 4.1 Merkle construction (mandatory, normative)

```
LEAF_PREFIX = 0x00
NODE_PREFIX = 0x01

leaf_hash(envelope_dict) = sha256( LEAF_PREFIX || canonical_json(envelope_dict) )
node_hash(left, right)   = sha256( NODE_PREFIX || left || right )
```

Tree construction:

1. Hash every ledger record into a list of 32-byte leaves, in ledger
   order (line order of the source JSONL).
2. While the layer has more than one node:
   - If the layer length is odd, **duplicate the last node** so the
     layer becomes even.
   - Pair adjacent nodes left-to-right and compute parents via
     `node_hash`.
3. The final 32 bytes are the seal's `merkle_root`.

The leaf at position `i` corresponds to the `i`-th JSON line of the
ledger. Implementations MUST refuse to mint inclusion proofs if the
recomputed root does not match the seal's claimed `merkle_root`.

### 4.2 Seal signature

```
seal_payload   = seal − {signature, sig_algorithm}
sig_bytes      = Ed25519_sign(issuer_private_key, canonical_json(seal_payload))
seal.signature = hex(sig_bytes)
```

Note: the seal's `signature` field carries **raw hex**, not the
`ed25519:` prefix used by envelopes. Both producer and verifier MUST
treat them consistently.

### 4.3 Key identity

```
key_id = sha256( raw_ed25519_public_key_bytes )[:8].hex()  (16 hex chars)
```

`key_id` is what the **trust root** advertises. A verifier MUST refuse
a seal whose `key_id` does not match the public key declared in the
issuer's trust root for the seal's `sealed_at` timestamp.

---

## 5 · Bitcoin anchor (OpenTimestamps)

The seal's `merkle_root` is timestamped via OpenTimestamps:

1. The 32 raw bytes of `merkle_root` are written to a temp file.
2. `ots stamp` is invoked, producing a `.ots` proof committed to four
   independent calendar servers (catallaxy, alice, bob, eternitywall).
3. After typical 1–6 hours, `ots upgrade` retrieves Bitcoin block
   header attestations, extending the proof.
4. The fully upgraded `.ots` file commits the digest to one or more
   Bitcoin block headers and is published at:

```
https://<issuer>/.../substrate/anchors/<merkle_root>.ots
```

A receipt is **fully Bitcoin-anchored** when its seal's `.ots` file
contains at least one `bitcoin_block_<height>` attestation. Until
then, the receipt is **calendar-anchored** (signed, sealed, but only
attested by OpenTimestamps calendars).

A verifier obtaining a `.ots` file MUST NOT trust Crovia's claim that
it is Bitcoin-anchored; it should run `ots verify` locally against
its own Bitcoin node or block header set.

---

## 6 · Receipt / proof bundle (`crovia.axiom_proof.v1`)

A proof bundle is the static JSON document that ships everything a
third party needs to verify the four layers of evidence locally:

```json
{
  "schema": "crovia.axiom_proof.v1",
  "axiom_id": "axm_…",
  "envelope": { /* the full signed envelope */ },
  "ledger": {
    "ledger_path": "...",
    "leaf_index": 12345,
    "leaf_hash": "<hex>"
  },
  "merkle_proof": {
    "path": [
      { "sibling": "<hex>", "side": "left" | "right" },
      …
    ],
    "leaf_prefix": "0x00",
    "node_prefix": "0x01",
    "hash_alg": "sha256",
    "odd_leaf_rule": "duplicate_last"
  },
  "seal": { /* the full original seal payload + signature */ },
  "bitcoin_anchor": {
    "status": "bitcoin" | "calendar" | "pending_next_stamp",
    "ots_url": "/.../<merkle_root>.ots",
    "bitcoin_attestations": [ "bitcoin_block_946914", … ],
    "calendar_attestations": [ "https://…", … ],
    "stamped_at": "..."
  },
  "trust_root": {
    "url": "/registry/canon/TRUST_ROOT.md",
    "key_id": "<16-hex>",
    "public_key_hex": "<64-hex>",
    "signature_algorithm": "ed25519"
  },
  "verifier": {
    "url": "https://<issuer>/v/?id=axm_...",
    "spec": "/registry/canon/AXIOM_RECEIPT_v1.md"
  }
}
```

### 6.1 Path encoding

For each step in `merkle_proof.path`:

- `sibling` is the 32-byte hash (hex) of the **other** node at that
  level.
- `side` indicates which side the sibling occupies:
  - `"right"` — the sibling sits to the right of the current hash;
    parent = `node_hash(current, sibling)`.
  - `"left"`  — the sibling sits to the left;
    parent = `node_hash(sibling, current)`.

When the source layer had odd length and the current node is the
last (duplicated) one, the sibling is the node itself, with
`side: "left"` (verifier still computes
`node_hash(current, current)` correctly via the duplication rule —
or, equivalently, through the explicit `sibling = self` step encoded
in the path).

---

## 7 · Verification algorithm (browser, ~150 LOC)

```
INPUT:  bundle (parsed JSON)
OUTPUT: { content_ok, env_sig_ok, merkle_ok, seal_sig_ok, btc_status }

1. content_ok ← (
     "axm_" + sha256_hex(canonical_json(env − {signature,axiom_id,anchors}))
     == env.axiom_id
   )

2. env_sig_payload ← canonical_json(env − {signature, axiom_id})
   env_sig_bytes   ← hex_decode(env.signature.removeprefix("ed25519:"))
   env_sig_ok      ← Ed25519_verify(trust_root.public_key, env_sig_payload, env_sig_bytes)

3. leaf ← sha256(LEAF_PREFIX || canonical_json(env))
   h    ← leaf
   for step in merkle_proof.path:
     h ← step.side=="right"
         ? sha256(NODE_PREFIX || h || step.sibling)
         : sha256(NODE_PREFIX || step.sibling || h)
   merkle_ok ← hex(h) == seal.merkle_root

4. seal_sig_payload ← canonical_json(seal − {signature, sig_algorithm})
   seal_sig_bytes   ← hex_decode(seal.signature)
   seal_sig_ok      ← Ed25519_verify(trust_root.public_key, seal_sig_payload, seal_sig_bytes)

5. btc_status ← bitcoin_anchor.status
   // Browser does NOT verify the .ots proof itself. The user MUST
   // run `ots verify <merkle_root>.ots` against a Bitcoin node for
   // ground truth. The verifier UI presents the asserted block
   // heights and the .ots URL.
```

A receipt is `TRUSTED` when steps 1–4 all pass AND `btc_status ==
"bitcoin"`. Anything else is `REVIEW`.

---

## 8 · Multi-issuer interoperability

Anyone may publish AXIOM receipts. The minimum requirements:

1. Serve a public **trust root** declaring `key_id`, `public_key_hex`,
   and operational scope.
2. Sign envelopes per §3.2.
3. Publish a Merkle-sealed ledger per §4.
4. Anchor seals into Bitcoin via OpenTimestamps per §5.
5. Publish proof bundles per §6, addressed by `axiom_id`.
6. Implement `crypto.subtle.verify` on the public verifier widget,
   compliant with §7.

Crovia maintains a master aggregator at
<https://croviatrust.com/registry/v/> that resolves any compliant
issuer's `axiom_id` (via convention `<issuer-prefix>_<sha256-hex>`).
The aggregator is **not** authoritative — it merely fetches the
issuer's published bundle and runs §7. Multiple aggregators MAY exist.

---

## 9 · Threat model & guarantees

What an AXIOM Receipt **proves**, given a Bitcoin-anchored seal:

- **Origin**. The envelope was signed by the holder of the issuer's
  Ed25519 key declared in the trust root.
- **Integrity**. The envelope content was not modified since signing
  (any byte change breaks `axiom_id`).
- **Inclusion**. The envelope is a leaf of the sealed batch. The
  issuer cannot publish a contradicting envelope for the same leaf
  index without forking the seal.
- **Time-binding**. The seal `merkle_root` existed at the moment the
  earliest Bitcoin block in the `.ots` proof was mined. The issuer
  cannot retroactively backdate observations past that block height.

What an AXIOM Receipt does **not** prove:

- **Truth of the observation**. The receipt proves *the issuer
  asserted X*; whether X is empirically true is outside the
  cryptographic system.
- **Issuer identity in the real world**. Trust roots advertise key
  ownership; binding to a legal entity requires out-of-band
  attestation (PGP web of trust, domain TLS, in-person key signing).
- **Completeness of a ledger**. An issuer may withhold envelopes from
  publication. Coverage claims require ledger-shape attestations
  (left to a future sub-spec).

---

## 10 · Compatibility & versioning

The spec evolves under SemVer. Minor versions are additive (new
optional fields, new `axiom_type` values, new `bitcoin_anchor.status`
values). Major versions break canonical bytes.

When a major version ships, issuers SHOULD:

- Continue serving v1 proof bundles for all v1 envelopes indefinitely
  (already-published evidence does not migrate).
- Sign new envelopes under both v1 and v2 during a transition window
  (≥ 6 months recommended).

---

## 11 · Reference test vectors

**Sample envelope** (Crovia substrate, AX.ABS):

- `axiom_id`: `axm_af6ff8a7b1bc3f9a2efdd7e91772735e650fef6f07efc4760176287dc7599b24`
- Browser verifier: <https://croviatrust.com/registry/v/?id=axm_af6ff8a7b1bc3f9a2efdd7e91772735e650fef6f07efc4760176287dc7599b24>
- Proof bundle: <https://croviatrust.com/registry/data/substrate/proof/af/axm_af6ff8a7b1bc3f9a2efdd7e91772735e650fef6f07efc4760176287dc7599b24.json>
- Seal `merkle_root`: `813a750aa010dca339de24855b65b2d355f07f373ee7497c7296b31c60bfa3bb`
- `.ots` proof: <https://croviatrust.com/registry/data/substrate/anchors/813a750aa010dca339de24855b65b2d355f07f373ee7497c7296b31c60bfa3bb.ots>
- Bitcoin attestations: blocks **#946911**, **#946914**, **#946939**, **#946954**

Verification status: **TRUSTED** end-to-end (4/4 checks pass + Bitcoin).

---

## 12 · Authors & contact

- Crovia substrate engineering — <substrate@croviatrust.com>
- Spec lives at <https://croviatrust.com/registry/canon/AXIOM_RECEIPT_v1.md>
- Reference impl in repo: `axiom/`, `collectors/substrate_seal.py`,
  `scripts/axiom_proof_generator.py`, `verifier/index.html`

---

*AXIOM Receipt v1 is not a product. It is a public protocol, MIT-licensed,
designed so that Crovia is one issuer among many — not the only one. If
you want to issue receipts about your own AI systems, fork this spec
and ship.*
