Comment générer un wallet Ethereum en JavaScript

tutoriel
javascript

A moins que vous ne viviez dans une grotte, vous avez forcément entendu parler de l'Ethereum, la crypto-monnaie la plus populaire après le Bitcoin ! Et si vous vous intéressez un minimum au monde de la blockchain et des cryptomonnaies, vous possédez peut-être un wallet Ethereum, constitué:

  • d'une adresse publique qui vous permet de recevoir des ETH (ou autre token déployé sur cette blockchain)
  • d'une clé privée qui vous permet d'en envoyer

Aujourd'hui, on va plonger un peu dans la théorie et décortiquer ce qui se cache derrière ces clés, puis on mettra ces connaissances en pratique et on écrira quelques lignes de code JavaScript (plus précisément Node.js) qui nous permettront de générer un wallet Ethereum 😎

Comment on crée une adresse Ethereum ?

Le premier point à bien comprendre, c'est que créer une adresse est une opération qui n’interagit pas avec la blockchain. Ce n'est pas du tout comme se "créer un compte" sur un site, dans lequel on choisirait un identifiant et un mot de passe.

Le processus général est plutôt l'inverse de ce qu'on trouve sur un site web: on choisit juste un mot de passe très long, si possible le plus aléatoire possible, qu'on appelle "clé privée", et c'est tout. À chaque clé privée correspond une et une seule adresse associée, qu'on peut calculer mathématiquement, mais heureusement, cette opération ne peut être faite que dans un sens: il est impossible de trouver la clé privée associée à une adresse.

Par conséquent, le terme "créer une adresse" est trompeur, car on ne crée rien du tout, en réalité on choisit juste un très grand nombre au hasard, parmi les milliards de milliards de possibilités, et on espère que personne ne choisira jamais le même.

Le nombre de possibilités pour une clé privée Ethereum est de 2²⁵⁶, qui équivaut à peut près à 10⁷⁷ en décimal, c'est-à-dire un nombre à 77 chiffres 🤯

À titre de comparaison, on estime que l'univers visible contient entre 10⁷⁸ et 10⁸⁰ atomes. Il y a donc presque assez de clés privées pour en donner une différente à chaque atome de l'univers. Si vous choisissez une clé privée vraiment au hasard, il n'y a aucun moyen concevable pour que quelqu'un la devine ou la choisisse lui-même.

Une clé privée Ethereum est composée de 32 octets, ni plus ni moins. Pour rappel, un octet est un nombre composé de 8 chiffres binaires, soit une suite de 8 0 et 1. On peut convertir un octet en nombre décimal, ce qui donnera un nombre entre 0 et 255, mais on peut aussi l'écrire sous forme hexadécimale, où chaque "chiffre" va de 0 à F (et non pas de 0 à 9), ce qui est le plus pratique car deux chiffres suffisent pour représenter un octet:

Forme binaireForme DécimaleForme Hexadécimale
0000000000000
000111100301E
10110100180B4
11111111255FF

Pour illustrer cet article, je vais choisir une clé privée au hasard, c'est à dire un gros nombre de 32 octets complètement au pif. Je vais utiliser l'écriture hexadécimale, donc ça fera donc 64 caractères au hasard parmi la liste suivante: 0123456789ABCDEF (1 octet = 2 caractères hexadécimaux)

Voici les 32 octets que j'ai choisi, sous forme hexadécimale (les espaces servent juste à améliorer la lisibilité):

D6 8C 24 FB BF 36 8F F1 45 C8 43 FC 4C 58 4B D2 84 0B FD 26 12 37 9F 5D 68 18 E8 9F 0B CF 37 91

Trouver l'adresse à partir de la clé privée

Normalement, j'ai déjà tout ce qu'il faut pour effectuer des transactions ! Je pourrais commencer à envoyer des ETH à d'autres adresses, mais a priori ce nouveau wallet est vide 😕

Il faut d'abord que je reçoive des fonds, et pour cela, je dois déterminer l'adresse unique associée à ma clé privée. Je pourrai ensuite partager cette adresse à des tiers, ou m'en servir pour consulter mon solde et la liste de mes transactions.

La méthode permettant de déterminer l'adresse à partir de la clé privée est une opération à sens unique: on peut facilement trouver l'adresse à partir de la clé privée, mais il est impossible de faire l'inverse. Le seul moyen de retrouver la clé privée initiale est d'essayer toutes les clés privées possibles, ce qui évidemment n'est pas possible vu le nombre de possibilités.

Il y a deux étapes pour déterminer l'adresse publique à partir de la clé privée:

1. Déterminer la clé publique

L'algorithme permettant de convertir une clé privée en clé publique s'appelle ECDSA, qui signifie "Elliptic Curve Digital Signature Algorithm".

Pour la faire courte, c'est un algorithme qui utilise une courbe elliptique afin de transformer n'importe quel nombre en un point sur la courbe de notre choix . Une courbe elliptique, c'est une courbe définie par l'équation y² = x³ + ax + b. Il existe un paquet de courbes elliptiques différentes, mais Ethereum utilise une courbe bien spécifique qui porte le joli nom de "secp256k1".

Ce qu'on va faire, c'est utiliser appliquer ECDSA à notre clé privée de 32 octets pour obtenir un point sur la courbe elliptique. A partir de ce point, on pourra obtenir un nombre de 65 octets, dont le premier octet est toujours 04 (c'est une norme), les 32 octets suivants sont l'abscisse X, et les 32 autres octets sont l'ordonnée Y de notre point.

Si on retire le préfixe 04 qui ne sert à rien, on obtient notre clé publique, de 64 octets, soit 128 caractères hexadécimaux.

const secp256k1 = require('secp256k1');

const privateKey = 'D68C24FBBF368FF145C843FC4C584BD2840BFD2612379F5D6818E89F0BCF3791';

// Je convertis ma clé hexadécimale en octets (format binaire) const bytes = Buffer.from(privateKey, 'hex');

// Je trouve la clé publique en utilisant ECDSA avec la courbe secp256k1 const publicKey = secp256k1.publicKeyCreate(bytes, false);

// J'affiche ma clé publique au format hexadécimal Buffer.from(publicKey).toString('hex');

Vous pouvez cliquer sur "Run" pour exécuter le code, et obtenir la clé publique, qui est donc la suivante une fois le préfixe 04 retiré:

BA 35 70 8E E7 EE C3 24 EA 61 8C 92 D0 21 CA 50 4C 67 4B 99 58 35 A8 05 57 64 3A 14 27 A1 E3 30 56 AA 6C 1D 2C B4 63 5F DF F7 E0 4C 75 9A 25 26 48 1F 3F D1 D7 A9 8D 1D 40 57 53 3D B0 A8 CB 6F

2. Trouver l'adresse correspondante à la clé publique

Nous n'en sommes qu'à la moitié du boulot. Afin de renforcer le côté "non-réversible" de l'opération, il va falloir passer notre clé privée dans un algorithme de hash, en l’occurrence "keccak-256", qui fait partie de la famille de SHA-3.

Pour rappel, une fonction de hash prend en entrée une donnée de n'importe quelle taille (quelques octets, ou des milliards), et renvoie une chaîne de caractères de longueur fixe (un hash) qui sera toujours le même pour une entrée donnée. La moindre variation des données d'entrée va complètement changer le hash, et cette fonction est encore une fois à sens unique: si on connaît l'entrée, on peut trouver son hash, mais l'inverse est impossible.

Nous allons donc hasher notre clé privée avec keccak-256, puis garder les 20 derniers octets du résultats (soit 40 caractères hexadécimaux).

Par exemple, si j'utilise cet outil en ligne et que j'y copie-colle ma clé privée (sans espace), j'obtiens ce hash:

FF 61 A8 6B 91 C2 07 CB 54 2C 2D 42 A5 7A 84 1A D8 64 3F 3D EC 40 61 10 36 C3 0C F0 3A B7 22 84
keccak-256

Il ne me reste plus qu'à extraire les 40 derniers caractères de ce hash, et voici mon adresse Ethereum:

# Le préfixe 0x indique qu'il s'agit d'une représentation hexadécimale# La casse n'est pas importante !# Je peux utiliser minuscules ou majuscules selon mes préférences0xA57A841Ad8643F3dEC40611036C30Cf03Ab72284

Vérification

Dans cet article, je me suis amusé à calculer l'adresse étape par étape à partir de la clé privée, mais je préfère quand même m'assurer que je n'ai pas fait d'erreur. En effet, ce serait dommage de recevoir des cryptos sur une adresse qui n'est pas la bonne !

En guise de test final, je vais faire un tour sur MyEtherWallet, et je rentre ma clé privée initiale (sans les espaces). Je clique sur "Access wallet", et me voilà dans le tableau de bord dédié à ce wallet.

En haut à gauche, je peux regarder l'adresse du wallet et confirmer que je n'ai pas fait d'erreur dans mon calcul ! 🥳

MEW

N.B: En faisant cette vérification, j'ai demandé à MyEtherWallet de refaire toutes les opérations du paragraphe précédent. La seule opération qui a nécessité une véritable action de ma part a été de choisir une clé privée au hasard.

Cela signifie que créer un wallet Ethereum est une opération incroyablement simple, qui peut même se faire sans ordinateur ! J'aurais pu par exemple choisir les 64 caractères de ma clé privée avec un dé à 16 faces, ou en piochant des papiers dans un chapeau, rendant l'opération extrêmement sécurisée 🔐

L'ordinateur est nécessaire uniquement pour connaître l'adresse associée à ma clé privée, et c'est cette opération qui est très compliquée.

Pourquoi c'est si compliqué ?

Parce que la blockchain est décentralisée, il n'y a pas d'entité "centrale" qui pourrait, comme dans un site web, vérifier la validité de notre mot de passe pour autoriser ou interdire certaines actions.

Les blockchains sont conçues pour que les détenteurs de clés privées puissent effectuer certaines opérations, et que les autres utilisateurs puissent ensuite vérifier si cette opération est valide ou non.

En pratique, les algorithmes de cryptographie tels que ECDSA permettent de "signer" des messages, et il serait impossible de construire un système décentralisé sans cette fonctionnalité essentielle. Les algorithmes de signature prennent en entrée n'importe quel suite d'octets (du texte par exemple) + une clé privée, et génèrent en sortie une signature, qui est une suite d'octets que l'on peut communiquer au public sans danger. Il est ensuite possible de vérifier que la signature est valide en ayant accès uniquement la clé publique !

À titre d'exemple, imaginons que j'écrive le message suivant:

Je suis détenteur de l'adresse 0xA57A841Ad8643F3dEC40611036C30Cf03Ab72284 et je dois 0.5 ETH à Jean-Michel depuis l'été 2019. Promis, je vais lui rendre.

Ensuite, à l'aide de la clé privée qui a servi à créer mon adresse, je génère une signature de ce message.

Enfin, je poste le message et la signature sur un forum, et tout le monde pourra valider mathématiquement que je suis bien le détenteur de la clé privée liée à cette adresse, sans avoir besoin de donner ma clé privée à personne ! Si il le souhaite, Jean-Michel pourra donc légalement utiliser ce couple message / signature en justice puisque cela constitute une preuve mathématiquement vérifiable. Personne d'autre que moi n'aurait pu signer ce message comme je l'ai fait, car je suis la seule personne connaissant la clé privée liés à cette adresse.

Pour essayer, vous pouvez aller dans le menu "Message > Sign message" de MyEtherWallet qu'on a ouvert au paragraphe précédent. Si j'écris par exemple "bonjour tout le monde" et que je clique sur sign, j'obtiens la signature suivante:

0ff69bf8065251fd9ef42133b5d1e8bf9f6407b1a83e26fd5ed3e7081d29d2903af2dc796f3c3d76820c9f764347de223c7d79042815e1f791f7dc55f6b3d6b91c

Je vais maintenant pouvoir vérifier ma signature dans un autre outil, par exemple celui d'Etherscan. Je clique sur le bouton "Verify signature", puis je rentre l'adresse, le message et la signature dans les bons champs (attention, il faut préfixer la signature par 0x).

Etherscan confirme que le message a bien été écrit puis signé avec la clé privée de cette adresse ! Si j'essaie de changer le moindre caractère du message ou de l'adresse, la vérification échoue, c'est tout l'intérêt.

etherscan verification

Et c'est exactement comme cela que fonctionnent les paiements sur la blockchain Ethereum ! Chaque block de la blockchain contient des transactions du type "transfert de 0.1 ETH de l'adresse X vers l'adresse Y", et chacun de ces messages est signé par le détenteur de l'adresse d'origine, afin de prouver qu'il détient bien la clé privée, et qu'il est donc bien autorisé à effectuer cette transaction.

Avant qu'un block soit ajouté à la blockchain, toutes les signatures sont vérifiées, et cette vérification est effectuée par les mineurs, qui reçoivent les frais de transaction en échange de leur travail de vérification.

Conclusion et code final

Bien que cet article soit très vulgarisé (j'ai essayé d'éviter les maths au maximum), j'espère qu'il vous aura tout de même permis de comprendre un peu mieux ce qui se cache derrière le système clé privée / clé publique qu'on retrouve sur la plupart des blockchains, ainsi qui le système de signature qui est incroyablement puissant et essentiel à la décentralisation.

J'ai personnellement appris beaucoup de choses sur le sujet en travaillant sur un projet personnel: l'outil Vanity-ETH, utilisable en ligne sur vanity-eth.tk. Il s'agit d'un outil très simple, utilisable directement dans le navigateur, qui permet de générer des milliers d'adresses par seconde jusqu'à en trouver une qui corresponde à vos critères.

Vanity-ETH est en première position quand on recherche "ethereum address generator", "eth wallet generator", ou encore "generate ethereum address" sur Google. Pas moins de 11 600 personnes l'ont utilisé en 2022.

Pour conclure, je vous laisse donc le code JavaScript (qui d'ailleurs est utilisé par Vanity-ETH) et qui permet de reproduire toutes les étapes que l'on a vu dans ce tutoriel, de la clé privée jusqu'à l'adresse.

const secp256k1 = require('secp256k1'); const keccak = require('keccak'); const randomBytes = require('randombytes');

// Je crée ma clé privée au format binaire let privateKey = Buffer.from('D68C24FBBF368FF145C843FC4C584BD2840BFD2612379F5D6818E89F0BCF3791', 'hex');

// Décommentez la ligne suivante pour générer une clé au hasard à la place ! // privateKey = randomBytes(32);

// Je trouve la clé publique en utilisant ECDSA avec la courbe secp256k1 let publicKey = secp256k1.publicKeyCreate(privateKey, false)

// Je tronque le premier octet qui ne sert à rien publicKey = publicKey.slice(1);

// Je hashe ma clé publique avec keccak-256 const hashedPublicKey = keccak('keccak256').update(Buffer.from(publicKey)).digest();

// Je garde uniquement les 20 derniers octets pour obtenir l'adresse const address = hashedPublicKey.slice(-20);

// J'affiche toutes les étapes au format hexadécimal console.log('Clé privée : ' + privateKey.toString('hex')); console.log('Clé publique : ' + Buffer.from(publicKey).toString('hex')); console.log('Hash : ' + hashedPublicKey.toString('hex')); console.log('Adresse : ' + address.toString('hex'));

Bonne journée à tous, et n'hésitez pas à commenter si vous avez des remarques ou des questions ! 👍