DEOS Attestation API — v1 Specification

Status: v1.0 — published 2026-05-02 Spec URI: https://projectmaya.deoscomputing.io/attestation/api/v1 Maintainer: DEOS Computing (did:web:deos.computing) Reference impl: Rust HTTP server in DEOS Computing's working repo (access-controlled during v1 alpha). Reference verifier: standalone Rust binary, intentionally shares no crate with the server, in the same repo.

The Attestation API is the substrate's outward-facing HTTP surface. Auditors, counterparties, regulators, courts, and SDK verifiers use it to retrieve receipts, capability chains, inclusion proofs, the log root, and the substrate's signing key — everything needed to verify a receipt without trusting the substrate beyond the published trust-root key.

This document is normative for v1.


1. Routes

All routes are under /attestation/v1/. All responses are Content-Type: application/json. All hashes are lowercase hex (BLAKE3-256, 64 chars). All keys/signatures are base64url-nopad.

Method Path Purpose
GET /attestation/v1/health Liveness probe
GET /attestation/v1/trust-root Substrate's signing identity
GET /attestation/v1/log Current log state (leaf_count, root)
GET /attestation/v1/receipt/{id} A signed receipt + its log position
GET /attestation/v1/proof/{id} An inclusion proof for that receipt
POST /attestation/v1/append Submit a receipt for signing + inclusion

1.1 GET /attestation/v1/health

{ "status": "ok", "service": "deos-attestation-v1" }

1.2 GET /attestation/v1/trust-root

Returns the substrate's issuer DID, key id (kid), algorithm, and public key.

{
  "issuer_did": "did:deos:substrate-alpha-1",
  "kid": "did:deos:substrate-alpha-1#sig-1",
  "alg": "ed25519",
  "public_key_b64u": "<base64url-nopad of 32 bytes>"
}

A verifier MUST fetch this once per session (or cache it with a known expiry) and use it as the trust anchor for all signature checks. The kid returned here is the only kid this substrate signs with; receipts whose signatures[].kid does not match (and which lack any other resolvable kid) MUST be rejected.

1.3 GET /attestation/v1/log

{
  "leaf_count": 5,
  "root": "16d63c30139e288d67c3aa08b36acdb9b41185c239adfa0b270453f8837d5e1e"
}

The current root advances monotonically as receipts are appended. Verifiers SHOULD pin a fetched root for the duration of a single verification so that intervening appends don't shift the ground truth mid-flight.

1.4 GET /attestation/v1/receipt/{id}

Returns the signed canonical receipt plus the log mapping recorded at the time of inclusion.

{
  "receipt": { /* full v1 signed receipt object */ },
  "leaf_index": 0,
  "leaf_count_at_inclusion": 1,
  "root_at_inclusion": "<hex>",
  "log_leaf_count_now": 5,
  "log_root_now": "<hex>"
}

leaf_count_at_inclusion and root_at_inclusion are the values the substrate observed immediately after appending this receipt. They are informational; for verification, callers should rely on the proof returned by /proof/{id} and the current /log.

If the receipt is unknown: HTTP 404 with {"error":"receipt not found"}.

1.5 GET /attestation/v1/proof/{id}

Returns an inclusion proof in the format defined by /merkle/log/v1 §6, augmented with the root the proof is against:

{
  "leaf_index": 0,
  "leaf_count": 5,
  "siblings": ["<hex>", ...],
  "bag_peaks_left": ["<hex>", ...],
  "bag_peaks_right": ["<hex>", ...],
  "root": "<hex>",
  "id": "<hex>"
}

The root field is the current log root at the time the proof was generated. Verifiers MUST cross-check this matches /attestation/v1/log (within their pinning window).

1.6 POST /attestation/v1/append

Body: a v1 receipt object (without id or signatures — those are populated by the substrate).

The substrate:

  1. Strips any caller-supplied id and signatures.
  2. Computes the receipt's canonical id.
  3. Signs with the substrate's private key.
  4. Canonicalizes the SIGNED receipt and appends those bytes as the next MMR leaf.
  5. Persists: receipt blob (by id), log snapshot, id↔leaf-index index.
  6. Responds:
{
  "id": "<receipt id, hex>",
  "leaf_index": 0,
  "leaf_count": 1,
  "root": "<hex>",
  "canonical_size_bytes": 1141
}

Errors:

Note on access control for v1: this endpoint is unauthenticated in the reference implementation. In production, gate POST /append to authorized substrate-internal callers (mTLS or capability-token auth — the substrate should only sign receipts it itself produced).

2. Verification flow (normative)

A correct verifier performs these steps in order:

  1. GET /attestation/v1/trust-root → cache {issuer_did, kid, public_key}.
  2. GET /attestation/v1/receipt/{id} → get receipt.
  3. Recompute the receipt id from BLAKE3(canonical(receipt minus {id, signatures})). It MUST match receipt.id AND the {id} requested.
  4. Verify signatures. Build the signing payload: "deos-receipt-v1:" || canonical(receipt minus {signatures}). Verify each signature in receipt.signatures whose kid is recognized. At least one must verify and its kid must belong to issuer_did.
  5. GET /attestation/v1/proof/{id} → get {leaf_index, leaf_count, siblings, bag_peaks_left, bag_peaks_right, root}.
  6. GET /attestation/v1/log → cross-check that the log's current root and leaf_count match what the proof was issued against.
  7. Verify the inclusion proof per /merkle/log/v1 §6.2. The leaf data is canonical(full signed receipt) — the log binds the signed instance, not the unsigned body.
  8. (Future, M5) If receipt.anchors is non-empty, verify at least one anchor proof and treat the anchor's timestamp as the receipt's time-of-existence floor.

If any step fails, the receipt is rejected with a specific error code:

Code Meaning
INVALID:id Recomputed id does not match claim or request
INVALID:issuer receipt.issuer differs from trust-root issuer DID
INVALID:alg Unrecognized signature algorithm
INVALID:sig Signature does not verify, no signature by issuer kid, or sig encoding broken
INVALID:proof Inclusion proof does not yield the published root
INVALID:root Proof's stated root disagrees with /log
INVALID:signatures Signatures array missing or empty

The reference verifier implements this flow and is intentionally compiled with no shared crate with the receipt-format reference impl or the attestation server. Spec-only interoperability is the explicit goal.

3. Trust assumptions (v1)

What v1 trusts:

What v1 does not trust:

4. Tampering taxonomy (caught by the verifier)

Tamper Detected at step
Mutate any byte of the receipt body (3) id mismatch, then (4) signature would also fail
Strip a signature (4) issuer signature missing
Substitute a forged signature (4) signature does not verify
Send a different receipt under the requested id (3) recomputed id ≠ requested id
Tamper with the inclusion proof (7) proof does not yield root
Forge a fake root in /log (4) the root only matters for inclusion verification — the receipt is still independently signed; a fake root makes proofs fail (7), it doesn't admit a forged receipt
Append a fake receipt to the log without a substrate signature (4) verifier rejects: no valid issuer signature

5. Operational notes

6. Open questions deferred


7. Maintainer

DEOS Computing — design questions, conformance reports, and proposed additions to github.com/DEOS-Computing.

License: CC BY 4.0 (text), Apache-2.0 (reference code).