Skip to main content

Verify

How verification works

An OptProof receipt is proof you can check yourself. You need three public things: the receipt, the ledger records, and OptProof's published public keys.

  1. 1

    The receipt is signed

    When you give or withdraw consent, OptProof signs a small receipt with an Ed25519 key. It says what happened, for which purposes, under which notice version, and when. It contains no personal data.

  2. 2

    Every record is fingerprinted

    Each consent record in the ledger has a SHA-256 fingerprint. The fingerprint covers the record and the fingerprint of the record before it.

  3. 3

    Records form a chain

    Because each fingerprint includes the previous one, changing, removing or reordering any past record breaks every fingerprint after it.

  4. 4

    A daily anchor seals the chain

    Every day at 00:30 IST, OptProof signs the latest fingerprint of each chain and stores it in write-once storage that nobody, including OptProof, can overwrite.

Each record points to the one before it. The daily anchor seals the latest record.

What the verifier checks

  1. 1The receipt is signed by OptProof
  2. 2It matches a record in the consent ledger
  3. 3Each record's fingerprint matches its contents
  4. 4The records link up in order, with none missing
  5. 5The daily anchor is signed by OptProof
  6. 6The record is sealed by the daily anchor

What a valid receipt shows, and what it does not

A valid receipt shows that OptProof recorded this consent event at this time and that the record has not been changed since.

It does not say who the person was beyond a pseudonymous reference, and it is not a legal opinion on the notice or the consent.

Records newer than the latest daily anchor show as waiting for the daily seal. Until then, the receipt you hold is the main protection.

Check it yourself

You do not need OptProof software. Any Ed25519 and SHA-256 tool works. The sketch below uses only Node.js built-ins.

verify.mjs
js
// node verify.mjs OP-XXXX-XXXX   (Node.js 20+, no dependencies)import { createHash, createPublicKey, verify } from 'node:crypto';​const api = 'https://api.optproof.in';const code = process.argv[2];const data = await (await fetch(`${api}/v1/public/receipts/${code}`)).json();const { keys } = await (await fetch(`${api}/.well-known/optproof-keys.json`)).json();const b64 = (s) => Buffer.from(s, 'base64url');​function checkJws(jws, typ) {  const [h, p, s] = jws.split('.');  const header = JSON.parse(b64(h));  const jwk = keys.find((k) => k.kid === header.kid);  if (!jwk || header.alg !== 'EdDSA' || header.typ !== typ) return null;  const key = createPublicKey({ key: { kty: 'OKP', crv: 'Ed25519', x: jwk.x }, format: 'jwk' });  return verify(null, Buffer.from(`${h}.${p}`), key, b64(s)) ? JSON.parse(b64(p)) : null;}​// 1. Receipt signature (use your own copy of the receipt if you have one)const receipt = checkJws(data.receipt_jws, 'optproof-receipt+jws');// 2. Each record: SHA-256(previous hash bytes || canonical JSON) must equal its hashconst hashes = data.segment.every((r) =>  createHash('sha256').update(Buffer.from(r.prev_hash, 'hex')).update(r.canonical, 'utf8').digest('hex') === r.hash);// 3. Records link in orderconst links = data.segment.every((r, i) => i === 0 ||  (r.prev_hash === data.segment[i - 1].hash && BigInt(r.seq) === BigInt(data.segment[i - 1].seq) + 1n));// 4. The receipt names the first recordconst matches = receipt?.h === data.segment[0].hash && receipt?.seq === data.segment[0].seq;// 5–6. The signed daily anchor seals a record of this chainconst anchor = data.anchor && checkJws(data.anchor.jws, 'optproof-anchor+jws');const sealed = Boolean(anchor) && data.segment.some((r) => r.seq === anchor.last_seq && r.hash === anchor.last_hash);​console.log({ receipt: Boolean(receipt), matches, hashes, links, anchor: data.anchor ? sealed : 'pending' });

Or use the OptProof Node SDK, which runs the full algorithm: verifyReceipt() in @optproof/node.

Check a receipt