Pour créer un compte, je donne presque toujours une adresse email. Un code arrive dans ma boîte, je le recopie, le service affiche « adresse vérifiée ». Plus tard, je me connecte avec cet email et un mot de passe ; le jour où je l’oublie, un lien de réinitialisation repart vers la même boîte. Toute mon identité tient à une seule question : est-ce que je sais lire cette boîte mail ?
Cette question prouve peu. Elle établit que je contrôle une boîte, et non que je suis une personne réelle. Elle a aussi un coût : le service stocke mon adresse, peut m’écrire, me relier d’un service à l’autre, et exposer cette adresse le jour d’une fuite.
Cet article présente une architecture qui pose une autre question. Au lieu de « contrôles-tu cette boîte mail ? », elle demande « es-tu une personne réelle, munie d’une pièce d’identité valide ? », et elle y répond sans lire le nom ni la date de naissance. Le ressort est le même d’un bout à l’autre : une présentation vérifiable (Verifiable Presentation), où un portefeuille (wallet) montre une preuve signée et démontre qu’il en détient la clé. Tout se passe sans adresse email, sans code de vérification, et sans rien conserver de tel.
Le compte par email enchaîne trois rituels : une adresse, un code reçu par email, un mot de passe. Le compte sans email y répond par une présentation de PID à l’inscription, un compte logé dans le wallet, puis un passkey ou un code de secours pour se rattraper.
Ce que prouve le code reçu par email
Le rituel est connu. Le service fabrique un code, l’envoie à l’adresse, et attend que je le recopie. Réussir prouve une chose : à cet instant, je sais lire cette boîte. L’adresse devient alors l’ancre du compte, l’identifiant qui me suit, le canal de réinitialisation, et la clé de rapprochement entre services.
Cette ancre est fragile. Une boîte se partage, se pirate, se perd. Et comme presque tout s’y rattache, en perdre l’accès, un compte suspendu, piraté, ou fermé par la plateforme, revient à se voir couper de presque toute sa vie numérique d’un coup. Elle relie aussi mes comptes entre eux : la même adresse, présentée à dix services, dessine une trace commune. Et elle oblige le service à conserver une donnée personnelle dont il n’a, au fond, pas besoin pour savoir qu’il a affaire à une personne.
Cette approche déplace la preuve. Elle remplace « je contrôle cette adresse » par « je suis une personne, et voici de quoi le prouver ».
Prouver une personne, sans révéler son identité
Le socle, c’est le PID (Person Identification Data), la pièce d’identité numérique du portefeuille européen EUDI, signée par l’État. À l’inscription, le wallet en fait une présentation vérifiable : elle prouve une personne réelle, sans en révéler l’identité. Le fonctionnement de ces présentations, je l’ai détaillé en conférence à DevLille.
Reste à rattacher cette personne à un compte, et un seul. C’est le rôle d’une valeur
dérivée du PID, le nullifier. Le PID porte une graine secrète propre à chaque personne
(seed) ; le service en calcule N = HMAC(seed, scope), où le scope est un
identifiant stable du service. Le résultat est un pseudonyme : déterministe pour une
personne donnée sur ce service, et différent d’un service à l’autre.
Les attributs d’identité du PID restent cachés. Le service ne lit que la graine, dont il dérive le nullifier N = HMAC(seed, scope) : un pseudonyme par personne, qui ancre un seul compte.
Ce pseudonyme fait tout le travail, sans jamais identifier qui que ce soit. La même
personne retombe toujours sur le même N, donc sur le même compte : impossible d’ouvrir
mille comptes pour une seule personne (résistance dite Sybil), et re-présenter son PID
suffit à retrouver son compte (la récupération, plus bas). Comme le scope change d’un
service à l’autre, le pseudonyme vu ici ne se recoupe pas avec celui d’ailleurs.
Un compte que personne ne détient à votre place
Une fois l’éligibilité prouvée, le service émet le compte lui-même. Il ne s’appuie sur
aucun fournisseur d’identité tiers : il fabrique un Verifiable Credential à lui,
l’AccountCredential, et le remet au wallet.
Ce credential est, lui aussi, un SD-JWT VC. Son sujet, account_subject, est
exactement le nullifier N calculé plus haut, et il est rattaché à la clé du wallet par
un bloc cnf (holder binding) : seul le porteur de cette clé pourra le présenter plus
tard.
{
"iss": "https://compte.example",
"sub": "Zk9P2x7nQ1cE...",
"vct": "https://compte.example/credentials/account",
"cnf": {
"jwk": { "kty": "EC", "crv": "P-256", "x": "...", "y": "..." }
},
"iat": 1783584000,
"exp": 1815120000
}
À l’émission, le wallet prouve qu’il détient bien la clé qu’il déclare, puis reçoit le credential. Le détail des échanges importe peu ici ; le point qui compte est ce que le service garde de tout cela.
Côté serveur, l’ancre du compte est le haché de account_subject, et non sa valeur en
clair. C’est ce haché qui pointe vers un user_id interne. Au total, le service
conserve trois éléments.
{
"subject_hash": "sha256:1f6e...",
"user_id": "b1c0e7...",
"holder_thumbprint": "sha256:Nzb..."
}
Il ne garde ni adresse email, ni mot de passe, ni le DID du wallet, ni le moindre attribut du PID, ni la graine dont le nullifier est tiré. L’identité du compte tient à deux choses que le porteur seul peut réunir : un credential rangé dans son wallet, et la clé privée qui va avec.
Le compte est rattaché à la clé du wallet par le bloc cnf. Côté serveur, account_subject (le nullifier N) est haché vers un user_id interne. L’email, le DID du wallet et les attributs du PID ne sont jamais stockés.
Bout à bout, l’inscription ressemble à ceci avec un vrai wallet, le téléphone (Altme) à droite et le navigateur à gauche : on présente le PID, le compte est créé, les codes de secours s’affichent, la carte de compte arrive dans le wallet, et la note de démonstration « test-alice » est enregistrée.

Inscription en conditions réelles, wallet à droite et navigateur à gauche : présentation du PID, compte créé, codes de secours, session connectée et note « test-alice » enregistrée.
Se connecter en présentant sa carte de compte
Se connecter revient alors à présenter ce credential. Le wallet montre
l’AccountCredential et une preuve fraîche de possession de la clé, et le service
vérifie la même chaîne qu’à l’inscription : sa propre signature sur le credential, puis
la preuve que la clé du wallet correspond au bloc cnf. Il en extrait account_subject,
le hache, retrouve le user_id, et ouvre la session. La session s’ouvre ainsi sans
qu’un mot de passe circule, et sans code à recopier.
On peut aussi envisager de se connecter par passkey. Une fois connecté, le porteur en enregistre un
(WebAuthn) sur son appareil : la clé privée reste dans l’appareil, le serveur ne garde
que la clé publique, rattachée au user_id. Le présenter rouvre la session directement,
sans nom d’utilisateur à saisir. Le credential du wallet reste l’identité portable ; le
passkey est un second chemin, plus rapide, lié à un appareil.
À l’inscription, la preuve du PID et l’émission du compte s’enchaînent dans la foulée : l’utilisateur scanne une fois, et la suite se déroule dans le wallet. Le détail des échanges entre le wallet et le service sort, lui aussi, du cadre de cet article (voir la conférence évoquée plus haut).
La connexion, elle aussi en conditions réelles, donne ceci :

Connexion : le wallet présente l’AccountCredential (à droite), la session s’ouvre et la note « test-alice » est toujours là (à gauche, entourée) ; le wallet journalise la présentation dans son activité.
Garder l’accès sans email
La force du nullifier se voit le jour où l’on perd tout. Le N ne dépend ni de
l’appareil ni de la clé du wallet : il suit la personne. Récupérer son PID auprès de
l’État sur un téléphone neuf, le re-présenter, et le service recalcule le même N,
retrouve le même compte, et ré-émet une AccountCredential vers la nouvelle clé. La
récupération se fait ainsi sans aucun secret conservé d’avance.
Reste un filet de dernier recours, pour rouvrir une session quand ni le wallet ni le
passkey ne sont sous la main. À l’inscription, le service remet une fois des codes de
secours à usage unique. C’est le mécanisme des recovery codes, répandu dans l’industrie : GitHub,
Google et bien d’autres les proposent en repli de l’authentification à deux facteurs.
Huit codes au format ABCDE-FGHJK, tirés d’un alphabet qui exclut les caractères
ambigus (les lettres I et O, les chiffres 0 et 1). Le serveur n’en conserve que le
haché, salé par un pepper. Présenter un code valide ouvre une session, et le code est
aussitôt consommé : il ne sert qu’une fois.
ABCDE-FGHJK
LMNPQ-RSTUV
WXYZ2-3456A
...
La récupération, justement, se voit le mieux en action : on re-présente le PID, le compte est recréé, et la session rouvre sur le même identifiant avec la note « test-alice » intacte.

Récupération, wallet à droite et navigateur à gauche : re-présenter le PID rouvre le même compte, même identifiant et note « test-alice » intacte (entourée).
Limites
Deux limites de fond tiennent à la maturité de l’écosystème, une troisième est un compromis assumé.
- Le PID provient d’un service d’émission de test séparé, jouant un faux « État ». Le service ne fait ici que le vérifier, en épinglant la clé publique de cet émetteur. En production, la confiance reposerait sur les vraies racines de l’écosystème EUDI, et sur une révocation réelle.
- L’ancre du compte est un nullifier
N = HMAC(seed, scope), dérivé d’une graine par-personne que le PID EUDI standard ne porte pas encore (on l’ajoute au PID de test). Surtout, sans preuve à divulgation nulle (zero-knowledge), cette graine est transmise en clair au service : des services qui la voient pourraient relier la même personne, malgré des nullifiers distincts. L’unlinkability complète suppose un nullifier prouvé en zero-knowledge (par exemple BBS#), une approche proposée dans l’ARF du portefeuille EUDI, pas encore validée. - Se passer d’email supprime aussi le canal pour joindre l’utilisateur : les notifications, alertes de sécurité et relances par email disparaissent. Contrairement aux deux précédentes, cette limite ne tient pas à la maturité de l’écosystème ; c’est un compromis de fond, et contacter l’utilisateur supposerait un canal séparé, explicitement consenti (ce n’est pas toujours un mal : l’utilisateur garde le choix de donner une adresse, ou non).
J’ai testé le flux de bout en bout avec un vrai wallet (Altme, profil DIIP) ; la mécanique fonctionne.
Conclusion
On a fait de l’email une pièce d’identité, et c’est une anomalie. Pour « créer un compte », on réclame aujourd’hui une adresse email, c’est-à-dire un compte chez Google ou Apple : un compte que l’utilisateur ne possède pas vraiment, qui devient la clé de toute sa vie numérique, et son premier point de compromission. Cette dépendance est devenue si banale qu’on ne la remarque plus.
L’approche explorée ici prend une autre voie. L’utilisateur prouve une seule fois qu’il est une personne réelle, au moyen de son portefeuille d’identité (le futur wallet EUDI). De cette preuve est dérivé un nullifier, une empreinte déterministe, propre à cette personne et à ce service. Un seul objet remplit alors trois fonctions :
- l’inscription ;
- la résistance à la création de faux comptes (résistance Sybil), pour les services qui la souhaitent ;
- la récupération en cas de perte totale : re-présenter la preuve suffit pour que le service reconnaisse son titulaire, sans email, sans mot de passe et sans secret conservé, depuis un appareil neuf.
Côté serveur, il n’y a aucune adresse Google en jeu, et aucune base de mots de passe à dérober.
Est-ce prêt pour la production ? Non. Le PID EUDI n’embarque pas encore l’attribut nécessaire, et la brique zero-knowledge qui scellerait l’ensemble en est encore au stade de la discussion à Bruxelles.
Code source :
- wallet-login : le POC décrit ici (signup, login, récupération).
- pid-issuer : le faux émetteur « État » qui délivre le PID de test, fourni pour pouvoir rejouer le flux de bout en bout.