Skip to content

Independent R&D project · Cologne

How verification works

This page uses the live demonstration passport, not a mock-up. The first part is five steps in plain language: what each one does, and what it does not establish. The second part is the same check as commands, for anyone who would rather run it than read about it.

The five steps

  1. Download the credential

    Every passport page offers its credential as a signed token, a single line of text. Download it. Everything that follows operates on that file, never on the page that displays it.

    This establishes that you hold the same artefact the issuer published. It does not establish that anything in it is true.

  2. Verify the issuer's signature

    The token carries a signature over its contents. A verifier recomputes the check with the issuer's public key; change one character of the contents and the check fails.

    This establishes that the contents have not changed since the holder of that key signed them. It does not establish who holds the key: that is the next step.

  3. Check the issuer's identity

    The issuer is named as did:web:anydpp.eu, which resolves to a public identifier document at anydpp.eu. The key used in step two must be listed there, under the issuer's own name and domain.

    This establishes that the key belongs to whoever controls that domain. It does not establish that the domain's owner is who they claim to be in the real world.

  4. Check the status list

    The credential names a status list, published and signed by the same issuer, and its own position in it. Read the bit at that position: 0 means not revoked, 1 means revoked.

    This establishes whether the issuer has withdrawn the credential since issuing it. It does not establish why, and it does not re-check anything the credential says.

  5. Inspect the evidence references

    The credential lists the source documents it was built from, each by identifier and by a digest of the record, and names the source document behind every attribute and its confidence level. It does not contain the documents themselves.

    This establishes which documents the issuer claims to have read, and lets whoever holds one prove it is the same one. It does not establish that those documents are genuine or that their contents are true.

Open the demonstration passport

How verification works

From the source document to a signature a third party can check. The method, at the level we publish it.

This film: AI-assisted production, human-verified content. Synthetic voiceover in English, captions in five languages. Accessibility and AI transparency

What changed

The first demonstration credentials, issued in July 2026, carried a proof labelled Ed25519Signature2020 whose signed bytes were not that suite's canonical form. A real signature, but one that no standards-based verifier accepted, so the only check available was AnyLAI's own. That proof format is deprecated: the four passports that carry it stay readable as historical evidence, marked as such, with no verify button.

The current demonstration credentials are issued as a signed token in the form the W3C (World Wide Web Consortium) Verifiable Credentials standard defines for enveloping proofs, with the issuer key published under did:web:anydpp.eu and a signed status list for revocation. The steps above, and the commands below, use only that token, that key and that list.

What verification does not establish

A verification that passes every step above answers one question: is this the credential the issuer published, and does the issuer still stand behind it. It does not answer these:

Three different things are easily read as one: cryptographic authenticity, which the steps above establish; the truth of the sources, which only the documents and the people behind them can; and regulatory sufficiency, which is for the operator who places the goods on the market and for the competent authority.

  • The truth of every statement in a source document. A digest proves a document is the one that was read, not that what it says is accurate.
  • Legal compliance. The credential records evidence and states no verdict on whether goods may be placed on any market.
  • The completeness of due diligence. It lists the documents that were read, not the documents an operator's own obligations may still require.
  • Certification of any kind. Nobody has audited the producer, the plot or the consignment on the strength of this credential.
  • Regulator approval. No competent authority has reviewed or endorsed the credential, the issuer or the method.

For engineers: the same check, as commands

Everything below runs without a repository and without an account: three downloads, a verifier of about thirty lines in Node or Python, a tamper test, and the published values to compare against. The commands are not translated; a translated command stops working.

Copy and paste

  1. 1. Download the three artefacts

    # 1. the credential, as a compact JWS (application/vc+jwt)
    curl -s https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt -o DEMO26-000001.jwt
    
    # 2. the issuer's DID document (the public key lives HERE, not in the credential)
    curl -s https://anydpp.eu/.well-known/did.json -o did.json
    
    # 3. the status list credential (an EnvelopedVerifiableCredential around its own JWS)
    curl -s https://anylai.eu/api/corridor/status-list/1 -o status-list-1.json
    
    # what you downloaded
    sha256sum DEMO26-000001.jwt

    The credential is one line of text: header, payload and signature, base64url-encoded and dot-separated. The DID document is fetched from the issuer's own domain, never taken from inside the credential: a forged credential must not be allowed to vouch for itself.

  2. 2. Verify it in Node (one file, one dependency)

    // verify.mjs  --  npm i jose  --  node verify.mjs
    import { compactVerify, importJWK } from "jose";
    import { gunzipSync } from "node:zlib";
    
    const b64 = (s) => Buffer.from(s, "base64url");
    const jws = (await (await fetch("https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt")).text()).trim();
    const header = JSON.parse(b64(jws.split(".")[0]));
    if (header.typ !== "vc+jwt" || header.cty !== "vc") throw new Error("not a vc+jwt");
    
    const did = header.kid.split("#")[0];
    const didDoc = await (await fetch(`https://${did.replace("did:web:", "")}/.well-known/did.json`)).json();
    const method = didDoc.verificationMethod.find((m) => m.id === header.kid);
    if (!method || !didDoc.assertionMethod.includes(header.kid)) throw new Error("kid not under assertionMethod");
    
    const key = await importJWK(method.publicKeyJwk, header.alg);
    const { payload } = await compactVerify(jws, key); // throws JWSSignatureVerificationFailed on a bad signature
    const vc = JSON.parse(Buffer.from(payload).toString("utf8"));
    if (vc.issuer.id !== method.controller) throw new Error("key controller is not the issuer");
    console.log("signature: valid | issuer:", vc.issuer.id, "| kid:", header.kid);
    
    const wrapper = await (await fetch(vc.credentialStatus.statusListCredential)).json();
    const listJws = wrapper.id.replace("data:application/vc+jwt,", "");
    const listHeader = JSON.parse(b64(listJws.split(".")[0]));
    const listMethod = didDoc.verificationMethod.find((m) => m.id === listHeader.kid);
    const listKey = await importJWK(listMethod.publicKeyJwk, listHeader.alg);
    const list = JSON.parse(Buffer.from((await compactVerify(listJws, listKey)).payload).toString("utf8"));
    const encoded = list.credentialSubject.encodedList;
    if (!encoded.startsWith("u")) throw new Error("encodedList is not multibase base64url");
    const bits = gunzipSync(b64(encoded.slice(1)));
    const index = Number(vc.credentialStatus.statusListIndex);
    const revoked = (bits[index >> 3] >> (7 - (index & 7))) & 1;
    console.log("status:", revoked ? "revoked" : "active", "| status list signature: valid");

    Steps two to four of the page above, as code. It throws on a bad signature, on a key that is not listed under the issuer's assertionMethod, on a controller that is not the issuer, and on a status list that is not signed by the same key.

  3. 3. Or in Python

    # verify.py  --  pip install pyjwt cryptography requests  --  python verify.py
    import base64, gzip, json, jwt, requests
    
    jws = requests.get("https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt").text.strip()
    header = jwt.get_unverified_header(jws)
    assert header["typ"] == "vc+jwt" and header["cty"] == "vc"
    
    did = header["kid"].split("#")[0]
    did_doc = requests.get(f"https://{did.removeprefix('did:web:')}/.well-known/did.json").json()
    method = next(m for m in did_doc["verificationMethod"] if m["id"] == header["kid"])
    assert header["kid"] in did_doc["assertionMethod"]
    
    key = jwt.algorithms.OKPAlgorithm.from_jwk(json.dumps(method["publicKeyJwk"]))
    vc = jwt.decode(jws, key=key, algorithms=["EdDSA"])  # raises InvalidSignatureError on a bad signature
    assert vc["issuer"]["id"] == method["controller"], "key controller is not the issuer"
    print("signature: valid | issuer:", vc["issuer"]["id"], "| kid:", header["kid"])
    
    wrapper = requests.get(vc["credentialStatus"]["statusListCredential"]).json()
    list_jws = wrapper["id"].removeprefix("data:application/vc+jwt,")
    list_header = jwt.get_unverified_header(list_jws)
    list_method = next(m for m in did_doc["verificationMethod"] if m["id"] == list_header["kid"])
    list_key = jwt.algorithms.OKPAlgorithm.from_jwk(json.dumps(list_method["publicKeyJwk"]))
    status_list = jwt.decode(list_jws, key=list_key, algorithms=["EdDSA"])
    encoded = status_list["credentialSubject"]["encodedList"]
    assert encoded.startswith("u"), "encodedList is not multibase base64url"
    body = encoded[1:]
    bits = gzip.decompress(base64.urlsafe_b64decode(body + "=" * (-len(body) % 4)))
    index = int(vc["credentialStatus"]["statusListIndex"])
    print("status:", "revoked" if bits[index // 8] >> (7 - index % 8) & 1 else "active", "| status list signature: valid")

    The same check with PyJWT and cryptography. Both verifiers are the reader's own code calling an open-source library; neither calls anything of AnyLAI's except the three public downloads.

  4. 4. Prove the check has teeth

    // tamper.mjs -- change ONE character of the payload segment and the signature check must fail
    import { compactVerify, importJWK } from "jose";
    const jws = (await (await fetch("https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt")).text()).trim();
    const [h, p, s] = jws.split(".");
    const flipped = p.slice(0, 10) + (p[10] === "A" ? "B" : "A") + p.slice(11);
    const didDoc = await (await fetch("https://anydpp.eu/.well-known/did.json")).json();
    const key = await importJWK(didDoc.verificationMethod[0].publicKeyJwk, "EdDSA");
    await compactVerify(`${h}.${flipped}.${s}`, key)
      .then(() => { throw new Error("TAMPERED CREDENTIAL VERIFIED: stop trusting this check"); })
      .catch((e) => console.log("tampered credential rejected:", e.code ?? e.message));

    Feed a credential with one character of its payload changed to the same verifier. If it still passes, the verification is not doing anything and you should stop trusting it. That is the test worth running first.

Published values

Published values for the demonstration credential
Credential (compact JWS)https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jwt
Credential (JSON-LD envelope)https://anylai.eu/api/corridor/passports/DEMO26-000001/credential.jsonld
DID documenthttps://anydpp.eu/.well-known/did.json
Status listhttps://anylai.eu/api/corridor/status-list/1
kiddid:web:anydpp.eu#key-1
alg / typ / ctyEdDSA / vc+jwt / vc
Expected outcomesignature valid; kid under assertionMethod; controller = issuer; status active
SHA-256 of DEMO26-000001.jwtpublished in the release record

The digest is read from the live passport API when this page is viewed, so it names the artefact actually being served; compare it with the output of sha256sum above.

The regulatory radar, once a month

What actually changed in EU deforestation and product-passport rules, with a source for each item and nothing else in the envelope.

Double opt-in: you confirm by clicking a link in your inbox, and you can leave in one click from any issue.

Do it yourself

The demonstration passport is public. Its credential can be checked against the issuer key published at did:web:anydpp.eu with the commands on this page, without an account.