Tapscript : Nouveaux opcodes, réduction de limites et conditions de dépense
Liquid Network Blockstream Research

Tapscript : Nouveaux opcodes, réduction de limites et conditions de dépense

Andrew Poelstra

Introduction

Voici le troisième et dernier article de notre série sur les améliorations que nous avons déployées sur Liquid dans le cadre du lancement de Taproot. Nos deux articles précédents portaient sur Taproot lui-même et sur les transactions partiellement signées d’Elements. Dans cet article, nous décrirons les nouveaux opcodes que nous avons introduits dans Elements Script en illustrant ce à quoi ils peuvent servir et ce que nous en faisons.

Cet ensemble d'opcodes constitue une d'améliorations majeure apportée à Script depuis le lancement d'Elements Alpha en 2015. Notre équipe de recherche a également innové dans le domaine de la cryptographie appliquée liée à Bitcoin, fait des améliorations sur MuSig, la blockchain d’Elements, elle a aussi travaillé sur le Lightning Network et a poursuivi son travail sur les scripts et le langage Simplicity. Nous avons largement contribué au projet Taproot de Bitcoin, et certaines des modifications apportées à Elements Script découlent de ces contributions, notamment :

  • La suppression des limites numériques fixes sur le nombre d'opcodes et la taille des scripts ;
  • Le remplacement des signatures ECDSA par Schnorr ;
  • Le remplacement de CHECKMULTISIG par CHECKSIGADD, permettant la vérification en lots des signatures.
  • Des opcodes OP_SUCCESS, qui élargissent l'ensemble des opcodes que nous pouvons intégrer au réseau par rapport aux anciens opcodes NOP.

Nous parlerons également des conditions de dépense, qui donnent à Script la possibilité de limiter les flux de paiement pour construire des «contrats intelligents» qui attachent des conditions aux jetons ou à d'autres actifs. Les conditions de dépenes ont été proposées pour Bitcoin sous de nombreuses formes au fil des ans, mais elles sont présentes dans Elements depuis le tout début. Avec Tapscript, nous avons pris en compte les commentaires des développeurs sur notre implémentation originale des conditions de dépense et avons introduit de nouveaux opcodes qui rendront les conditions de dépense plus efficaces et ergonomiques.

Enfin, nous parlerons brièvement d'Elements Miniscript, notre extension du langage Miniscript de Bitcoin, qui utilise ces opcodes pour produire des scripts compatibles avec les conditions de dépense. Ce langage est en cours de développement et nous sommes impatients de voir les possibilités de nouvelles applications sur Liquid qui en découleront. Nous aborderons ce sujet plus en détail dans un prochain article.

Condition de dépense et actifs

Les conditions de dépense ont été initialement proposées pour Bitcoin par Greg Maxwell en 2013 d’une manière presque ludique, en demandant aux utilisateurs d'imaginer des applications amusantes ou même mauvaises. Bien que ces «mauvaises idées» soient amusantes à imaginer, elles semblent malheureusement avoir nuit au concept de conditions de dépense jusqu'à ce jour, même si les préoccupations ne sont pas nécessairement applicables ou ne leurs sont pas nécessairement spécifique. En partie à cause de cette prudence liée au risque, les conditions de dépense sont restées un sujet controversé dans l'espace Bitcoin.

La proposition la plus récente et la plus populaire pour les conditions de dépense Bitcoin est OP_CTV, dont les fonctions ont été délibérément réduite afin d'éviter la controverse.

À notre avis, ces craintes sont largement exagérées. Elles proviennent de l'idée que les conditions de dépense pourraient être utilisées pour imposer un système qui permettrait de ternir les bitcoins existants, par exemple en exigeant la signature d'un tiers pour leur utilisation, ce qui contaminerait l'offre de jetons et serait impossible à abroger par la suite. Mais un tel schéma peut en fait être mis en œuvre aujourd'hui, par l'utilisation de scripts 2-of-2 CHECKMULTISIG dans lesquels une partie refuse de signer les transactions qui ne préservent pas la condition de dépense 2-of-2. Personne ne l'a implémenté, et pour cause : ces pièces ne seraient évidemment pas acceptées par les utilisateurs à la place des jetons ordinaires et ne pourraient pas être acceptées par les utilisateurs ayant une obligation fiduciaire ou autre obligation légale de garde des jetons ordinaires. En outre, les jetons qui en résulteraient nécessiteraient des frais de réseau plus élevés pour être dépensées en raison de leur poids supplémentaire et augmenteraient la complexité technique des portefeuilles qui permettraient de les dépenser.

Faire la même chose avec des conditions de dépense serait encore plus coûteux et nécessiterait une telle complexité au niveau des portefeuilles que personne ne serait intéressé à les implémenter, sans parler des utilisateurs pourrait simplement le rejeter.

Une autre preuve que les conditions de dépense sont inoffensives est qu'elles existent sur le réseau Liquid depuis son lancement en 2018, et qu'aucune utilisation malicieuse ne s'est répandue dans le système. (Bien que, comme nous le verrons, il existe des raisons techniques expliquant la faible adoption des conditions de dépense sur Liquid en général).

Ce concept de conditions introduirait certaines nouveautés pour Bitcoin, telles que les coffres-forts ou les limites de vélocité, donnant aux utilisateurs plus de flexibilité dans la façon dont ils conservent leurs jetons.

Dans Elements, où il est possible d’émettre des actifs, les fonctionnalités amenées par les conditions de dépense sont encore plus intéressantes ; nous pouvons :

  • Construire des ordres à cours limité ouverts ou d'autres types de transactions algorithmiques.
  • Construire des produits financiers dérivés tels que des options.
  • Créer des "jetons de contrôle", c'est-à-dire des NFT qui activent des fonctionnalités dans d'autres contrats.

Dans un scénario multi-actifs avec un riche ensemble d'opcodes arithmétiques, nous serions en mesure d'égaler l'ensemble des fonctionnalités d'Ethereum et de ses concurrents. En évitant que le système soit Turing-complet, nous devrions même être en mesure de mettre en œuvre des formes d'analyse solide. Mais comme nous allons le voir, l'histoire est un peu plus complexe que cela.

Elements et script Bitcoin

Elements a été lancé en 2015 sur un réseau de test appelé Elements Alpha comme un embranchement de Bitcoin intégrant des fonctionnalités supplémentaires. Ce réseau est antérieur à Segwit et Taproot ; il est apparu avant les émissions d’actifs ; il disposait d'un schéma d'ancrage en deux étapes qui s'est finalement révélé inutilement complexe et sujet aux bogues et a été remanié avant d'être déployé sur Liquid. Ses principales caractéristiques étaient les transactions confidentielles, qui ont ensuite été étendues pour prendre en charge l’émission d’actifs, un schéma naissant de séparation de témoin qui a été simplifié qui a été ensuite intégré sur Bitcoin sous le nom de Segwit, et un ensemble d'opcodes supplémentaires pour activer les conditions de dépense. C'est sur ce dernier point que nous allons nous concentrer dans cet article.

Lorsque nous avons introduit l'ensemble d'opcodes originaux d’Elements, Bitcoin Script reposait sur une programmation manuelle de bas niveau et était également difficile à analyser. Nos extensions ont augmenté l’étendu du langage mais n'ont pas compensé les limitations sous-jacentes. Les utilisateurs ont pu développer certaines applications avancées, telles que les prêts sur Liquid, mais ce développement témoigne davantage de la détermination et de l'ingéniosité humaine que de la facilité de programmation de notre plateforme de développement. D'autres applications, comme Bitmatrix, n'ont pu être déployées qu'après les mises à jour du système de script décrites dans cet article. Depuis lors, indépendamment d'Elements, Blockstream Research a développé Miniscript, une nouvelle façon de modéliser Bitcoin Script qui permettrait de résoudre ces problèmes d'analyse.

Avec Miniscript, il est possible d'énumérer automatiquement toutes les clés d'un script et de déterminer quels ensembles sont nécessaires pour produire une transaction ; de trouver la limite supérieure de terme de taille d'une telle transaction avant de recueillir les signatures ; de répondre à des questions sémantiques sur les conditions à remplir pour dépenser des jetons ; et de calculer les encodages exacts des scripts et des témoins. Miniscript complète Partially Signed Bitcoin Transactions, également développé par Andrew Chow de Blockstream Research, Pieter Wuille ayant participé au concept et au développement initial, qui fournit un protocole pour assembler les données nécessaires pour produire une transaction. Cela permet aux portefeuilles de signer des transactions même s'ils ne comprennent pas (ou ne connaissent pas à l'avance) le script exact qui répond aux conditions ; ils doivent simplement vérifier que la transaction a un sens, produire une signature ECDSA ou Schnorr, et laisser l'outil Miniscript faire le reste.

Cependant, même avec Miniscript dans notre boîte à outils, nous n'avons pas trouvé de chemin direct vers des "conditions de dépense faciles" sur Elements. Nos projets utilisant des conditions de dépense de l'ère 2016 étaient difficiles à utiliser de manière générique, et leur grande taille est difficile à éviter, étant limitée par la limite de 201 opcodes que nous avons héritée de Bitcoin. Ainsi, même si Bitcoin Script est devenu plus puissant grâce à Miniscript, PSBT et Tapscript (conçu avec Miniscript en tête), les extensions d'Elements à Script n'ont pas pu suivre.

Une percée : Les nouvelles abstractions

En février 2021, Andrew Poelstra et Sanket Kanjalkar, chercheurs chez Blockstream, ont finalement trouvé un moyen de combiner les conditions de dépense d'Elements avec Miniscript. Essentiellement, nous construisons des scripts compatibles avec les conditions de dépense à l'intérieur de "la machine", un modèle de script de taille fixe qui utiliserait l'opcode CHECKSIGFROMSTACK (CSFS) pour extraire toutes les données des transactions et les laisser sur la pile à des emplacements fixes où le "vrai code" peut y accéder efficacement. Cela nous permet d'amortir le coût élevé des clauses de type CSFS sur de nombreuses conditions de la transaction de dépense.

En fait, nous nous sommes par la suite éloignés de cette approche initiale, mais cette étape nous a permis de dépasser la limite conceptuelle de la construction de conditions de dépense et de voir quelles étaient les véritables limites d'Elements Script :

  • Arithmétique big-endian 31 bits héritée du bitcoin, qui ne pouvait pas être facilement utilisée avec les montants little-endian 64 bits utilisés par les transactions.
  • En outre, il n'y avait pas d’opcodes de multiplication ou de division ; Elements avait ajouté des décalages de bits et d'autres opérations arithmétiques, mais pas les bases nécessaires pour effectuer, par exemple, des calculs de frais.
  • Pour le calcul des frais, les conventions de type CSFS ne peuvent pas accéder au poids de la transaction et ne peuvent donc pas calculer les frais.
  • L'impossibilité de hacher plus de 520 octets de données est due à la combinaison de la limite de taille des éléments de la pile de 520 octets de Bitcoin et de son opcode SHA256. Combiné avec le besoin du CSFS de hacher des quantités importantes de données de transaction en une seule fois, cela a créé des limites sur la taille totale des transactions, rendant la vie des créateurs de portefeuilles beaucoup plus difficile.
  • Impossibilité d'exécuter des scripts comportant plus de 201 opcodes, dont des dizaines sont nécessaires à la machine CSFS et des dizaines d'autres pour convertir des entiers 64 bits en 32 bits et inversement.
  • Une incapacité à faire de l'arithmétique à courbe elliptique, sauf pour la validation de signature, qui peut être forcée à faire beaucoup de choses mais pas à vérifier que les sorties de Taproot sont correctement formées. Si nous avions conservé cette limitation dans Tapscript, cela aurait réduit la fonctionnalité tout en augmentant la complexité de nos scripts.

Une fois que nous avons reconnu ces problèmes, il a été facile de les résoudre :

  • Nous avons ajouté de nouveaux opcodes pour manipuler les valeurs de 64 bits, incluant la multiplication, ainsi que des opcodes pour les convertir en valeurs de 32 bits pour les utiliser avec les opcodes Bitcoin ordinaires.
  • Nous avons ajouté des opcodes "d'introspection directe" pour accéder aux données de la transaction, éliminant le besoin de "la machine" et réduisant notre nombre d'opcodes. Nous avons également ajouté un opcode TXWEIGHT pour accéder au poids de la transaction pour le calcul des frais.
  • Nous avons ajouté des opcodes de "hachage en continu" pour permettre d'introduire des données dans un moteur de hachage sans avoir à les placer toutes dans un seul élément de pile. Cela permet de contourner la limite de 520 octets imposée aux éléments de la pile sans nécessiter une utilisation supplémentaire des ressources de l'interpréteur de script.
  • Nous avons ajouté un opcode ECMULSCALARVERIFY pour traiter les sorties Taproot, les engagements de paiement par contrat et d'autres opérations de cryptographie à courbe elliptique.
  • Nous avons suivi l'exemple de Bitcoin en éliminant la limite de 201 opcodes, bien qu'avec les améliorations ci-dessus, cela aurait pu être gérable.

Une fois terminé, nous disposions d'un petit ensemble de nouveaux opcodes qui s'intègrent facilement au modèle Bitcoin Script (c'est-à-dire sans accès au disque, sans utilisation illimitée des ressources et sans flux de contrôle complexe tel que les boucles) et qui résolvent de gros problèmes permettant l’utilisation des conditions de dépense en production sur Elements.

Voici une liste complète des nouveaux opcodes.

Après avoir surmonté cet obstacle, nous sommes prêts à nous attaquer à des questions plus complexes concernant les conditions de dépense, qui ne sont pas propres à Elements.

Comme exemple concret des nouvelles choses que nous pouvons faire avec ces opcodes, considérons le schéma de voûte où nous pouvons restreindre le nombre de retraits dans un délai de 60 blocs (MAX_WITHDRAW).

Pour créer cette condition de dépense, nous devons respecter les conditions suivantes (décrites en notation Miniscript, que nous aborderons en détail dans un prochain article).

  • Retrait partiel (partial_withdraw) : S'il y a plus de MAX_WITHDRAW sats dans la condition, alors il doit rester au moins total_value - MAX_WITHDRAW.
  • "num64_gt(curr_inp_v,MAX_WITHDRAW)" : Le nombre total de sats en entrée est supérieur à MAX_SATS
  • "asset_eq(curr_inp_asset,out_asset(0))" : L'actif d'entrée et l'actif de sortie 0 sont identiques
  • "num64_geq(out_v(0),sub64(curr_inp_v,MAX_WITHDRAW))" : La valeur de la sortie doit être au moins égale à total_value - MAX_WITHDRAW.
  • "spk_eq(curr_inp_spk,out_spk(0))" : Les jetons restants sont envoyées à la condition
  • Retrait complet (full_withdraw) : Si la condition a moins de MAX_WITHDRAW sats, alors les sorties n'ont pas de contrainte.
  • num64_leq(curr_inp_v,MAX_WITHDRAW)
  • Contrôle des clés(keys) : Attendez 60 blocs avant de dépenser à nouveau et exigez une clé K pour le dépenser.
  • pk(K) : Requiert la signature de la clé K
  • plus vieux(60) : 60 blocs de temps d'attente avant le prochain retrait
  • curr_idx_eq(0) : L'index de dépense actuel doit être égal à 0. Cela permet d'éviter le problème dit de demi-dépense, où l'on peut dépenser deux utxos de condition ensemble, et l'un d'entre eux peut échapper à la condition.
  • Enfin, nous combinons toutes les conditions ci-dessus : and(keys,or(full_withdraw,partial_withdraw))

Il est possible de les étendre avec des clés de retrait d'urgence, des retraits en plusieurs étapes, une liste blanche et des dépositaires à signatures multiples.

Problèmes ouverts et travaux futurs

En étendant le Miniscript avec le support des conditions de dépense et en fournissant des opcodes pour le faire efficacement, nous avons créé un cadre dans lequel les utilisateurs peuvent facilement construire des scripts avec des conditions de dépense, comprendre le comportement de ces scripts et créer des transactions qui exécutent ces contrats. Mais en faisant cela, nous avons sapé la structure récursive de Miniscript et donc brisé certaines formes d'analyse.

Par exemple, il est possible de «composer» deux Miniscripts en créant un «and_b node» dont les deux enfants sont les deux scripts originaux. (En termes de script, nous ne faisons que concaténer les scripts originaux et ajouter un opcode BOOLAND à la fin). Si un utilisateur est convaincu qu'il peut satisfaire chacun des scripts originaux indépendamment, il peut être assuré qu'il peut satisfaire le script combiné.

Inversement, si il veut déterminer la satisfiabilité d'un script complexe, il peut parcourir le script de manière récursive : chaque fois qu'il voit un «and node», il doit satisfaire les deux sous-scripts, tandis que chaque fois qu'elle voit un «or node», il doit satisfaire l'un d'eux.

Avec les conditions de dépense, cette forme de composition n'est plus valable. Bien que vous puissiez toujours combiner deux scripts en utilisant «and_b» pour obtenir un script syntaxiquement valide, le résultat peut être insatisfaisant même si les deux sous-scripts peuvent être satisfaits. Par exemple, considérons la combinaison d'un script qui exige que la première sortie de la transaction brûle l'actif A avec un script qui exige que la première sortie de la transaction brûle l'actif B. Il est clair que les deux ne peuvent pas être satisfaits simultanément.

En général, comme les conditions de dépense introduisent des conditions globales sur la transaction en cours, les fragments de script individuels peuvent avoir des effets non locaux sur d'autres fragments de script. Nous pouvons aussi observer que dans Bitcoin Miniscript, nous pouvons considérer nos scripts comme des fonctions monotones des conditions de dépense. Un script représente une règle pour décider quels ensembles de conditions de dépense (signatures, préimages de hachage, etc.) sont valides pour dépenser un jeton, et quels ensembles sont invalides. Le fait que ces règles soient monotones signifie que si un ensemble de conditions est valide, n’importe quel sur-ensemble l'est aussi.

Avec les conditions de dépense, ce modèle ne s'applique tout simplement pas, et nous perdons donc les outils d'analyse généraux et puissants que fournit Miniscript. Au lieu de cela, les programmes Miniscript d'Elements dotés de conditions de dépense doivent être analysés de manière ad hoc, en posant des questions spécifiques et en y répondant.

Simplicity, un remplacement complet de Script sur lequel nous travaillons en parallèle, présente également cette limitation. Pour rendre ce type d'analyse ad hoc tenable, l'interpréteur de Simplicity a une implémentation de référence dans l'assistant de démonstration de théorèmes Coq, ce qui signifie que si les humains doivent trouver les bonnes questions à poser, les réponses seront au moins solides et vérifiables par la machine. Avec Elements Miniscript, les programmes sont entièrement dépendants de la révision humaine du code. Nous espérons l'avoir conçu de manière à ce que des programmes puissants puissent néanmoins être simples et corrects.

À l'avenir, nous prévoyons de continuer à étendre la fonctionnalité du Miniscript Elements, d'écrire des outils et des bibliothèques de portefeuilles qui peuvent l'utiliser pour produire et interagir avec les conditions de dépense, et de continuer à avancer sur Simplicity, qui sera encore plus étendu que Script Elements nouvellement amélioré.

Bon piratage !

If you have specific preferences, please, mark the topic(s) you would like to read: