Proof Format: occ/1

Normative specification for the occ/1 proof format. Derived from the reference implementation.

Proof JSON schema

proof.json
{
  "version": "occ/1",                // REQUIRED - exact value
  "artifact": {
    "hashAlg": "sha256",             // REQUIRED - "sha256" only in v1
    "digestB64": "<base64>"          // REQUIRED - SHA-256, 32 decoded bytes
  },
  "commit": {
    "nonceB64": "<base64>",          // REQUIRED - >=16 decoded bytes
    "counter":  "42",                // OPTIONAL - decimal string, monotonic
    "slotCounter": "41",             // OPTIONAL - slot's counter (< commit counter)
    "slotHashB64": "<base64>",       // OPTIONAL - SHA-256 of canonical slot body
    "time":     1700000000000,       // OPTIONAL - Unix ms
    "prevB64":  "<base64>",          // OPTIONAL - chain link, 32 bytes
    "epochId":  "<hex>"              // OPTIONAL - SHA-256 hex
  },
  "signer": {
    "publicKeyB64":  "<base64>",     // REQUIRED - Ed25519, 32 bytes
    "signatureB64":  "<base64>"      // REQUIRED - Ed25519, 64 bytes
  },
  "environment": {
    "enforcement": "measured-tee",   // REQUIRED - "stub"|"hw-key"|"measured-tee"
    "measurement": "<opaque>",       // REQUIRED - non-empty string
    "attestation": {                 // OPTIONAL
      "format":    "aws-nitro",      // REQUIRED when parent present
      "reportB64": "<base64>"        // REQUIRED when parent present
    }
  },
  "slotAllocation": {                // OPTIONAL - causal slot record
    "version":      "occ/slot/1",
    "nonceB64":     "<base64>",      // same as commit.nonceB64
    "counter":      "41",            // same as commit.slotCounter
    "time":         1700000000000,
    "epochId":      "<hex>",
    "publicKeyB64": "<base64>",      // enclave Ed25519 key
    "signatureB64": "<base64>"       // Ed25519 over canonical slot body
  },
  "agency": {                         // OPTIONAL - actor-bound proof
    "actor": { keyId, publicKeyB64, algorithm, provider },
    "authorization": { purpose, actorKeyId, artifactHash, challenge, timestamp, signatureB64 },
    "batchContext": {                 // OPTIONAL - present on batch proofs
      "batchSize": 8,
      "batchIndex": 0,
      "batchDigests": ["<base64>", ...]
    }
  },
  "attribution": {                   // OPTIONAL - signed creator metadata
    "name":    "string",
    "title":   "string",
    "message": "string"
  },
  "timestamps": {                    // OPTIONAL - external timestamps
    "artifact": { TsaToken },
    "proof":    { TsaToken }
  },
  "metadata": { },                   // OPTIONAL - NOT signed, advisory
  "claims": { }                      // OPTIONAL - NOT signed, advisory
}

Signed body

The Ed25519 signature covers the canonical serialization of a SignedBody object:

SignedBody
{
  version:           proof.version,
  artifact:          proof.artifact,
  actor:             proof.agency?.actor,        // when present
  attribution:       proof.attribution,          // when present
  commit:            proof.commit,               // ALL fields verbatim
  publicKeyB64:      proof.signer.publicKeyB64,
  enforcement:       proof.environment.enforcement,
  measurement:       proof.environment.measurement,
  attestationFormat: proof.environment.attestation?.format  // when present
}

What is NOT signed

FieldReason
signatureB64The seal -- cannot sign itself
attestation.reportB64Vendor-signed, self-authenticating separately
slotAllocationSelf-authenticating (own Ed25519 signature); bound via commit.slotHashB64
agencyP-256 signature independently verifiable; actor identity IS in signed body
timestampsAdded post-signature by external TSA
metadataAdvisory, not trusted
claimsAdvisory, not trusted

Causal slot allocation

Every proof is causally bound to a pre-allocated slot. The slot is created before the artifact hash is known, proving the enclave committed to a nonce and counter independently of the artifact content.

BindingHow
Nonce bindingcommit.nonceB64 === slotAllocation.nonceB64
Counter orderingcommit.slotCounter < commit.counter
Hash bindingcommit.slotHashB64 === SHA-256(canonicalize(slotBody))
Same enclaveslotAllocation.publicKeyB64 === signer.publicKeyB64

The slot has its own Ed25519 signature proving the enclave created it. The commit signature includes slotHashB64, cryptographically binding the proof to that exact slot.

Canonical serialization

The signed body is serialized to bytes using a deterministic algorithm:

  1. 1. Recursively sort all object keys in Unicode code-point order
  2. 2. Serialize with JSON.stringify() -- no whitespace
  3. 3. Encode the resulting string as UTF-8 (no BOM)

Top-level key order after sort:

actor? → artifact → attestationFormat? → attribution? → commit → enforcement → measurement → publicKeyB64 → version

Field classification

Signed (security-critical)

These fields are in the SignedBody. Tampering invalidates the signature:

version, artifact.*, agency.actor (when present), attribution.* (when present), commit.*, signer.publicKeyB64, environment.enforcement, environment.measurement, attestation.format

Self-authenticating

Not in the signed body, but independently verifiable:

signatureB64 (Ed25519), attestation.reportB64 (vendor-signed), slotAllocation (own Ed25519 signature), agency.authorization (P-256)

Advisory (unsigned)

Not signed. Must not be used for security decisions: timestamps, metadata, claims.

Algorithms

PurposeAlgorithmDetails
Proof signatureEd25519 (RFC 8032)32-byte key, 64-byte signature
Agency signatureECDSA P-256 / ES256WebAuthn or direct; device-bound key
HashSHA-256 (FIPS 180-4)32 bytes, Base64 encoded
EncodingBase64 (RFC 4648 §4)Standard, with = padding
CounterDecimal stringBigInt-safe, no leading zeros