étude de cas · 1er prix
Projet Keter
Prouver qu’on a le droit d’échanger un titre réglementé, sans dire qui on est.
Le problème
Les security tokens sont des actions et des obligations qui vivent sur une blockchain. La réglementation impose à l’émetteur de savoir qui les détient ; une blockchain publique montre chaque transfert à tout le monde. Mettez les deux ensemble, et l’identité de chaque investisseur se retrouve exposée.
Nous avions un week-end pour tenter une issue : laisser chaque investisseur prouver qu’il respecte les règles (un pays de l’UE, un type d’investisseur approuvé, un plafond personnel) sans révéler qui il est. Le token ne vérifie que la preuve.
La démarche
La banque valide le KYC de chaque investisseur et range une empreinte de ses informations dans un arbre ; seule la racine de l’arbre va on-chain. Pour un transfert, le navigateur de l’investisseur construit une preuve à divulgation nulle de connaissance que ses informations sont dans l’arbre et respectent les règles, et le contrat du token vérifie cette preuve avant de déplacer quoi que ce soit.
La chaîne publique voit un oui ou un non, jamais la personne. La banque, elle, a validé le KYC : elle connaît ses investisseurs. La confidentialité vaut face à tous les autres, pas face à la banque.
Ce qui tient
- Une preuve ne peut pas resservir : chacune laisse une marque à usage unique que le contrat enregistre.
- Le contrat vérifie que la preuve correspond bien à l’expéditeur, au destinataire et au montant réels avant de l’accepter.
Ce qui ne tient pas encore
Ce que j’ai relevé en relisant le code après l’événement. C’était construit en un week-end, pas audité.
- La vérification peut être contournée. Les fonctions de transfert ordinaires du token déplacent des tokens sans aucune preuve ; seul le transfert avec preuve en vérifie une. Cela suffirait à empêcher tout usage réel.
- La preuve d’un investisseur ne dit rien de la personne qui reçoit. Vérifier le destinataire demande une seconde preuve, et un seul des chemins de transfert l’exige.
- Le contrôle du montant peut être trompé : les montants sont tassés sur 64 bits avant d’être comparés au plafond, donc un montant énorme pourrait boucler et passer.
- Retirer un investisseur oblige à publier une nouvelle racine, ce qui casse d’un coup toutes les preuves en attente.
- L’arbre ne tient que 16 investisseurs, et l’âge est enregistré mais jamais vérifié.
Le parcours d’un transfert
Construit avec
- Preuves
- Circuits Noir, générés dans le navigateur avec NoirJS
- On-chain
- Contrats Solidity (vérificateur et token) sur Sepolia
- Standard de token
- Security tokens ERC-1400
- Application
- Next.js, MetaMask
Voir le circuit
Le cœur du circuit écrit par mon coéquipier (circuit/src/main.nr, condensé). Les informations de l’investisseur restent privées ; seuls la racine, la marque à usage unique, l’expéditeur, le destinataire et le montant sont publics.
let leaf = hash_8([name, surname, age, address, wallet, country_code, investor_type, kyc_face]);
let leaf_final = hash_2([hash_2([leaf, salt]), max_amount]);
assert(nullifier == hash_2([salt, nonce]), "InvalidNullifier");
assert(wallet == sender, "UnauthorizedSender");
// … walk the Merkle path up to merkle_root …
assert(country_valid, "InvalidCountryCode");
assert(amount as u64 <= max as u64, "AllocationLimitExceeded");