Partie 1 : Comment fonctionne Bitcoin Script, et pourquoi est-il si difficile ? Partie 2 : Qu'est-ce que Miniscript, et comment facilite-t-il l'écriture de scripts Bitcoin ? Partie 3 : Écrire un parseur et un analyseur Miniscript en Go

Les seules conditions de dépense Bitcoin largement utilisées aujourd'hui sont les single-sigs simples et les multisigs simples, même si Bitcoin Script, le langage utilisé pour encoder les conditions de dépense dans les transactions Bitcoin, est bien plus puissant que cela. Pourquoi en est-il ainsi ?

La raison est que Bitcoin Script, qui semble à première vue être un langage simple à base de pile, est en réalité très difficile à utiliser en pratique. Pour chaque nouvelle condition de dépense qu'un développeur peut vouloir créer, il faut passer beaucoup de temps à s'assurer qu'elle est correcte et robuste dans toutes les situations, ce sur quoi il peut être difficile de raisonner. Nous allons bientôt voir un exemple parlant.

Plus important encore, le manque de standardisation et d'outillage pour ces types de scripts peut rendre difficile leur interopérabilité avec les wallets et autres logiciels. En pratique, cela signifie que même si vous décidez de faire l'effort de développer un nouveau script, vous vous retrouverez avec un wallet non standard. Les autres wallets ne seront pas compatibles, ce qui est évidemment mauvais pour les utilisateurs.

Dans cette série d'articles, nous allons explorer Bitcoin Miniscript en profondeur. Bitcoin Miniscript est un langage de haut niveau pour exprimer Bitcoin Script, conçu pour permettre aux développeurs de wallets de créer facilement des conditions de dépense complexes et de raisonner sur leur correction et leur robustesse, tout en permettant à tous les wallets et outils d'interagir facilement avec elles.

BitBox02

En tant que développeurs du hardware wallet BitBox02, nous cherchons constamment des moyens d'améliorer la sécurité et l'utilisabilité de la self-custody. Par le passé, nous avons implémenté et déployé le protocole anti-klepto et amélioré la sécurité du multisig pour tous les fabricants de hardware wallets.

Kevin et Antoine de Revault/wizardsardine nous ont contactés pour discuter de la possibilité d'ajouter la prise en charge de Miniscript à la BitBox02. C'était aussi un sujet phare de la BTC Azores '22 unconference, où Antoine a donné une excellente présentation à ce sujet et où Salvatore a montré ses progrès dans l'intégration de Miniscript dans Ledger

Nous considérons Miniscript, ainsi que les covenants et MuSig, comme des évolutions importantes pour la self-custody et pour la BitBox02.

Miniscript permet des conditions de dépense plus avancées, comme le montre Wizardsardine avec Liana, un nouveau type de wallet qu'ils développent.

En ajoutant la prise en charge de Miniscript à la BitBox02, des wallets avancés comme Liana peuvent être sécurisés par votre BitBox02. Cela ouvrirait aussi la voie au développement de solutions de self-custody plus avancées au sein de la BitBoxApp.

Avant toute intégration, nous devions d'abord approfondir nos connaissances sur Miniscript. D'après mon expérience, la meilleure façon d'apprendre un sujet d'ingénierie logicielle est de l'implémenter concrètement. En le construisant vous-même, vous êtes obligé de prendre en compte chaque détail. Une autre méthode efficace consiste à l'expliquer à d'autres. Cette série d'articles remplit ces deux objectifs : elle comprend une implémentation de Miniscript et cherche à vous l'enseigner dans un format différent de ce qui existe aujourd'hui.

Introduction très courte à Bitcoin Script

Les bitcoins sont généralement verrouillés par des scripts qui encodent les conditions à remplir pour dépenser les coins. Dans cette série, nous nous concentrerons sur P2WSH (Pay-to-Witness-Script-hash). Dans ce cas, le witness script encode les conditions à remplir pour dépenser un bitcoin. Il inclut souvent des clés publiques. Le witness est la donnée nécessaire pour satisfaire les conditions de dépense. Les witness incluent souvent des signatures correspondant aux clés publiques.

Pour dépenser un coin verrouillé par un witness script, la transaction qui le dépense doit inclure un witness valide. Le witness et le witness script sont évalués selon les règles de Bitcoin Script.

Une adresse Bitcoin comme par exemple bc1q2fhgukymf0caaqrhfxrdju4wm94wwrch2ukntl5fuc0faz8zm49q0h6ss8 est simplement un encodage du fait qu'il s'agit d'une sortie P2WSH contenant le hash du witness script. Les nœuds Bitcoin savent que lorsqu'ils voient un coin envoyé vers une telle sortie, la transaction qui dépense ce coin doit inclure le witness script correspondant, ainsi que le witness nécessaire pour satisfaire le witness script.

Par exemple, pour encoder une condition simple de clé à signature unique, le witness script serait :

`<publicKey> OP_CHECKSIG
`

et le witness serait

`<signature>
`

Lors de la vérification du script, le witness et le witness script sont exécutés dans l'ordre, en partant d'une pile vide :

  1. Pile initiale : vide.
  2. La signature est poussée sur la pile : <signature>
  3. publicKey est poussé sur la pile : <signature> <publicKey>
  4. OP_CHECKSIG retire les deux éléments au sommet de la pile, vérifie la signature et pousse un 0 en cas d'échec, ou un 1 en cas de succès.

S'il reste exactement un élément non nul sur la pile et que le script ne s'est pas interrompu, le witness est valide et le coin peut être dépensé.

Dans Bitcoin Script, il existe tout un ensemble d'OP-codes différents en plus de OP_CHECKSIG, qui peuvent être utilisés pour encoder des conditions de dépense plus complexes. Par exemple, vous pouvez verrouiller des coins dans une sortie multisignature avec OP_CHECKMULTISIG, ou verrouiller les coins pendant une période donnée avec OP_CHECKSEQUENCEVERIFY.

Exemple motivant pour Miniscript

Miniscript aide les développeurs à créer des scripts Bitcoin plus sûrs et plus efficaces en traitant plusieurs problèmes du langage Bitcoin Script. Regardons quelques conditions de dépense simples que l'on pourrait vouloir mettre en place afin d'illustrer les difficultés liées au travail direct avec Bitcoin Script. Plus loin, nous verrons comment Miniscript résout ces problèmes et facilite grandement le développement et le déploiement de nouvelles conditions de dépense.

Par exemple, imaginons que nous voulions qu'une des deux personnes puisse dépenser un coin. La solution la plus simple consiste à utiliser OP_CHECKMULTISIG. Le witness script est :

`1 <publicKey1> <publicKey2> 2 OP_CHECKMULTISIG
`

Le 1 indique à OP_CHECKMULTISIG combien de signatures doivent être fournies, et le 2 combien il y a de clés publiques.

Le witness est :

`<> <signature>
`

(l'élément vide au début du witness est en réalité inutile et existe à cause d'un bug dans l'implémentation originale de OP_CHECKMULTISIG, qui retire un élément de trop de la pile).

Le script de vérification résultant, <> <signature> 1 <publicKey1> <publicKey2> 2 OP_CHECKMULTISIG, laisse un 1 sur la pile s'il y a une signature valide correspondant à l'une ou l'autre clé publique, ou 0 sinon.

Changeons maintenant légèrement la sémantique. Au lieu de pubkey1 OU pubkey2, essayons pubkey1 OU (pubkey2 dans un an) : le coin peut être dépensé par une personne à tout moment, ou par une autre personne après avoir attendu un an. Comme OP_CHECKMULTISIG ne peut vérifier que des signatures et pas des verrous temporels, le script doit changer complètement. Il peut y avoir de nombreux scripts qui implémentent un même ensemble de conditions de dépense. Voici l'une des nombreuses solutions possibles dans ce cas :

Solution

[]Witness script :

`<pubkey1> OP_CHECKSIG OP_IFDUP OP_NOTIF
<pubkey2> OP_CHECKSIGVERIFY <52560 (un an)> OP_CHECKSEQUENCEVERIFY
OP_ENDIF
`

52560 correspond à un an en nombre de blocs Bitcoin, qui arrivent en moyenne toutes les dix minutes.

Witness possibles :

  1. À tout moment : <signature for pubkey1>
  2. Seulement si un an s'est écoulé : <signature for pubkey2> <>.

Examinons l'exécution du script avec le deuxième witness. Comme auparavant, le witness et le witness script sont exécutés dans l'ordre à partir d'une pile vide. En supposant qu'un an se soit écoulé et que la signature soit valide :

  1. Pile initiale : vide.
  2. Pousser la signature : <signature for pubkey2>
  3. Pousser l'élément vide : <signature for pubkey2> <>
  4. Pousser pubkey1 : <signature for pubkey2> <> <pubkey1>
  5. OP_CHECKSIG retire les deux éléments <> et <pubkey1> de la pile et vérifie la signature par rapport à cette clé publique. Comme la signature est vide, elle est invalide et OP_CHECKSIG pousse un 0 : <signature for pubkey2> 0.
  6. OP_IFDUP duplique l'élément au sommet de la pile s'il n'est pas nul. Comme il est nul, il ne se passe rien : <signature for pubkey2> 0.
  7. OP_NOTIF : retire l'élément au sommet de la pile. S'il vaut 0, les instructions jusqu'à OP_ENDIF sont exécutées : <signature for pubkey2>.
  8. Pousser pubkey2 : <signature for pubkey2> <pubkey2>.
  9. OP_CHECKSIGVERIFY retire deux éléments (signature et clé publique) et vérifie la signature. Si elle est valide, rien ne se passe ; sinon, l'exécution s'interrompt avec une erreur. La pile est maintenant : vide.
  10. Pousser 52560 (valeur numérique représentant un an) : <52560>.
  11. OP_CHECKSEQUENCEVERIFY considère le dernier élément comme une durée. Si le coin à dépenser est plus jeune, le script s'interrompt avec une erreur. S'il est plus ancien, il ne se passe rien : <52560>.
  12. OP_ENDIF : termine le bloc OP_IF.
  13. Il reste exactement un élément non nul sur la pile, donc le script réussit et le coin peut être dépensé.

À l'étape 5, l'élément de signature vide est appelé une « dissatisfaction ». Il est nécessaire pour passer la partie qui vérifie la première clé publique, que nous ne voulions pas utiliser. Notez que seul l'élément vide est une dissatisfaction valide pour <key> OP_CHECKSIG selon BIP141 — toute autre signature invalide entraîne l'arrêt du script :

Les signature(s) doivent être des vecteur(s) nuls si un OP_CHECKSIG ou OP_CHECKMULTISIG échoue (à la fois pour les witness scripts pré-segregated witness et pour P2WSH. Voir BIP146)

Notez aussi que le script ne réussit que parce que le verrou temporel d'un an est non nul. Si nous utilisions le même script mais remplacions simplement la valeur d'un an par 0, vous pourriez supposer que notre sémantique initiale pubkey1 OU (pubkey2 dans un an) deviendrait pubkey1 OU (pubkey2 à tout moment). Comme un 0 resterait sur la pile à la fin, le script échouerait toujours et la sémantique réelle deviendrait accidentellement pubkey1 seulement, et la deuxième clé publique ne pourrait jamais dépenser.

Comme exercice pratique, essayez d'exécuter le script ci-dessus avec le premier witness, en supposant que la première personne signe et que la deuxième ne signe pas, et vérifiez par vous-même que cela fonctionne.

Comme vous pouvez le voir, créer de tels scripts et des witnesses valides en Bitcoin Script est un processus laborieux et propice aux erreurs, même pour une sémantique simple. La séquence d'opérations sur la pile est compliquée et difficile à construire comme à analyser. Si vous voulez étendre les conditions de dépense, le processus de développement repart essentiellement de zéro. Une compréhension approfondie de Bitcoin Script est nécessaire. Entre autres choses, vous devez connaître la règle cleanstack (qui exige qu'il ne reste qu'un seul élément non nul à la fin de l'exécution) et le fait que seul l'élément vide constitue une dissatisfaction valide pour une vérification de signature.

Pour résumer, travailler directement avec Script est difficile pour les raisons suivantes :

  • Script se compose mal, ce qui signifie que de petits changements dans les conditions de dépense souhaitées peuvent aboutir à des scripts très différents.
  • Les OP-codes de Script ont des sémantiques différentes en cas d'échec ou de succès, ce qui rend leur composition et leur réutilisation difficiles. Certains poussent un 0 ou un 1 sur la pile en cas d'échec ou de succès, tandis que d'autres interrompent toute l'exécution en cas d'échec et ne font rien en cas de succès.
  • Il existe de nombreux scripts possibles pour un même ensemble de conditions de dépense souhaitées, ce qui rend difficile le choix du bon.
  • Créer des witnesses valides dans toutes les situations est difficile.
  • Il existe des limites de consensus et de standardness sur la taille des scripts, le nombre d'opcodes et de signatures, ainsi que le nombre d'éléments dans la pile du witness, que le développeur doit prendre en compte pour éviter un rejet par le réseau.
  • Concevoir un script complexe qui laisse exactement un élément non vide sur la pile à la fin de l'exécution est difficile, comme nous l'avons vu dans l'exemple ci-dessus.

Dans le prochain volet de cette série, nous examinerons en détail ce qu'est Miniscript et comment il simplifie radicalement Bitcoin Script, au point de rendre l'utilisation pratique de conditions de dépense élaborées. Restez à l'écoute !

Passez à la deuxième partie.

Vous n'avez pas encore de BitBox ?

Garder votre crypto en sécurité ne doit pas être compliqué. Le hardware wallet BitBox02 stocke les clés privées de vos cryptomonnaies hors ligne. Vous pouvez ainsi gérer vos coins en toute sécurité.

La BitBox02 existe aussi en version Bitcoin-only, avec un firmware radicalement ciblé : moins de code signifie moins de surface d'attaque, ce qui améliore encore votre sécurité si vous stockez uniquement du Bitcoin.

Achetez-en une dans notre boutique !

Foire aux questions (FAQ)

Qu'est-ce que Bitcoin Miniscript ? Bitcoin Miniscript est un langage de haut niveau pour exprimer Bitcoin Script, qui permet aux développeurs de créer plus facilement des conditions de dépense complexes et d'en garantir la correction.

Pourquoi Bitcoin Script est-il difficile à utiliser ? Bitcoin Script, bien que puissant, est difficile à utiliser en pratique. Créer des scripts et des witnesses valides est laborieux et propice aux erreurs, même pour des conditions simples.

Comment Miniscript améliore-t-il Bitcoin Script ? Miniscript simplifie Bitcoin Script, ce qui rend l'utilisation pratique de conditions de dépense élaborées possible. Il traite plusieurs problèmes du langage Bitcoin Script et le rend plus simple à utiliser pour les développeurs.

Quelles sont les conditions de dépense Bitcoin les plus courantes aujourd'hui ? Les conditions de dépense Bitcoin les plus courantes sont les single-sigs simples et les multisigs, même si Bitcoin Script offre bien plus de possibilités.

Quelle est l'importance de la BitBox02 par rapport à Miniscript ? La BitBox02 étudie des moyens d'ajouter la prise en charge de Miniscript, ce qui renforcerait la sécurité et l'utilisabilité de la self-custody ainsi que des solutions de wallets avancées.

Shift Crypto est une entreprise privée basée à Zurich, en Suisse. Notre équipe de contributeurs Bitcoin, d'experts crypto et d'ingénieurs sécurité conçoit des produits qui permettent à nos clients de progresser sans stress, du niveau débutant au niveau expert, dans la gestion des cryptomonnaies. La BitBox02, notre hardware wallet de deuxième génération, permet aux utilisateurs de stocker, protéger et utiliser Bitcoin et d'autres cryptomonnaies en toute simplicité, avec son compagnon logiciel, la BitBoxApp.

This post was translated with the help of AI