Lien « Soutenir » de Faeries Space
Le contexte
Objectif : permettre de soutenir via Bitcoin.
Avez-vous remarqué le petit bouton « Soutenez faeries.space » dans le cartouche en bas ? Si vous cliquez dessus, vous verrez une modale avec un lien et un QR Code de paiement en Bitcoin (d’ailleurs, s’il vous en prend l’envie, sentez-vous libre de le tester, hein …). Alors pour les novices et/ou normies comme moi, je pousserai une autre note sur « qu’est-ce que Bitcoin ». L’objet de cet article, c’est de comprendre comment ai-je réussi à ajouter ce bouton à mon site.
Vous allez voir, ce n’est pas si évident que ça …
(bon, faut dire aussi que je n’ai pas choisit la voie la plus simple, mais sinon ce ne serait pas drôle :p)
Contraintes sécurité : clés publiques / privées et rotation des adresses de transaction
La principale raison pour laquelle c’est « un peu » compliqué, concerne la sécurité. Il faut savoir que, sur la blockchain de Bitcoin (en gros, le livre de compte), toutes les transaction sont publiques. Un des moyens pour préserver un minimum l’anonymat des clients, c’est de leur réserver chacun une adresse “receive” de transaction (ce sont des adresses commençant par “bc1…” qu’on peut partager avec ses clients). Pour cela, il m’a fallut réfléchir à une stratégie pour assurer une rotation des adresses de transaction.
Une autre contrainte de sécurité concerne notre propre wallet : on génère nos adresses de transaction avec notre « clés publiques étendues » (zpub). Par contre, il faut éviter au maximum de l’exposer (c’est l’une des raisons des adresses de transaction).
Contrainte technique : clés xpub et zpub
Enfin, une dernière contrainte m’a donné du fil à retordre (et surtout l’occasion de creuser plus loin l’univers Bitcoin) : j’ai créé mon wallet via Proton, qui ne fournit qu’une « clés publiques étendues » xpub. Nous pourrions également les utiliser, mais il est recommandé de passer par des clés zpub, qui sont plus modernes et efficaces (et avec des frais moindres).
Le déroulement
- Pour commencer, nous avons besoin d’un Wallet, à partir duquel nous obtenons des clés.
- Nous verrons ensuite comment générer les adresses “receive” pour les transactions.
- Enfin, il ne restera plus qu’à exposer les informations au navigateur du client, en fonction du client, pour permettre les transactions.
Le point de départ : le Wallet
Comme dis plus tôt, je ne m’étendrai pas sur le fonctionnement de Bitcoin dans cet article. Néanmoins, je vais donner un petit peu plus de contexte sur la notion de Wallet. Un wallet, c’est précisément ce que ça dit : un porte-feuille. Pour Bitcoin (et les cryptos en général), le porte-feuille contient l’ensemble des pairs clé privé / clé publique de nos comptes.
Vu que ce sont des donnés sensibles, nous disposons d’une « clé publique étendue » pour chaque pair de clé privé/publique (donc moins risquée), et à partir de cette « clé publique étendue », on peut générer des adresses de transaction.
Plein d’adresses de transaction.
L’encodage des clés : Base 58
Comme évoqué plus haut, nous sommes partie d’un Wallet Proton, qui fournit des clés xpub, et nous devons nous arranger pour ne stocker que des adresses bc1.
La première étape consiste à obtenir une clé zpub à partir de la clé xpub. Pour cela, j’ai donc rédigé un petit script “Key Converter” qui s’appuie sur un standard Bitcoin : Base58. Je vous invite à y jeter un coup d’œil pour voir comment il fonctionne.
Base58 est un système d’encodage numérique (similaire à Base64) utilisé dans Bitcoin pour représenter des données binaires (comme nos clés privées/publiques) sous forme de chaînes de caractères lisibles par les humains. Pour cela, cet encodage prend les chiffre de 1 à 9, les lettres de a à z (sauf l — « L » minuscule), et les lettres de A à Z (sauf I — « i » majuscule — et O). Les exclusions doivent éviter toute ambiguïtés (on parle bien de « lisibles pour les humains »).
Base58 étant utilisé à la fois pour les clés xpub et zpub (et tout les autres formats de clé, en fait), notre convertisseur …
- prend en entrée une clé (xpub pour nous) et le format cible (ici zpub) ;
- vérifie que les formats sont valides et compatibles (on peut convertir n’importe quel type de clé publique vers n’importe quel type de clé publique, idem en privé vers privé, mais on ne peut pas faire privé vers publique ou publique vers privé, ce serait une faille de sécu) ;
- décode la clé pour vérifier son checksum (géré par la lib base58) ;
- reconstitue la clé cible au format hexadécimal ;
- encode cette clé au format base58 (toujours grâce à la lib base58) ;
- renvoi la chaîne de caractère correspondante au format utf-8.
À l’issu de cette première étape, nous récupérons une clé zpub utilisable pour obtenir les adresses de transaction, grâce à notre générateur d’adresse.
Bitcoin Improvement Proposals — BIP44 et BIP84 pour obtenir les adresses “receive”
Descendre jusqu’au concept de BIP prendrait beaucoup trop de temps, pour cet article. On va donc se concentrer sur BIP44 et BIP84 : ce sont les deux standards sur lesquels repose notre algorithme de génération d’adresse de transaction “receive”.
La BIP44 est un standard datant de 2014 pour les portefeuilles multi-comptes et multi-cryptomnnaies. Dans notre cas, il apporte surtout le standard pour les adresses de receive qui servent aux paiements entrants (en gros, qu’un autre bitcoiner peut utiliser pour nous virer de l’argent ₿).
La BIP84, basée sur BIP44, apporte des évolutions pour des porte-feuilles plus efficaces, notamment via optimisation des transactions grâce à l’activation de SegWit natif. Dans notre cas, la lib pour BIP 84 fournit la génération de l’adresse de réception à partir de notre clé publique compatible SegWit (zpub) et de l’index propre à chaque adresse de transaction.
Maintenant, on est capable de récupérer des adresses de transaction …
L’adresse de transaction “receive” ainsi générée commence selon le format standard (par “bc1”), et utilise un encodage Bech32 (limité aux minuscules et chiffres, pour des adresses plus compactes que les clés zpub).
Une nouvelle adresse “receive” pour une nouvelle transaction
Dans l’esprit Bitcoin, une adresse doit être utilisée au maximum pour une seule transaction, ou à minima pour un client, l’idée étant que chaque client ne puisse pas retrouver les transactions des autres. En effet, il suffit d’avoir une adresse de transaction pour récupérer tout l’historique, comme nous allons le voir ci-après.
L’API de Block Stream permet justement de vérifier si une adresse a déjà été utilisée.
Pour cela, rien de plus simple : on construit l’url à partir de l’adresse “bc1…” qu’on vient de générer comme suit :
https://blockstream.info/api/address/{address}
Celle-ci renvoi une foule d’informations, mais pour notre cas, nous nous concentrerons sur 4 éléments :
- l’index de l’adresse (un nombre, la première adresse ayant l’index 0)
- l’adresse elle-même
- la somme des transactions reçues (funded_txo_sum) et émises (spent_txo_sum), qu’on utilisera pour calculer la balance (funded_txo_sum - spent_txo_sum)
- le nombre de transaction (tx_count).
Pour notre besoin, l’information la plus importante est la dernière : si le nombre de transaction est supérieur à 0, alors l’adresse a été utilisée au moins une fois, et donc on ne veut pas la réutiliser.
Le script à proprement parler
Ainsi, notre second script va s’appuyer sur notre clé zpub pour
- récupérer un nombre configurable d’adresses de transaction (via bip_utils), et
- vérifier le solde des transactions sur cette adresse (grâce à l’API blockstream), avant
- de constituer une liste d’adresses exploitables.
Pour les besoins de notre site web (on ne perd pas l’objectif de vue, svp), on va générer deux types de fichier :
- un fichier json avec la liste des adresses de transaction, et
- un ensemble de fichiers SVG, contenant chacun la version QRCode d’une adresse.
Le QR Code qu’on retrouve sur le site web !
Si vous avez tenté de scanner ce QR Code, vous verrez qu’il contient une chaîne de caractère sous la forme suivante : “bitcoin:bc1…?label=support.faeries.space”. Si vous avez déjà un wallet, vous savez ce que ça fait. Si ce n’est pas le cas, c’est tout simple : la plupart des applications permettant de gérer son wallet permettent de scanner ce QR Code, pour ensuite envoyer son paiement en Bitcoin.
Stocker les adresses sur le serveur
Maintenant que nous sommes capable de générer un fichier json avec l’ensemble des adresses, et les fichiers SVG des QRCode à afficher sur le site web, on doit les stocker sur le site web. Vous vous demanderez pourquoi les générer, puis les envoyer, au lieu de générer directement sur notre serveur ? C’est pour une raison de sécurité : moins nous partageons de donné sensible, mieux nous nous portons. Les adresses de transaction étant le minimum possible, nous nous efforçons de ne stocker aucune autre information sur le serveur.
Sur notre serveur, les fichiers SVG seront exposés via une API relativement simple :
- un header X-BTC-Addr avec l’adresse de transaction, et
- le corps de la réponse contenant le fichier SVG.
Par contre, on veut avoir un QR Code unique pour chaque client … Pour cela, on a choisit la stratégie suivante :
- le client fournit une clé X-Hash-Key qui est forgé directement côté client (nous y reviendront plus loin) ;
- côté serveur, nous vérifions si cette clé est valide (en gros, longueur de 16 caractères) ;
- ensuite, nous allons vérifier si elle a déjà été utilisée ou non ;
- si elle n’a pas encore été utilisée, nous allons récupérer une adresse qui n’est pas encore utilisée (grâce à son index), nous l’associons à la clé du client, nous l’enregistrons et nous mettons à jour le dernier index utilisé ;
- enfin, nous renvoyons l’adresse de transaction et son QR Code.
Vous pouvez retrouver et tester cette API sur mon dépôt Git.
Côté web : récupération et affichage de la modale
Tout à l’heure, nous parlions de la clé générée côté client à titre d’identifiant unique. Le but n’est pas tant d’anonymiser le compte client que de s’assurer qu’une clé donné corresponde à une clé donné. Étant donné qu’on vise une adresse de transaction réservée à un client donné, on va conserver cette clé dans le navigateur du client.
L’algorithme a donc été conçu de sorte à ce que la clé soit générée pour ce client, de façon semi-aléatoire :
- le timestamp, pour garantir que toute nouvelle clé générée sera différente de la précédente,
- le user agent du navigateur, et
- une chaîne générée aléatoirement.
On rajoute une passe de hash (généreusement suggérée par Grok) pour renforcer un minimum la clé personnelle de notre utilisateur, puis on la stocke en localStorage.
Enfin, lorsque l’utilisateur souhaite soutenir faeries.space (i.e. qu’il clique sur « Soutenez faeries.space ! », on va récupérer cette clé et l’envoyer au serveur. Comme ça, le serveur va renvoyer le QR Code et l’adresse de transaction qui vont servir à remplir la petite modale que vous voyez apparaître en cliquant sur le bouton !
Tout le code Javascript (la logique) se trouve également sur mon dépôt Git.
Conclustions
Limitation : constamment garder un œil sur les transactions
Côté serveur, nous avons fait face à une limitation … Notre porte-feuille (Proton Wallet), comme beaucoup de porte-feuilles, ne charge par défaut que 20 adresses inutilisées pour vérifier le solde du compte dans son ensemble (un “gap-limit” fixé pour éviter de surcharger le service). Par conséquent, nous ne pouvons pas nous permettre de générer des centaines d’adresses d’un coup, mais seulement une vingtaine (on peut tolérer quelques trous).
Par conséquent, on devra régulièrement surveiller l’utilisation des adresses de transaction, et mettre à jour la liste sur le serveur (et on devra probablement automatiser tout ça, le jour où faeries.space commence à recevoir des centaines de dons ₿₿₿₿₿).
Afin d’éviter les blocages, nous avons dû faire une petite concession à une règle de sécurité : éviter de réutiliser une adresse de transaction plus d’une fois. Nous avons mis en place un système de rotation, de sorte que si la dernière clé dispo est utilisée, nous renvoyons à nouveau le plus petit index de notre fichier json … Il sera donc important d’être attentif aux transactions sur notre site web, pour éviter au maximum de se retrouver à fournir chaque adresse à plus d’une personne.
Limitation : transactions on-chain uniquement
Les adresses de transaction que je proposes ne fonctionnent que “on-chain”. Cela signifie qu’elles sont directement sur la chaîne principale de Bitcoin, “Layer 1”. Celles-ci sont généralement coûteuses de l’ordre de 10€, en fonction de la charge du réseau) et lentes (jusqu’à 1h d’attente avant de voir les fonds arriver. Par contre, les transactions on-chain sont plus simples (c’est un peu pour ça que j’ai commencé par là) et efficaces pour de gros montants.
Maintenant, je ne m’attends pas à de gros montants, sur un site comme faeries.space (i.e. logique de “tips”, donc de petits montants ponctuels). Une solution plus adaptée passe donc par ce qu’on appelle le Layer 2 : ce sont des transactions qui ne se font pas directement “on-chain”, mais une sur surcouche qui sera plus performante (quasi-instantannés) et moins coûteuse à utiliser (l’équivalent de quelques centimes maximum, en satoshis). Cependant, cette solution est soit plus complexe à implémenter, soit nécessite de faire confiance à un tiers. Pour être autonome, une solution comme Lighhtning nécessite de créer, configurer et maintenir son propre nœud sur le réseau. Autrement, nous pouvons nous créer un compte via un service tiers, auquel il faut alors faire confiance pour gérer ses fonds. Il est ensuite possible de rappatrier les satoshis obtenus sur son compte principal (qui se trouve sur le Layer 1).
Limitation : encore tant de choses à dire !
Pour l’instant, j’en suis aux tout débuts de mes explorations sur Lightning … et Bitcoin en général 😝 ! Heureusement, d’autres articles suivront pour vous partager le fruit de toutes mes recherches.