Skip to content

External anchoring

Layers 1–2 of the evidence stack prove that history is internally consistent. Anchoring proves something stronger: that the hub couldn’t rewrite history even if it wanted to, because commitments to that history live in an independent append-only ledger the hub doesn’t control.

  • The anchor target MUST be an append-only ledger with: signed tree heads (SP/1 reference: Ed25519 over the tree size + Merkle root), inclusion proofs, and consistency proofs — all verifiable client-side. The reference implementation anchors into Engrave.
  • The hub MUST pin the ledger’s public signing key at provisioning time (no trust-on-first-use).
  • Anchored payloads MUST contain no personal data and no linkable subject identifiers — event type, entity id, fingerprint, origin domain, page host, and timestamp only. (Even privacy-preserving identifiers like email blind indexes MUST NOT be anchored.)
  • Anchoring MUST be fail-open: ledger unavailability never delays or blocks a consent flow. A missed per-event anchor is acceptable because of checkpoints (below).

Grants, revocations, cease acknowledgements, and certificate issuance are anchored individually:

{
"v": "1",
"event": "cert.issued",
"entity_id": "cc_…",
"fingerprint": "<sha-256 fingerprint>",
"origin_domain": "example.com",
"page_host": "example.com",
"ts": "2026-07-28T16:02:38.455Z"
}

Ledger keys are hierarchical and deterministic: pryvc/cert/{id}/issued, pryvc/share/{id}/granted, pryvc/share/{id}/revoked, pryvc/share/{id}/ceased.

The hub stores the returned receipt (transaction id, entry index, entry hash, tree size, root) and MUST expose it on the certificate’s public verification surfaces.

On a fixed cadence (reference: every 15 minutes) the hub anchors the audit chain head:

{
"v": "1",
"audit_seq": "162",
"chain_head": "<row_hash at seq 162>",
"from_seq": "1",
"api": "https://api.pryvc.com",
"ts": "2026-07-28T16:15:21.103Z"
}

One checkpoint commits to every audit row up to audit_seq — so even an event whose individual anchor write failed is externally timestamped within one checkpoint interval.

Each cycle the hub MUST fetch a consistency proof between its last-stored tree head and the ledger’s current head and verify it locally (pure Merkle computation + the pinned public key — never by asking the ledger’s own server to attest to itself). A failed proof indicates ledger rewrite and MUST be recorded as a first-class audit event (anchor.consistency_failed in the reference implementation).

To dispute a record someone must now: break the SP/1 hash chain, and forge Ed25519-signed Merkle history in a second organization’s ledger, and do so consistently with every checkpoint interval since the record was made. The verifier needs the certificate URL, the ledger tx reference, the pinned public key, and ~30 SHA-256 computations.