SecFall EN FR

This site is also in English.

Read in English →

← Retour aux projets

é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é.

  1. 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.
  2. 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.
  3. 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.
  4. Retirer un investisseur oblige à publier une nouvelle racine, ce qui casse d’un coup toutes les preuves en attente.
  5. L’arbre ne tient que 16 investisseurs, et l’âge est enregistré mais jamais vérifié.

Le parcours d’un transfert

L’arbre des investisseurs : 16 investisseurs, informations caviardées, une seule racine on-chain on-chain : tout ce que l’on voit racine contrat du token oui la preuve 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Choisissez un investisseur. Son chemin jusqu’à la racine s’allume, les voisins dont la preuve a besoin sont entourés, et le contrat ne voit jamais que la racine.

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");