Daily anchors
How each day's sealed events are combined into one signed Merkle root, and why keeping your own copy makes the proof independent.
Sealing protects each record. Anchors protect the set. An anchor shows that no event was added, changed or removed after its day closed. If you keep the anchors you are sent, that proof no longer depends on whoever runs the deployment.
How an anchor is built#
At 00:15 UTC the deployment takes each closed UTC day that has sealed events and no anchor. For each project it builds a Merkle tree over that day's events. The construction is the one in RFC 6962, used by Certificate Transparency.
leaf = SHA-256(0x00 || eventId + ":" + consentId + ":"
+ sequence + ":" + proofHash)
node = SHA-256(0x01 || left || right)Events are ordered by time, then by event id. The deployment then signs a statement with the same Ed25519 key that signs receipts:
{
"specification": "piedap-evidence-anchor/1.1",
"projectId": "…",
"periodStart": "2026-09-17T00:00:00.000Z",
"periodEnd": "2026-09-18T00:00:00.000Z",
"eventCount": 1204,
"merkleRoot": "…",
"previousAnchorHash": "…",
"sealedEventTypes": ["CREATED", "UPDATED", "WITHDRAWN", "RENEWED",
"EXPIRED", "ERASED", "LINKED"]
}sealedEventTypes names the event types the tree was built from. It is part of the signed statement, so a verifier knows which events of the day belong in the tree. LINKED is the event written when an identifier is linked to a record.
anchorHash is the SHA-256 of the canonical statement. Each anchor names the anchorHash of the anchor before it. Removing or re-issuing a day therefore breaks every later link.
Why your copy matters#
Each new anchor is sent to your webhooks as evidence.anchored. The event carries the statement, the anchorHash, the signature, the algorithm and the id of the signing key.
Keep the anchors you are sent
Store evidence.anchored deliveries in a system you control. Once you hold a day's root, nobody can change that day's events without the proofs failing against your copy. That includes the operator. Without your copy, you are trusting the deployment's own table.
How an anchor is checked#
In the console, the anchors are under Audit, Evidence anchors. Verify derives an anchor again from the database as it is now and reports four checks:
| Check | What it shows |
|---|---|
| Signature | The statement was signed by the deployment's signing key |
| Statement hash | The stored anchorHash matches the statement |
| Events | The root and the count match the events in the database today |
| Previous anchor | The link to the anchor before is intact |
Anchors made before version 1.1 carry piedap-evidence-anchor/1.0 and no sealedEventTypes. The versions that made them did not seal LINKED events. For such a day, Verify first rebuilds the tree with every event type sealed today. If that does not match, it rebuilds the tree without LINKED. A match then counts as valid, with the note "Sealed by an earlier version that did not seal identifier links".
Stored hashes and signatures are never changed.
A receipt carries an inclusion proof for each event whose day is anchored. The proof holds the leaf, its index, the tree size and the audit path. A verifier recomputes the root from them, as in RFC 9162, section 2.1.3.2. The verifier then compares the root with merkleRoot in the signed statement. Proofs for a day anchored by an earlier version are built from the same events as its anchor.
An example#
A project records 1,204 sealed events on 17 September. At 00:15 UTC on 18 September the deployment signs one root over them and sends it to your endpoint. Your receiver stores the delivery.
A year later an auditor asks about one consent from that day. The receipt carries the inclusion proof for its event. The auditor recomputes the root and compares the anchorHash with the one you stored. A match shows the event was part of that day's set and that the day was not issued again.
Records deleted later#
A record can be deleted by the retention job, or by the 30-day clean-up of test records. The leaf hash of each of its sealed events is kept as a tombstone. The anchor of that day still verifies, and so do inclusion proofs in receipts issued earlier.
Limits#
- Today is not anchored yet. A day's events are covered by the anchor made after the day closes.
- A day with no sealed events has no anchor.
- A missed run catches up. The job anchors every closed day that has no anchor yet. Anchor closed days now in the console does the same for one project.
- A changed signing key. The console check verifies with the current key only. Anchors signed earlier still verify offline against the public key you saved.
- A restore from backup. Consent recorded after the backup is gone. Anchors you stored for those days then show the gap.
- Imported events count as today's. Events imported with a property are anchored on the day of the import.