Consent Docs

Receipts

What a signed consent receipt contains, how it is signed with Ed25519, and how a third party verifies one without the deployment.

Concept3 min readFor architects, security reviewers and auditors

A receipt is the portable form of the proof. It is one signed JSON document that you can hand to an auditor, a regulator or the person. They can check it without access to your project.

What a receipt contains#

  • The record. Its id, the subject hash and type, the status, the environment, the interaction, the jurisdiction and the decisions as they stand. For consent brought in from earlier, the basis you stated and the notice reference. For consent taken by your staff or on paper, how it was taken.
  • The subject fingerprint, unless the record is erased.
  • The form. Its id, version, content hash and name, and the banner wording in the form's default language.
  • The sealed chain. Each event's id, number, type, time, proofHash and previousProofHash.
  • The replay result. The deployment replays every seal with its chain key when it issues the receipt, and writes the outcome into proof.verification.
  • Anchor proofs. For each event whose day is anchored, an inclusion proof and the signed anchor.

The purposes as shown, with their descriptions, data and partners, are not copied in. Neither are the other languages. The content hash covers them, so you compare it with that form version in the console.

How it is signed#

JSON
{
  "receipt": {
    "specification": "piedap-consent-receipt/2.0",
    "consentId": "cns_…"
  },
  "signature": "base64url signature over canonical(receipt)",
  "algorithm": "Ed25519",
  "keyId": "ed…",
  "publicKeysUrl": "https://console.dpconsent.com/consent/sdk/v1/keys"
}

The signature is Ed25519 over the canonical JSON of receipt. Canonical JSON has its keys sorted at every depth. The public key is published as a JWK Set at /sdk/v1/keys, and reading it needs no account.

Where a receipt comes from#

Source Is the export logged
Records, open a record, Export signed receipt Yes, as an EXPORTED event
An access request, or POST /sdk/v1/subjects/export Yes
GET /sdk/v1/consent/{consentId}/verify with a key of the project No. It only reads

Reading does not change evidence. The EXPORTED event is outside the sealed chain.

How a receipt is verified#

There are three ways to check a receipt. The console has Verify a receipt under Records. The API has POST /sdk/v1/receipts/verify, which needs no key. Both work the signature, the links between events and every inclusion proof out again from the document. Nothing in the receipt is trusted.

The third way is offline, and needs nothing from the deployment at the time of the check:

  1. The verifier holds a copy of the JWK Set, fetched once and kept. The key is kty: OKP, crv: Ed25519.
  2. The signature is verified over canonical(receipt) with the key whose kid equals keyId.
  3. The chain links are checked. The first event's previousProofHash is 64 zeros, and each later one equals the proofHash before it. The last proofHash equals receipt.proof.proofHash.
  4. For each entry in receipt.proof.anchors, the leaf is rebuilt and the Merkle root is recomputed from the audit path.
  5. The anchor's statement hash and signature are checked.
  6. Each anchorHash is compared with the copy your webhook received that day.

The last step is the one that does not rely on the operator. Daily anchors explains why.

Who the receipt is about#

A receipt names the person only by a keyed hash and by the subject fingerprint. You recompute the fingerprint from your own identifier and your project's fingerprint key. A match shows the receipt is about the person it is said to be about. An erased record's receipt carries no fingerprint.

Limits#

  • A receipt alone does not re-run the seals. That needs the chain key. A third party relies on the signed proof.verification.
  • The Node helper checks the signature only. verifyReceipt does not walk the chain or the anchors. Those steps are yours to run, or use the API.
  • A changed signing key. The deployment publishes and checks with its current signing key only. After a change, older receipts still verify offline against the key you saved. They no longer pass the console or API check.
  • Receipts signed with HMAC. A receipt issued before signing moved to Ed25519 carries "algorithm": "HMAC-SHA256". Only the deployment that issued it can check it.
  • A test receipt is not evidence. Its environment is test.

↑↓ to move, Enter to open