Technique › Qui a reçu ?

Adresses à usage unique

Les stealth addresses : envoyer de l’argent à une adresse sans l’inscrire sur la blockchain

En bref

L’expéditeur choisit un r aléatoire, publie R = r·G et envoie l’argent à la clé à usage unique P = Hs(8·r·A ‖ i)·G + B. Le destinataire la retrouve en calculant la même chose via a : Hs(8·a·R ‖ i)·G + B. Il la dépense avec la clé privée x = Hs(8·a·R ‖ i) + b.

L’objectif

Nous voulons :

  1. que ni l’adresse du destinataire, ni rien qui se répète d’un paiement à l’autre vers une même personne n’apparaisse sur la blockchain ;
  2. que le destinataire retrouve lui-même ses sorties, sans communiquer avec l’expéditeur ;
  3. que seul le destinataire puisse dépenser la sortie.

La solution est un échange de clés Diffie–Hellman intégré à chaque transaction.

Pas à pas

Expéditeur Destinataire r (aléatoire) R = r·G connaît l’adresse : A, B secret partagé : 8·r·A P = Hs(8rA ‖ i)·G + B sur la chaîne : R et P (et rien d’autre sur l’adresse) a, b (ses clés) voit R dans la transaction secret partagé : 8·a·R = 8·a·r·G = 8·r·A vérifie : Hs(8aR ‖ i)·G + B == P ?

1. L’expéditeur choisit un r aléatoire

Un nouveau r aléatoire (clé privée de transaction) pour chaque transaction. Sa version publique R = r·G (clé publique de transaction) est inscrite dans le champ tx_extra de la transaction.

2. Le secret partagé

L’expéditeur calcule 8·r·A (A est la clé publique de visualisation tirée de l’adresse). Plus tard, le destinataire calcule 8·a·R. C’est le même point, car a·R = a·r·G = r·a·G = r·A. Dans le code de Monero, on l’appelle la key derivation (dérivation de clé).

Le facteur 8 (le cofacteur) « nettoie » le point de toute composante d’ordre faible : même si quelqu’un fournit un R malveillant, le résultat tombe dans le bon sous-groupe.

3. La clé à usage unique

P = Hs( 8·r·A ‖ i )·G + B i est l’indice de la sortie dans la transaction (varint), si bien que deux sorties d’une même transaction obtiennent des clés différentes

C’est l’adresse à usage unique (clé publique de sortie). Elle ressemble à un point aléatoire : sans a ni r, il n’y a rien à quoi la comparer.

4. Le destinataire balaie

Pour chaque nouvelle transaction et chaque sortie i, le portefeuille calcule Hs(8·a·R ‖ i)·G + B et le compare à P. Correspondance — la sortie est à nous. Cela ne demande que la clé de visualisation a et la clé publique B — un portefeuille en lecture seule peut donc trouver les paiements entrants.

5. Le destinataire dépense

La clé privée de la sortie à usage unique :

x = Hs( 8·a·R ‖ i ) + b vérification : x·G = Hs(…)·G + b·G = Hs(…)·G + B = P ✓

Il faut pour cela la clé de dépense b. La clé x signe la dépense, et la key image est calculée à partir d’elle.

Le montant est lui aussi chiffré avec le secret partagé

Un masque pour le montant est dérivé du même secret partagé. Le montant de la sortie (8 octets) est stocké sous la forme amount ⊕ Keccak("amount" ‖ Hs(8rA ‖ i))[0..8], et le facteur d’aveuglement de l’engagement sous la forme Hs("commitment_mask" ‖ Hs(8rA ‖ i)). Le destinataire calcule la même chose et déchiffre le montant. Plus de détails dans engagements de Pedersen.

Points faibles

  • Le balayage coûte cher : une multiplication de point pour chaque sortie de chaque transaction. Partiellement résolu par les view tags.
  • L’attaque Janus : un expéditeur qui soupçonne que deux sous-adresses appartiennent à la même personne peut construire une sortie à partir de morceaux des deux et voir si le destinataire « remarque » le paiement. Les portefeuilles ont une protection partielle ; Carrot la ferme complètement.
  • L’expéditeur connaît r et peut prouver à un tiers qu’il a payé une adresse donnée (parfois utile — une « preuve de paiement »).