Skip to content

Engineering / 13 Apr 2026

Merkle receipts: residency evidence a DPA can verify without trusting us

How YTT Secunet signs a receipt for every inference call, commits it to an hourly Merkle root, and lets an auditor check inclusion offline.

Post record

author
Rahul Sethi
role
Head of Cryptography
published
category
Engineering
length
4 min / ~950 words
Key takeaways
  • A signature proves who made a claim; a published Merkle root stops the signer from quietly rewriting that claim later.
  • Each receipt binds the policy decision to the upstream TLS certificate thumbprint, so the endpoint claim is checkable, not narrative.
  • Inclusion proofs are logarithmic: about twenty hashes cover a tree of a million receipts.
  • Receipts prove where YTT Secunet sent a request. They do not prove what happened inside a provider's facility, and we say so.

Logs are testimony, not evidence

When a supervisory authority asks a controller to show that EU personal data was processed only in the EU, the usual answer is an export from a logging system. The problem is that the controller, or its vendor, controls that system end to end. Rows can be filtered, edited or regenerated. An auditor reading a CSV is reading testimony from an interested party.

We designed the YTT Secunet proof ledger around a narrower and more useful claim: for every inference request, here is a statement of where it was routed and why, signed at the moment of routing, and committed to a public structure that makes later alteration detectable by anyone. You should not need to trust us to check it. That requirement drove every design choice below.

What goes in a receipt

A receipt is a small JSON document minted in the request path after the policy decision and after the upstream TLS handshake completes. It carries a SHA-256 hash of the canonicalised request rather than the request itself, so the ledger never holds prompt content. It names the policy pack and version that made the decision, the resolved endpoint, the provider's region identifier, a facility identifier, the model id, the zero-retention flag and a timestamp.

The field I care most about is the upstream certificate thumbprint. Any system can write the string eu-central into a log. Binding the receipt to the SHA-256 thumbprint of the certificate the gateway actually validated during the TLS 1.3 handshake ties the claim to a cryptographic fact about the connection. An auditor can compare that thumbprint with the provider's published regional certificate and catch a receipt that names one endpoint while the connection went to another.

json
{
  "v": 2,
  "request_sha256": "9b1c4e0f6a7d2e83c51f0b4a7e9d3c2b18f6a05d4c3b2a1908e7f6d5c4b3a291",
  "tenant": "tn_01HV8K2M7Q",
  "policy": { "pack": "eu-gdpr-strict", "version": "3.4.1", "decision": "allow" },
  "endpoint": "https://eu-fra.provider.example/v1/chat/completions",
  "provider_region": "eu-central",
  "facility": "DE-FRA-2",
  "jurisdiction": "EU",
  "upstream_cert_sha256": "3f9a7c1e5b2d8f40a6c3e1b9d7f5a2c4e8b0d6f3a1c9e7b5d2f4a8c6e0b3d1f7",
  "model": "mistral-large-latest",
  "zdr": true,
  "ts": "2026-04-09T14:22:07.481Z"
}

Signing: Ed25519 over a compact JWS

The receipt is serialised with a deterministic JSON canonicalisation and signed as a compact JWS using EdDSA with Ed25519, as registered in RFC 8037. We chose Ed25519 for boring reasons: deterministic signatures, no nonce to get wrong, small keys and fast verification in every mainstream language. The signature travels back to the caller in the X-YTT-Proof-Signature response header, so the application can store it alongside its own records if it wants.

Each signing key has a kid in the JWS header, and keys rotate on a published schedule. On Sovereign Dedicated deployments the signing key lives in the customer's HSM, which matters: if your own key signs your receipts, our key compromise cannot forge your evidence.

Canonicalisation deserves a sentence because it is where these schemes usually break. Two JSON serialisers can emit the same object with different key order or number formatting, which produces different bytes and therefore different hashes. We fix the field order and encoding in the receipt schema, version it in the v field, and the verifier refuses any receipt whose version it does not understand rather than guessing.

Why a signature alone is not enough

A signature proves that the holder of a key made a statement. It does not stop that holder from making a different statement later and signing that too. If we wanted to, we could mint a fresh, validly signed receipt for last March saying a request went to Dublin when it went somewhere else. Signatures give authenticity. They do not give history.

Merkle trees fix that. Every hour, each zone's receipts become the leaves of a SHA-256 tree. We follow the construction in RFC 6962, the Certificate Transparency spec: leaves are hashed with a 0x00 prefix and interior nodes with a 0x01 prefix, which blocks the second preimage trick of passing an interior node off as a leaf. The root of each hourly tree is signed and published. Once a root is out, changing any receipt under it changes the root, and anyone holding the old root sees the mismatch.

Publication only helps if the root escapes our control. Each hourly root goes to a public endpoint and into the customer's own tenant export, and customers can mirror roots into their own storage or a timestamping service as they arrive. A controller that keeps its own copy of each root from the day it was published does not need to trust our endpoint at audit time; it compares what we serve against what it already holds.

  • An inclusion proof is the list of sibling hashes from the leaf to the root.
  • Proof length is the base-2 logarithm of the tree size: about twenty hashes for a million receipts.
  • Verification is pure hashing and needs no network access once you hold the root.

Verifying offline with ytt-verify

The verifier is open source and deliberately small, so an auditor's own engineer can read it in an afternoon. It checks the JWS signature against a pinned public key, recomputes the leaf hash, walks the inclusion proof and compares the result with the published root. It does not phone home. An auditor can run it on an air-gapped laptop against a bundle the controller exported.

bash
# Bundle exported from the Compliance Reports console for one DPA request
ytt-verify inclusion \
  --receipt receipts/7f3a2c.jws \
  --proof proofs/7f3a2c.json \
  --root roots/eu-2026-04-09T14.json \
  --pubkey keys/yttsecunet-eu-2026q2.pub

# Check a whole export, fail on the first mismatch
ytt-verify batch --bundle dpa-export-2026-04.tar.gz --pubkey keys/yttsecunet-eu-2026q2.pub --strict

What a receipt does not prove

Precision about limits is part of the evidence. A receipt proves that YTT Secunet evaluated a named policy, chose a named endpoint and completed a TLS handshake with a server presenting a specific certificate, at a specific time. It does not prove what the provider did with the request inside its facility, and it cannot by itself prove that the provider's regional endpoint does not forward work elsewhere. Those claims rest on the provider's contract, its certifications and, increasingly, on hardware attestation.

What receipts do is move the conversation with a supervisory authority from what the architecture is supposed to do to what it demonstrably did, per request, with gaps visible. In our experience that is the difference between an audit that runs for months and one that closes after the first evidence package.

About the author

RS

Rahul Sethi

Head of Cryptography, YTT Secunet

Applied cryptographer, M.Tech from IISc Bengaluru. Designed the Merkle receipt format and the offline verifier regulators can run without trusting YTT Secunet.

Meet the team

Sovereign route ready

Stop risking 4% global turnover GDPR fines on AI inference.

Deploy sovereign, cryptographically proven AI routing in 15 minutes.