Verification Record anatomy.
A verification record is one portable JSON document. Everything needed to check it (every system's before-and-after, two signatures, and the transparency proof) travels inside the file. It verifies offline, with standard cryptography, even if BurnLedger no longer exists.
The shape
Current format version is 9.0. 4.0 added issuer.legal_entity and a scope commitment that 3.0 did not, 5.0 brought the enclave measurement issuer.enclave_pcr0 inside the signature, 6.0 carries 5.0's shape under its own domain-separation tags following the Verification Record rename, 7.0 is the first format since 4.0 to change what is signed rather than what it is called — every system states how read-only was established and what protected the query, issuer.algorithm names the signature scheme, the record commitment is keyed, and the subject hash is Argon2id rather than SHA-256 — 8.0 adds recoverable_state, which says whether anything checked for a restorable copy of the records this document certifies removed, and 9.0 adds authorization and customer_key_id, which say whether the enclave checked that system against a registration certificate you signed, and under which of your keys. Records at every earlier version stay verifiable, because a verifier rebuilds the payload for the version the file declares. Binary fields (keys, hashes, signatures) are serialized as JSON byte arrays. Abridged and illustrative:
{
"certificate_format_version": "9.0",
"certificate_id": "11111111-1111-4111-8111-111111111111",
"attestation_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
"issued_at": "2026-03-10T14:34:17Z",
"issuer": {
"name": "BurnLedger",
"public_key": [5, 6, 136, …], // 32 bytes, Ed25519
"key_id": "dp_k_d59705f4…",
"algorithm": "Ed25519",
"legal_entity": "ProChatFlow LLC"
},
"subject": {
"identifier_hash": [11, 140, 190, …], // 32 bytes, salted SHA-256
"identifier_type_hint": "email"
},
"attestation": {
"proof_mode": "count",
"attested_at": "2026-03-10T14:04:00Z",
"attestation_signature": [12, 240, …] // 64 bytes, signed days earlier
},
"systems": [ // one entry per system
{
"system_id": "5111e111-1111-4111-8111-111111111111",
"system_name": "prod-users", // display only
"connector_type": "postgresql",
"hash_scope": "existence",
"query_hash": [118, 56, 100, …], // 32 bytes: which question
"attested_count": 42,
"attested_at": "2026-03-10T14:04:00Z",
"verified_count": 0,
"verified_at": "2026-03-10T14:34:00Z",
"read_only_enforcement": "verified_by_introspection",
"transport_security": "verified",
"recoverable_state": "not_probed_no_engine_mechanism",
"authorization": "certified", // or "none"
"customer_key_id": "cust_k_a5a5a5a5…" // absent when "none"
}
],
"scope": { "version": "2.0", "text_sha256": [216, 30, …] }, // SHA-256 of the scope text
"certificate_signature": [166, 92, …], // 64 bytes, binds everything above
"transparency_status": "INCLUDED",
"transparency": { … } // inclusion proof + signed tree head
}
Values are examples; a JSON file carries no comments. The authoritative shape is whatever the offline verifiers accept.
Identity and issuer
| Field | Meaning |
|---|---|
| certificate_format_version | Schema version. Verifiers refuse versions they don't know. |
| certificate_id | UUID of this verification record. |
| attestation_id | UUID of the attestation this verification record closes out. |
| issued_at | When the verification record was signed, stamped by the signing enclave's clock. |
| issuer.public_key | The Ed25519 public key that signed everything in this file. Cross-check it against the published keys at /.well-known/burnledger-keys: never trust a key only because the file contains it. |
| issuer.key_id | Stable identifier of that key. |
| issuer.algorithm | The signature scheme the two signatures use, Ed25519. Signed since 7.0, so a verifier branches on a value in the file rather than on an assumption four verifiers happened to share. |
| issuer.legal_entity | The legal entity that issued the verification record, signed since 4.0 so the issuer of record cannot be edited out of the file. |
Subject
| Field | Meaning |
|---|---|
| subject.identifier_hash | A salted SHA-256 of the subject identifier. The plaintext identifier (an email, a customer id) is never stored and never appears in the verification record. Anyone holding both the identifier and your salt can re-derive the hash and prove which person the verification record is about — and BurnLedger stores your salt, so we can test a guessed identifier against the hash as well as you can. It is therefore pseudonymised personal data, not anonymous data, and our DPA treats it that way. Someone holding only the verification record cannot tell who it names. |
| subject.identifier_type_hint | What kind of identifier was hashed (for example email). |
The systems
The verification record's substance is one list, with each system's before and after on the same record:
| Field | Meaning |
|---|---|
| systems[].system_id | The system's stable identity. This, not the name, is what verification is keyed on. |
| systems[].system_name | Display text. It carries no weight in verification — editing it changes nothing a verifier checks except the signature it breaks. |
| systems[].query_hash | SHA-256 of the normalized query template that established presence and absence. The enclave recomputes this from the template it is about to run and refuses to execute on a mismatch, so it records the question that was actually asked — not one asserted alongside it. |
| systems[].attested_count / attested_at | What was found in this system, and when, before your deletion. |
| systems[].verified_count / verified_at | What was found when the same query was re-run after your deletion, and when. A verification record is only issued when every re-query comes back empty. |
| systems[].hash_scope, connector_type | How presence was captured for this system, and what kind of store it is. |
| systems[].read_only_enforcement | How BurnLedger established that the credential used against this system cannot write: verified_by_introspection when the enclave asked the engine itself, operator_asserted when only you said so, write_access_detected when it found otherwise, unknown when nothing was established. Signed since 7.0, so it is a claim inside the record rather than a row that can be edited afterwards. |
| systems[].transport_security | What protected this system's queries in transit — verified, encrypted, plaintext or unknown — classified by the enclave when it connected. Signed since 7.0. |
| systems[].recoverable_state | Whether anything checked for a restorable copy of these records, and what it found. Six connectors ask the datastore directly and report probed_none_found; a probe that finds a restorable copy never reaches a record at all, because the connector refuses and nothing is issued. The other fourteen report why they did not ask: not_probed_engine_retains where the engine keeps a restorable copy and no sound probe exists, not_probed_no_engine_mechanism where a deleted row is simply gone from the interface the credential speaks. operator_asserted and unknown carry no BurnLedger claim. Signed since 8.0: before it, all twenty produced identical bytes, so a record where the question was answered was indistinguishable from one where it was never put. |
| systems[].authorization | Whether the enclave checked this system against a registration certificate you signed before it ran the query. certified means it did; none means the verification took the legacy path and no signed registration was presented. Neither value is a BurnLedger judgement about your deployment — the enclave cannot refuse an uncertified request, so this field says which mode produced the record rather than claiming a control. Signed since 9.0: before it, both paths produced identical bytes. |
| systems[].customer_key_id | Which of your key groups authorized the registration, present only when authorization is certified. Check it against the key you hold. If it names a group you do not have, something other than you authorized this verification — that is what this field is for, and it is inside the signature so it cannot be edited after the fact. |
| attestation.proof_mode / attested_at | How presence was captured overall (for example count), and the attestation envelope's own timestamp. |
| scope.version / text_sha256 | Which scope statement the verification record was issued under, and the SHA-256 of its text. Signed since 4.0, so the claim a verification record makes cannot be widened after the fact. |
Before 3.0 this was two lists — an attested one and a verified one — joined only on the human-editable system name. A verification record that attested three systems and verified one satisfied every check and read as a complete deletion. One list keyed by system_id makes that document impossible to construct rather than merely invalid.
Two signatures, one binding
attestation.attestation_signature— signs the "present" observation at the moment it was taken, which is typically days before the verification record exists.certificate_signature— signs the verification record as a whole: every system's identity, query, both counts and both timestamps, bound to the subject and the issuance time.
Both are Ed25519 over canonical JSON bytes, and both are checked by every verifier. There is deliberately no third signature over the "absent" observation: it is produced inside the same enclave call that signs the verification record, so certificate_signature already covers it. A separate signature there would have been one more thing to check and nothing more to prove.
Verifiers additionally require that the system list is well formed: identities present and unique, no system verified before it was attested, every verified count zero, and at least one system that actually held records at attest time. A verification record where nothing was ever found documents the deletion of nothing, and does not validate.
Transparency
| Field | Meaning |
|---|---|
| transparency_status | INCLUDED once the verification record is in the public log. |
| transparency.entry_index | The verification record's position in the append-only log. |
| transparency.inclusion_proof | The Merkle audit path (RFC 6962) from this verification record's leaf up to the root. |
| transparency.signed_tree_head | The signed log head (tree_size, root_hash, timestamp, signature) the proof resolves to. |
This block is what makes the verification record more than a signature: it pins the verification record into a public history BurnLedger cannot rewrite. Anyone can fetch the live log head and demand a consistency proof from the head in the verification record to the head of today — see Verify a record.
Status and revocation
Revocation is not a field in the verification record. A signature commits to bytes at an instant, and revocation is discovered later, so no edit to a signed document can express it honestly — earlier formats carried a status field that nothing ever re-signed. It travels instead as a separate short-lived signed statement, public and needing no account:
curl https://api.burnledger.io/v1/certificates/{id}/status
The response is a signed status statement with its own validity window, verifiable against the same published keys. Three outcomes matter:
- A fresh statement saying
ACTIVE: the verification record stands. - A fresh statement saying
REVOKED: treat the verification record as withdrawn. - No statement, or an expired one: treat revocation as unknown. Verifiers report this distinctly; they never upgrade silence to "valid".
What a verification record proves, and what it doesn't
It proves that, for the queries whose hashes it names, records matching the subject were present in the identified systems at their attested_at and absent at their verified_at; that BurnLedger's published signing key signed both observations and their binding; and that the verification record sits in the public log at the index it names. Those are the three things a verifier recomputes from the file.
It does not make claims beyond those observations. In particular, it says nothing about systems that were never registered, data the registered queries do not match, or copies living outside the queried stores — for example backups and downstream exports. Scope your registered systems and queries to match what you intend to prove.
Three further limits are worth stating, because all three are easy to assume:
- The query hash is not the query. The verification record proves which question was asked and that it was not swapped after issuance. It does not let a reader see the question. To check the query itself, compare the hash against the template you were shown.
system_idis an identity, not an address. It proves the verification record speaks about one consistent, uniquely-named system. It does not tell a third party which host, database or table that is.- It carries no hardware attestation. The signing key is generated inside an AWS Nitro Enclave and released to it by a KMS policy pinned to a measured image. That constrains us, and the build is deterministic — we rebuild every release and confirm it measures identically. We do not publish the source or toolchain that would let you repeat that rebuild, by decision rather than by omission: the enclave source is the product. That check is therefore ours, not yours. Either way, no attestation document travels in the verification record, so a verifier cannot check from the file alone which image signed it. What the file proves is that the published key did. From format 5.0 the signature does cover
issuer.enclave_pcr0, the measurement the signer names for itself — a self-report until it is held against a prior public commitment, which is what the published measurement history is for. - It does not record the transport. Record counts are read over the connector's link to your datastore, and BurnLedger refuses links below verified TLS at connection time — but the transport level is not bound into the signature. A third party cannot tell from the verification record what link produced a count. See the transport security reference.
Next
Hand the file to anyone — with Verify a record they can recompute every signature and proof described here themselves.
Common questions
What is a verification record?
A verification record is a single portable JSON document that proves a data subject’s records were present in named systems at one moment and absent at a later one. It carries both observations, two Ed25519 signatures over canonical bytes, and an RFC 6962 inclusion proof placing it in the public transparency log, so anyone holding the file can check it offline without a BurnLedger account and without asking us anything. The subject is identified by a salted hash rather than an email address, and each named system carries its own record counts and query hash, so a reader can see exactly which questions were asked of which stores. Format 4.0 additionally signs the issuing legal entity and a hash of the scope wording the verification record was issued under. Version 3 documents remain verifiable forever; a verifier that dropped them would destroy the evidence they exist to be.
Does a verification record prove all of a person's data is gone?
No, and this is the most important limitation to understand. It proves the outcome of specific queries against specific registered systems. It says nothing about systems that were never registered, data the queries do not match, copies held outside the queried stores, backups, or anything an operator chose not to point at. The scope is exactly the systems and query hashes named in the file, which is why both are named in it rather than summarised. A verification record is evidence about a measurement, not a legal opinion that an erasure obligation has been discharged — that judgement belongs to the controller and its counsel. Read the named systems and query hashes before relying on one, and treat an unregistered system as unexamined rather than as clean. The hash scope recorded against each system tells you whether the measurement covered the existence of matching records or their full contents, which is a further limit on what the document can be read to say.
How is a verification record revoked?
Revocation is not a field inside the verification record, because a signature commits to bytes at one instant and revocation is discovered afterwards — no edit to the signed document could express it without invalidating the signature that makes it evidence. Instead, GET /v1/certificates/{id}/status returns a separately signed status statement with its own short validity window. A fresh ACTIVE statement means the verification record stands; REVOKED means it was withdrawn, and the statement says when; silence means unknown, which is never the same as valid. Verifiers treat a missing statement as an open question rather than a clean bill of health, and the public verify page will not claim revocation was checked unless a statement was actually verified against the published signing keys. A verification record fetched from the API arrives with a current statement stapled beside it, so the common case needs no second request at all.