IPFS document integrity
A receipt proves that someone consented. This page is about the other half of the question: what they consented to. The consent document is the §8 disclosure (the purposes, the means, the retention, the recipients, the person in charge, and the rest), and it is the same for everyone: it depends on no citizen. When an immutable version is published, Agreely pins that document to IPFS via Lighthouse and gets back a CID, then anchors that CID on-chain.
A CID proves the document, not the consent
The CID is a public, verifiable copy of the disclosure, nothing more. It does not prove any citizen consented (that proof lives only in the signed receipt, and is never on IPFS) and it contains no personal data. IPFS carries the document; the receipt carries the consent. Never conflate the two.
The pinned payload
What gets pinned is deliberately public and edgeless. It is exactly the disclosure the company must show anyone, and nothing else:
{
"code": "acme-billing-v3",
"name": "Billing and collections",
"version": "3",
"effectiveDate": "2026-06-01",
"disclosure": {
"fins": "...",
"moyensCollecte": "...",
"categories": ["..."],
"conservation": "...",
"retraitConsentement": "...",
"destinataires": ["..."],
"communicationHorsQuebec": "...",
"droits": "...",
"responsable": { "...": "..." },
"decisionAutomatisee": "..."
},
"responsable": { "...": "..." },
"automatedDecision": "..."
}
The payload never carries a DID, a customerId, a salt, a commitment, or a
receipt. It is citizen-agnostic by construction: there is nothing to correlate
and nothing to erase, because there is no person inside it.
Pinning is optional at runtime: without a configured Lighthouse key, it is simply disabled, and a pin failure never fails a publish. Publishing the document therefore does not depend on IPFS being up.
How the CID binds to the consent
The CID enters the consent flow through a document descriptor, signed by the
company, added additively (backward-compatibly) under offer.document in the
signed offer:
{
"offer": {
"document": {
"code": "acme-billing-v3",
"name": "Billing consent v3",
"version": "3",
"effectiveDate": "2026-01-01",
"ipfsCid": "bafybeib..."
},
"disclosureHash": "0x…sha256…"
}
}
Because the descriptor is part of the offer, it is covered by the company's
companySig (see
signatures and passkeys). On approval
it is copied into the receipt under consent.document, so the held receipt
points at the exact disclosure version that was in force.
The on-chain document anchor
keccak256( utf8(cid) ) is anchored on Base as an emit-only, edgeless
document anchor: an opaque bytes32, with no personal data and no link to any
consentRef. See the on-chain registry for
the exact event shape.
Verification
Two optional checks sit on top of the receipt verification recipe. They do not alter any of the four core checks; they harden the what:
- The disclosure copy (
disclosureCopy): the SDK or CLI fetches the IPFS copy at the receipt's CID, computes0x+sha256( JCS(disclosure) ), and compares it to the receipt'sdisclosureHash. A match proves the public copy is the bit-for-bit disclosure the company signed. - The on-chain document anchor (
documentAnchor): the SDK or CLI recomputeskeccak256( utf8(cid) )and checks its presence on-chain. This proves the document existed on-chain, not that any consent was given.
Both are implemented by verifying a receipt in TypeScript.
None of the frozen consent crypto changes
Document integrity is an additive layer. The WebAuthn challenge, the per-cell Merkle root, the signed receipt, and the disclosureHash (still 0x + SHA-256 of the canonicalized disclosure) are unchanged. If IPFS or the chain are offline, a receipt's four core checks still verify; you only lose the hardened confirmation of the what.
Next
- On-chain registry: the exact shape of the document anchor.
- Verify a receipt: where the two optional checks slot in.