Independent verification

Check a passport against the Bitcoin blockchain

Every published passport version is folded into a Merkle tree and the root is timestamped into the Bitcoin blockchain through the public OpenTimestamps calendars. This page recomputes that chain in your browser and reads the block from a public explorer we do not run. Nothing below depends on trusting DPPLive — that is the point.

Leave both extra fields empty to check the current passport. To re-check an older version you are holding a proof for, enter its version number and the commitment printed in that proof’s manifest — past versions are not enumerable, so that a passport’s full edit timeline stays private.

Verify it without this page

A verification tool written by the party being verified is worth very little. So the proof is emitted in the standard OpenTimestamps format. Download both files and check them with the reference client, which has nothing to do with us:

pip install opentimestamps-client

curl -o manifest.json     "https://api.dpplive.com/anchor/<UPID>/manifest.json"
curl -o manifest.json.ots "https://api.dpplive.com/anchor/<UPID>/proof.ots"

ots verify manifest.json.ots

A successful run prints Success! Bitcoin block <height> attests existence as of <date>. Then check that public_sha256 inside manifest.json equals the SHA-256 of the passport’s public JSON — that is the one step tying the timestamp to this specific product.

The specification

Frozen under the name dpplive-anchor-v1. Any change to it gets a new name, and proofs already issued keep verifying under the old one.

What is committed

Not the raw document hash. The full published version contains fields the public view strips (chemical detail, supplier chain, importer EORI), so an outsider could never recompute that hash — a proof nobody can check is worth nothing. Instead each version commits to a small manifest binding both views:

manifest = {
  "alg":           "dpplive-anchor-v1",
  "full_sha256":   sha256(canonicalJson(full published version)),
  "public_sha256": sha256(canonicalJson(public projection)),
  "published_at":  "YYYY-MM-DD HH:MM:SS",   // UTC, verbatim from the database
  "upid":          "...",
  "version":       n
}

commitment = sha256(canonicalJson(manifest))

A buyer hashes the public JSON they can actually see, matches public_sha256, and walks the rest. full_sha256 rides along so the unredacted document can still be proven later in a dispute — against a commitment made before the dispute existed. The manifest itself leaks nothing: two hashes, an identifier, a version, a date.

The public projection is frozen at anchor time and served back verbatim at /anchor/<UPID>/manifest.json’s sibling field public_json. It has to be: the list of fields DPPLive publishes changes as the regulation and our own review of it change, and re-deriving the hash from today’s list would make every older proof read as “does not match” — a false accusation against a passport nobody touched. So this page hashes the frozen copy, then separately compares it against the live passport and tells you which of the two kinds of difference it found: a changed value (the record moved — a real problem) or only a changed set of visible fields (we changed what we publish).

Past versions are not enumerable. Asking for ?v=N requires also presenting that version’s commitment, which anyone holding the proof already has. Without that, this endpoint would hand out every passport’s complete edit timeline to anyone who counted upwards.

canonicalJson

canonicalJson(v) = JSON.stringify(sort(v))
  sort(array)  = array.map(sort)                       // order preserved
  sort(object) = keys sorted ascending (UTF-16 code units), values sorted recursively
  sort(other)  = unchanged

Merkle tree

leaf(c)    = SHA256( 0x00 || c )          c = 32-byte commitment
node(l, r) = SHA256( 0x01 || l || r )
odd node   = promoted unchanged to the next level (never duplicated)
leaf order = commitments sorted ascending as lowercase hex
root of one leaf = that leaf

The 0x00/0x01 prefixes stop an internal node being passed off as a leaf (the standard second-preimage attack on naive Merkle trees). Promoting the odd node instead of duplicating it avoids Bitcoin’s own CVE-2012-2459 ambiguity, where two different trees can produce one root.

A path step records where the sibling sits: {"d":"l","h":…}next = node(h, cur); {"d":"r","h":…}next = node(cur, h).

Anchoring

The root is submitted to four independent OpenTimestamps calendars every six hours. They aggregate it with everyone else’s digests into a single Bitcoin transaction and pay for it themselves — there is no wallet, no gas, and no private key anywhere in this system. Once the transaction confirms (typically 1–6 hours) we upgrade the pending proof into a permanent one and stop depending on the calendars at all.

What this proves — and what it does not

It does prove

  • This exact passport content existed no later than the timestamp of that Bitcoin block.
  • It has not been altered since — any edit changes the hash and the old proof stops matching.
  • DPPLive could not have back-dated it. We cannot move a Bitcoin block either.
  • Each republished version is separately committed, so the version history is provable, not just the latest state.

It does not prove

  • That the data is true. If a supplier enters a false fibre composition, the blockchain faithfully preserves the false statement. Timestamping is an integrity control, not an audit.
  • That any certificate referenced in the passport is genuine — check those with the issuing body.
  • That the physical product in your hand is the one described. That is the job of the data carrier and the identifiers, not of the timestamp.

No personal data and no commercial data is written to the blockchain — only hashes. That is deliberate: content published to a public chain cannot be erased, which would collide with the GDPR right to erasure. Everything erasable stays in our database, where it can actually be erased.

Neither the ESPR (Regulation (EU) 2024/1781) nor Implementing Regulation (EU) 2026/1778 requires a blockchain. This is a voluntary integrity control on top of the hash and trusted timestamp those texts do ask for — not a compliance claim.

DPP Guide Regulation Features
DPP Guide Regulation Features