Comment fonctionne la vérification
Cette page utilise le passeport de démonstration en ligne, pas une maquette. La première partie, ce sont cinq étapes en langage clair : ce que chacune fait et ce qu'elle n'établit pas. La seconde partie est la même vérification sous forme de commandes, pour qui préfère l'exécuter plutôt que la lire.
Les cinq étapes
Téléchargez le justificatif
Chaque page de passeport propose son justificatif sous forme de jeton signé, une seule ligne de texte. Téléchargez-le. Tout ce qui suit opère sur ce fichier, jamais sur la page qui l'affiche.
Cela établit que vous détenez le même artefact que celui que l'émetteur a publié. Cela n'établit pas que quoi que ce soit dedans est vrai.
Vérifiez la signature de l'émetteur
Le jeton porte une signature sur son contenu. Un vérificateur refait le calcul avec la clé publique de l'émetteur ; changez un caractère du contenu et la vérification échoue.
Cela établit que le contenu n'a pas changé depuis que le détenteur de cette clé l'a signé. Cela n'établit pas qui détient la clé : c'est l'étape suivante.
Vérifiez l'identité de l'émetteur
L'émetteur est nommé did:web:anydpp.eu, ce qui se résout en un document public d'identification sur anydpp.eu. La clé utilisée à l'étape deux doit y figurer, sous le nom et le domaine propres de l'émetteur.
Cela établit que la clé appartient à qui contrôle ce domaine. Cela n'établit pas que le propriétaire du domaine est, dans le monde réel, celui qu'il prétend être.
Vérifiez la liste de statut
Le justificatif nomme une liste de statut, publiée et signée par le même émetteur, et sa propre position dans cette liste. Lisez le bit à cette position : 0 signifie non révoqué, 1 signifie révoqué.
Cela établit si l'émetteur a retiré le justificatif depuis son émission. Cela n'établit pas pourquoi, et ne revérifie rien de ce que le justificatif dit.
Examinez les références de preuve
Le justificatif liste les documents sources à partir desquels il a été construit, chacun par identifiant et par une empreinte de l'enregistrement, et nomme le document source derrière chaque attribut ainsi que son niveau de confiance. Il ne contient pas les documents eux-mêmes.
Cela établit quels documents l'émetteur déclare avoir lus, et permet à qui en détient un de prouver que c'est le même. Cela n'établit pas que ces documents sont authentiques ni que leur contenu est vrai.
Comment fonctionne la vérification
Du document source à une signature qu'un tiers peut contrôler. La méthode, au niveau où nous la publions.
Ce film : production assistée par IA, contenu vérifié par des humains. Voix de synthèse en anglais, sous-titres en cinq langues. Accessibilité et transparence de l'IA
Transcription
A passport is worth exactly what stands behind it.
Everything downstream of that is presentation.
Every attribute starts at a source document.
A check compares the published value against that document, deterministically.
The same document and the same rule always give the same answer, and a model never decides the fact.
The check does not return a verdict.
It returns a confidence level, and the reason for it.
A confidence level can be argued with.
A verdict can only be believed or not.
High confidence flows.
Low confidence stops, and it stops the whole passport rather than its own line, until a registered human attester resolves it under their own name.
Every step is written to a hash chain.
Change one entry and every entry after it stops matching, so tampering is detectable rather than deniable.
Nothing is deleted to make the record look better, because a deletion would break the same chain.
And then the part that matters.
A third party checks the credential's own signature, without access to AnyLAI's database.
The verifier resolves the issuer's public identifier document and checks the signature against it.
That is the step that does not rest on taking our word for it.
European rules place the final judgment with people who answer for it.
AnyDPP is built the same way. Where confidence is low, a named, registered professional decides, and the decision is recorded.
Ce qui a changé
Les premiers justificatifs de démonstration, émis en juillet 2026, portaient une preuve étiquetée Ed25519Signature2020 dont les octets signés n'étaient pas la forme canonique de cette suite. Une vraie signature, mais qu'aucun vérificateur fondé sur les standards n'acceptait, de sorte que la seule vérification disponible était celle d'AnyLAI. Ce format de preuve est obsolète : les quatre passeports qui le portent restent lisibles comme preuve historique, marqués comme tels, sans bouton de vérification.
Les justificatifs de démonstration actuels sont émis sous forme de jeton signé, dans la forme que le standard W3C (World Wide Web Consortium) des Verifiable Credentials définit pour les preuves enveloppantes, avec la clé de l'émetteur publiée sous did:web:anydpp.eu et une liste de statut signée pour la révocation. Les étapes ci-dessus et les commandes ci-dessous n'utilisent que ce jeton, cette clé et cette liste.
Ce que la vérification n'établit pas
Une vérification qui passe toutes les étapes ci-dessus répond à une question : est-ce le justificatif que l'émetteur a publié, et l'émetteur le soutient-il encore. Elle ne répond pas à celles-ci :
Trois choses différentes se lisent facilement comme une seule : l'authenticité cryptographique, que les étapes ci-dessus établissent ; la vérité des sources, que seuls les documents et les personnes derrière eux peuvent établir ; et la suffisance réglementaire, qui revient à l'opérateur qui met les marchandises sur le marché et à l'autorité compétente.
- La vérité de chaque énoncé d'un document source. Une empreinte prouve qu'un document est celui qui a été lu, pas que ce qu'il dit est exact.
- La conformité juridique. Le justificatif consigne des preuves et ne rend aucun verdict sur la possibilité de mettre des marchandises sur un marché quelconque.
- L'exhaustivité de la diligence raisonnée. Il liste les documents qui ont été lus, pas les documents que les obligations propres d'un opérateur peuvent encore exiger.
- Une certification de quelque nature que ce soit. Personne n'a audité le producteur, la parcelle ou l'envoi sur la foi de ce justificatif.
- Une approbation réglementaire. Aucune autorité compétente n'a examiné ni cautionné le justificatif, l'émetteur ou la méthode.
Pour les ingénieurs : la même vérification, en commandes
Tout ce qui suit s'exécute sans dépôt et sans compte : trois téléchargements, un vérificateur d'une trentaine de lignes en Node ou en Python, un test de falsification et les valeurs publiées pour comparer. Les commandes ne sont pas traduites ; une commande traduite cesse de fonctionner.
Copy and paste
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.jwtThe 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. 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. 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. 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
| 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 document | https://anydpp.eu/.well-known/did.json |
| Status list | https://anylai.eu/api/corridor/status-list/1 |
| kid | did:web:anydpp.eu#key-1 |
| alg / typ / cty | EdDSA / vc+jwt / vc |
| Expected outcome | signature valid; kid under assertionMethod; controller = issuer; status active |
| SHA-256 of DEMO26-000001.jwt | published 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.
Le radar réglementaire, une fois par mois
Ce qui a réellement changé dans les règles européennes sur la déforestation et le passeport produit, avec une source par point et rien d'autre dans l'enveloppe.
Double opt-in: vous confirmez en cliquant un lien dans votre boîte, et vous partez en un clic depuis n'importe quel numéro.
Faites-le vous-même
Le passeport de démonstration est public. Son justificatif peut être vérifié contre la clé de l'émetteur publiée sous did:web:anydpp.eu avec les commandes de cette page, sans compte.
