Pay to Script Hash (P2SH) pleinement expliqué

Bitcoin est un système de monnaie programmable et constitue la première implémentation de ce qu’on appelle les smart contracts ou contrats autonomes. À chaque transaction, des scripts sont exécutés pour vérifier que les fonds dépensés remplissent les conditions voulues par l’utilisateur précédent. De nombreuses conditions sont peuvent être mises en place (divulgation d’un secret, verrou temporel, multisignature), même si le plus souvent les fonds sont simplement dépensés grâce à la signature d’un utilisateur unique.

Bitcoin repose sur un modèle de pièces (UTXO), où chaque pièce est verrouillée par un script incomplet écrit sur la chaîne. Lors d’une transaction, les pièces de bitcoin en entrée sont déverrouillées par un script complémentaire. Puis, de nouvelles pièces sont créées à partir des anciennes grâce à de naouveaux scripts de verrouillage, ce qui perpétue le caractère programmable du système.

Transaction : UTXO et scripts

Lorsque j’ai découvert comment les transactions fonctionnaient et ce qu’elles permettaient, j’ai été fasciné par l’élégance de cette solution. Néanmoins, en approfondissant ma recherche, j’ai été perturbé par l’existence d’une chose qui différait des autres, une exception : le schéma Pay to Script Hash, qu’on abrège couramment en P2SH.

 

Les différents schémas : P2PK, P2PKH, P2MS et P2SH

Des scripts sont impliqués dans chaque transaction du réseau Bitcoin. Le langage de script est constitué de plus d’une centaine de codes opération, de sorte qu’un large éventail de possibilités s’offre à nous vis-à-vis des conditions qu’on souhaite imposer.

Cependant, dans le but d’améliorer la communication entre les différentes applications et de limiter le risque de perte, certains standards de scripts, ou schémas, ont émergé.

 

P2PK : Pay to Public Key

Le premier schéma s’appelle Pay to Public Key (P2PK), qu’on peut traduire littéralement en français par « payer à la clé publique ». Il s’agit d’envoyer des fonds vers la clé publique (public key) d’un utilisateur, que lui seul pourrait dépenser en signant avec sa clé privée. Le script de verrouillage (parfois appelé scriptPubKey) permettant ce type d’envoi est :

<clé publique> CHECKSIG

Au moment de la dépense, l’utilisateur doit utiliser un script de déverrouillage (parfois appelé scriptSig) contenant simplement sa signature :

<signature>

Pour l’expliquer en français, l’exécution successive de ces deux scripts permet de vérifier que la signature fournie par l’utilisateur correspond à sa clé publique, auquel cas elle est valide.

Si le schéma P2PK était utilisé dans les premiers jours de Bitcoin, notamment pour recevoir les gains de minage, il est aujourd’hui tombé en désuétude au profit d’un schéma rival : P2PKH.

 

P2PKH : Pay to Public Key Hash

Pay to Public Key Hash (P2PKH), littéralement « payer à l’empreinte de la clé publique », est le deuxième type de schéma apparu dans Bitcoin dès le début, grâce à la conception de Satoshi Nakamoto. Ce schéma permet non pas de réaliser un paiement vers une clé publique, mais vers l’empreinte d’une clé publique, et de faire en sorte que le système de script de Bitcoin vérifie quand même que la signature correspond à la clé publique lors de la dépense des fonds. L’empreinte de la clé publique est alors considérée comme la donnée essentielle de l’adresse, qui dans ce cas commence toujours par un 1, comme par exemple 1DzxhUphLFq8FZPGbFgLF8Ssz3hMX9EuMp.

Le script de verrouillage ici est :

DUP HASH160 <empreinte de la clé publique> EQUALVERIFY CHECKSIG

Et le script de déverrouillage est :

<signature> <clé publique>

Pour le dire en français, l’exécution des deux scripts permet de :

  • Vérifier que le passage de la clé publique fournie par la fonction de hachage HASH160 (l’empreinte) est égale à l’empreinte qui est spécifiée dans le script ;
  • Vérifier la signature fournie correspond à la clé publique fournie.

L’avantage de ce schéma est qu’il permet d’avoir des adresses plus courtes (l’information à encoder n’est que de 20 octets au lieu de 65 octets pour une clé publique), chose pour laquelle Satoshi Nakamoto l’a implémenté. De plus, en ne révélant la clé publique qu’au moment de la dépense, ce schéma accroît aussi la sécurité contre la menace (très hypothétique) de l’ordinateur quantique.

 

P2MS : Pay To MultiSig

Le schéma Pay To MultiSig (P2SH), littéralement « payer à la multisignature », s’est popularisé début 2012. Il s’agit essentiellement d’un schéma qui permet d’exiger la signature de M personnes faisant partie d’un groupe de N participants, via le script de verrouillage :

M <clé publique 1> ... <clé publique N> N CHECKMULTISIG

Le script de déverrouillage correspondant est :

<leurre (0)> <signature 1> ... <signature M>

Pour plus d’informations sur la multisignature, je vous invite à lire mon article sur les adresses multisignatures.

C’est ce schéma, particulièrement exigeant au niveau de la mise en place, qui a motivé la création du schéma P2SH.

 

P2SH

Pay to Script Hash (P2SH), littéralement « payer à l’empreinte du script ». Ce schéma reprend l’idée derrière P2PKH, à la seule différence que la donnée hachée ici n’est pas une clé publique, mais le script lui-même ! Le script en question est alors appelé script de règlement (redeem script) et son empreinte est la donnée constituante de l’adresse, cette dernière commençant toujours par un 3 à l’instar de 3DyDCGSC59yYY46dnRH7Vw1iKbV8zeW36q.

Ce type de schéma a pour avantage de permettre à un utilisateur d’y inclure n’importe quel script et de pouvoir recevoir des fonds de la quasi-totalité des portefeuilles existants. Le fardeau de la construction et du déverrouillage du script revient donc au détenteur de l’adresse, non à celui qui envoie les fonds, ce qui simplifie grandement la communication.

Le script de verrouillage pour le schéma P2SH est :

HASH160 <empreinte du script de règlement> EQUAL

Et le script de déverrouillage est un script de la forme :

[éléments de déverrouillage] <script de règlement>

En français, cela veut dire que le système de script originel de Bitcoin va vérifier que le hachage du script de règlement est égal à l’empreinte inscrite dans le script. Et c’est tout.

Comment ça, « c’est tout » ? Le script de règlement n’est pas exécuté ? N’importe qui connaissant le script pourrait dépenser les bitcoins ?

Comme on va le voir, ce n’est pas le cas et le script de règlement est bien exécuté, bien que ce ne soit pas indiqué explicitement.

 

Règles et exceptions : OP_EVAL et P2SH

Dans la vie, il y a souvent une manière élégante de faire les choses, qui demande parfois plus d’efforts initiaux mais qui préserve l’ordre et la simplicité, et une manière grossière, plus facile à implémenter mais qui complique les choses et crée le désordre.

Ainsi, dans le système législatif d’un pays, il est plus facile de créer et de faire voter de nouvelles lois execeptionnelles que de réformer en profondeur le système. Cette tendance à l’inflation législative fait qu’on se retrouve avec des pays comme la France, qui cumule 73 codes juridiques en vigueur et qui vit de 214 taxes et impôts ainsi que d’une myriade de cotisations sociales.

En informatique comme en droit, l’ajout de nouvelles exceptions crée de la dette technique, rendant le système plus complexe à appréhender, plus difficile à maintenir et plus susceptible de ne pas fonctionner comme attendu. P2SH fait partie de ces exceptions.

 

Le code opération mort-né : OP_EVAL

L’idée d’implémenter un schéma de script qui utilise l’empreinte d’un autre script comme identifiant est née en 2011, afin de reproduire ce qui est réalisé dans la schéma P2PKH. Toutefois, cette idée n’a pas été développée initialement comme P2SH, mais à travers OP_EVAL, un nouveau code opération permettant l’exécution récursive d’un script à l’intérieur d’un autre script.

L’ajout de ce code opération, proposé le 18 octobre 2011 par Gavin Andresen, devait être implémenté comme un soft fork, via le remplacement de l’instruction nulle OP_NOP1.

Un schéma standard aurait également été ajouté. Le script de verrouillage imaginé pour ce schéma était :

DUP HASH160 <empreinte du script de règlement> EQUALVERIFY EVAL

Le script de déverrouillage correspondant était :

[éléments de déverrouillage] <script de règlement>

Pour le dire en français, l’exécution des deux scripts côte à côte aurait permis de :

  • Vérifier que le hachage du script de règlement soit égal à l’empreinte spécifiée dans le script de verrouillage ;
  • Vérifier que l’exécution du script de règlement combiné aux éléments de déverrouillage soit bien valide.

Néanmoins cette solution n’a pas été acceptée, celle-ci ayant été jugée trop dangereuse au niveau du pouvoir de récursion. À la place c’est un autre modèle, plus restrictif, qui a prévalu : le schéma P2SH.

 

Comment fonctionne P2SH ?

P2SH a été proposé le 3 janvier 2012 comme alternative à OP_EVAL et à d’autres propositions. Il a été intégré au protocole Bitcoin le 1er avril sous la forme d’un soft fork activé par les mineurs.

L’exécution de ce type de script fonctionne exactement comme le schéma lié à OP_EVAL, à l’exception qu’une partie du script n’est pas explicitement indiquée. D’une part, la vérification de la correspondance entre l’empreinte indiquée et le script de règlement est bien réalisée par le script de verrouillage. En effet, celui-ci devient (comme on l’a vu) :

DUP HASH160 <empreinte du script de règlement> EQUALVERIFY EVAL

D’autre part, l’évaluation du script de règlement est effectuée implicitement grâce à une exception ajoutée au code source. Dès que les nœuds du réseau reconnaissent le schéma, ils l’interprètent différemment. Ainsi dans Bitcoin Core, on peut observer la condition suivante au sein de la fonction VerifyScript de l’interpréteur :

// Additional validation for spend-to-script-hash transactions:
if ((flags & SCRIPT_VERIFY_P2SH) && scriptPubKey.IsPayToScriptHash())
{
    ...
}

Cette exception permet l’exécution du script de règlement après l’exécution des deux scripts (déverrouillage et verrouillage). D’où le fait qu’on indique les éléments de déverrouillage avant de pousser le script de règlement dans le script de déverrouillage :

[éléments de déverrouillage] <script de règlement>

Si cette solution est pratique, elle crée une complexité et n’est pas très élégante. Comme l’a dit Gavin Andresen dans l’explication du BIP-16 :

Reconnaître une forme « spéciale » de scriptPubKey et réaliser une validation supplémentaire quand elle est détectée, c’est laid. Cependant, l’avis général est que les alternatives sont soit encore plus laides, soit plus complexes à implémenter, et/ou étendent le pouvoir du langage d’expression de manière dangereuse.

L’implémentation initiale de P2SH a donc compliqué les choses. Mais cela ne s’est pas arrêté pas là, car l’activation de SegWit en 2017 a ajouté de la complexité au modèle.

 

SegWit, P2SH-P2WPKH et P2SH-P2WSH

En août 2017, la mise à niveau SegWit a été intégrée à Bitcoin (BTC) sous la forme d’un soft fork. Celle-ci avait pour objectif de corriger la malléabilité des transactions, d’augmenter la capacité transactionnelle, d’améliorer la vérification des signatures et de faciliter les modifications futures du protocole.

Pour ce faire, SegWit implémentait un nouveau modèle de transaction, où les signatures sont situées dans une partie séparée de la transaction appelée le témoin (d’où le nom de Segregated Witness). Afin d’implémenter ce changement comme un soft fork, il a été nécessaire d’introduire un moyen d’accéder au nouveau type de transaction sans briser la compatibilité avec les anciennes adresses.

C’est ainsi que 4 nouveaux types d’adresse ont vu le jour : deux nouveaux types « natifs », P2WPKH (Pay to Witness Public Key Hash) et P2WSH (Pay to Witness Script Hash), incompatibles avec portefeuilles ne supportant pas SegWit ; et deux types « imbriqués » associés, P2SH-P2WPKH et P2SH-P2WSH, qui permettent la transition grâce à l’emploi de P2SH.

Pour ces deux derniers types d’adresse, le schéma utilisé est P2SH avec un script de règlement de la forme :

<version SegWit> <empreinte>

Dans la version 0 de SegWit (la seule qui existe pour le moment), l’empreinte est affectée à une clé publique ou à un script selon sa longueur : si elle est de 20 octets, elle est interprétée comme une empreinte de clé publique ; si elle est de 32 octets, elle est interprétée comme une empreinte de script. Cette empreinte est aussi appelée « programme ».

Ce script est anyone-can-spend puisque le script de règlement suffit à déverrouiller la pièce :

<script de règlement>

Comme pour P2SH, la connaissance du script de règlement ne suffit pourtant pas à dépenser les fonds, car une nouvelle exception est ajoutée au code de façon à ce que les éléments de déverrouillage soient transférées dans le témoin. Dans Bitcoin Core, cette exception se traduit par :

// P2SH witness program
if (flags & SCRIPT_VERIFY_WITNESS) {
    ...
}

SegWit a ainsi apporté un nouveau lot de complexité, et notamment un deuxième niveau de récursion. Dans le cas du schéma P2SH-P2WSH, on a en effet une série de 3 scripts imbriqués. Le premier script est le script de déverrouillage que l’on a présenté au début de cet article :

HASH160 <empreinte du script de règlement> EQUAL

Le deuxième script est le script de règlement spécifique à P2SH :

<version SegWit> <empreinte du script SegWit>

Le troisième est le script SegWit, indiqué dans le témoin avec les éléments de déverrouillage au moment de la dépense des fonds. Par exemple, il peut s’agir d’un script de multisignature comme vu précédemment :

2 <clé publique 1> <clé publique 2> 2 CHECKMULTISIG

qu’on complète avec les éléments :

0 <signature 1> <signature 2>

 

Conclusion

Pay to Script Hash (P2SH) est donc une manière imparfaite mais très pratique de permettre aux utilisateurs de payer à l’empreinte d’un script, c’est-à-dire à une adresse simple qui correspond à un script. La récursion qui intervient dans l’exécution du script aurait pu être implémentée de manière plus élégante grâce au code opération OP_EVAL, mais ce dernier a été jugé trop dangereux par la communauté pour voir le jour.

En ajoutant une nouvelle exception au protocole pour être exécuté, le schéma P2SH représente un vecteur de complexité. De plus, cette complexité est démultipliée par l’incorporation de SegWit, qui ajoute de nouvelles exceptions rigides à P2SH et qui finit de détourner complètement le fonctionnement originel du système de script de Bitcoin.

Néanmoins, ce qui est fait est fait, et aujourd’hui ces changements commencent à être connus dans l’écosystème, et on peut donc espérer que cette complexité n’impacte pas trop les nouveaux développeurs. En particulier, le schéma P2SH est répandu dans tout l’écosystème, par les protocoles associés à Bitcoin tel que Bitcoin Cash, Litecoin ou Dash. Seuls les développeurs de Bitcoin SV ont se sont opposés à cette particularité de manière catégorique et ont choisi de désactiver P2SH en février 2020.

Ce qu’il faut retenir de tout ceci, c’est que Bitcoin, au-delà de son aspect technique, est un système économique et social. Il évolue selon les exigences de ses utilisateurs, si bien qu’il est impossible de le comprendre sans appréhender les dynamiques sous-jacentes qui ont été à l’œuvre dans le passé.

 


Sources

Gavin Andresen, BIP-11 (M-of-N Standard Transactions), 18 octobre 2011.
Gavin Andresen, BIP-12 (OP_EVAL), 18 octobre 2011.
Gavin Andresen, BIP-16 (Pay to Script Hash), 3 janvier 2012.
Mike Hearn, On consensus and forks, 12 août 2015.
Eric Lombrozo, Johnson Lau et Pieter Wuille, BIP-141 (Segregated Witness), 21 décembre 2015.

Je suis fasciné par les cryptomonnaies et par l'impact qu'elles pourraient avoir sur nos vies. De formation scientifique, je m'attache à décrire leur fonctionnement technique de la façon la plus fidèle possible.

0 Comments

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.