You can also read the English version of this article.
Teil 1: Wie funktioniert Bitcoin Script, und warum ist es so komplex?
Teil 2: Was ist Miniscript, und wie macht es Bitcoin Scripting einfacher?
Teil 3: Einen Miniscript-Parser und -Analysator in Go schreiben
Die einzigen "Bitcoin-Ausgabebedingungen", die heute weit verbreitet sind, sind einfache Single-Sigs und einfache Multisigs. Und das obwohl Bitcoin Script, die Sprache, mit der die Ausgabebedingungen in Bitcoin-Transaktionen kodiert werden, noch sehr viel mehr kann. Warum ist das der Fall?
Der Grund dafür ist, dass Bitcoin Script zwar oberflächlich betrachtet eine einfache, stackbasierte Sprache zu sein scheint, in der Praxis aber sehr schwierig zu handhaben ist. Für jede neue Ausgabebedingung, die ein Entwickler erstellen möchte, muss eine Menge Zeit aufgewendet werden, um sicherzustellen, dass sie unter allen Umständen korrekt und solide ist. Dies kann in der Praxis sehr schwierig sein. Wir werden uns in Kürze ein motivierendes Beispiel ansehen.
Am wichtigsten ist jedoch, dass der Mangel an Standardisierung und Tools für diese Art von Scripten es für Wallets und andere Software schwierig machen kann, mit ihnen zu interagieren. In der Praxis bedeutet das: Selbst wenn du dich entscheidest, ein neues Script zu entwickeln, wirst du am Ende eine Wallet haben, die nicht standardisiert ist. Andere Wallets werden damit nicht kompatibel sein, was natürlich schlecht für die Nutzer ist.
In dieser Artikelserie werden wir uns eingehend mit Bitcoin Miniscript beschäftigen. Bitcoin Miniscript ist eine High-Level Programmiersprache für Bitcoin-Scripte, die es Wallet-Entwicklern leicht machen soll, komplexe Ausgabenbedingungen zu erstellen, deren Korrektheit und Zuverlässigkeit zu beurteilen, und mit der alle Wallets und Tools einfach interagieren können.
BitBox02
Als Entwickler der Hardware-Wallet BitBox02 suchen wir ständig nach Möglichkeiten, die Sicherheit und Benutzerfreundlichkeit für Self-Custody zu verbessern. In der Vergangenheit haben wir das anti-klepto Protokoll und Verbesserungen der Multisig-Sicherheit für alle Hardware-Wallet-Anbieter implementiert und bereitgestellt.
Kevin und Antoine von Revault/wizardsardine haben sich mit uns in Verbindung gesetzt, um die Möglichkeit zu besprechen, die BitBox02 um Unterstützung für Miniscript zu erweitern. Dies war auch ein wichtiges Thema auf der BTC Azores '22 Unkonferenz.
Wir sehen Miniscript, zusammen mit Covenants und MuSig, als wichtige Entwicklungen für die Selbstverwahrung und die BitBox02.
Miniscript ermöglicht komplexere Ausgabebedingungen, die Wizardsardine in Liana, einer neuen Art von Wallet, die sie entwickeln, vorstellt.
Wenn die BitBox02 Miniscript unterstützt, können auch innovative Wallets wie Liana mit der BitBox02 verwendet werden. Dies würde auch die Entwicklung von modernen Self-Custody-Lösungen innerhalb der BitBoxApp ermöglichen.
Bevor wir mit der Integration beginnen können, müssen wir zunächst unser Wissen über Miniscript vertiefen. Meiner Erfahrung nach lernt man am besten etwas über ein Software-Engineering-Thema, indem man es selbst umsetzt. Wenn du es selbst nachbaust, bist du gezwungen, jedes Detail zu berücksichtigen. Eine andere effektive Methode ist, es anderen zu erklären. Diese Blogpost-Serie dient beiden Zwecken - sie enthält eine Implementierung von Miniscript und soll dir Miniscript in einem anderen Format als dem heute verfügbaren vermitteln.
Sehr kurze Einführung in das Bitcoin Script
Bitcoins werden in der Regel durch Scripte gelockt. Diese kodieren, welche Art von Bedingungen erfüllt sein müssen, um die Coins auszugeben. In dieser Serie werden wir uns auf P2WSH (Pay-to-Witness-Script-hash) konzentrieren. Dort kodiert das Witness-Script die Bedingungen, die erfüllt werden müssen, um einen Bitcoin auszugeben. Dazu gehören in der Regel öffentliche Schlüssel. Der Witness sind die Daten, die benötigt werden, um die Ausgabebedingungen zu erfüllen. Witnesses enthalten in der Regel Signaturen, die den öffentlichen Schlüsseln entsprechen.
Um einen Coin auszugeben, der mit einem Witness Script gelockt wurde, muss die Transaktion, die ihn ausgibt, einen gültigen Witness enthalten. Der Witness und das Witness-Script werden nach den Bitcoin-Script-Regeln ausgewertet.
Eine Bitcoin-Adresse wie zum Beispiel bc1q2fhgukymf0caaqrhfxrdju4wm94wwrch2ukntl5fuc0faz8zm49q0h6ss8
ist einfach eine Kodierung der Tatsache, dass es sich um eine P2WSH-Ausgabe handelt, die den Hash des Witness-Scripts enthält. Bitcoin-Nodes wissen, dass, wenn sie einen Coin sehen, der an einen solchen Output gesendet wird, die Transaktion, die diesen Coin ausgibt, das entsprechende Witness-Script enthalten muss, sowie den Witness, der benötigt wird, um das Witness-Script zu erfüllen.
Um zum Beispiel eine einfache Single-Signature-Key-Bedingung zu kodieren, würde das Witness-Script wie folgt lauten:
<publicKey> OP_CHECKSIG
und der Witness würde lauten
<Signatur>
Bei der Überprüfung des Scripts werden der Witness und das Witness-Script der Reihe nach ausgeführt, beginnend auf einem leeren Stack:
- Initialer Stack: leer.
- Die Signatur wird auf den Stack geschoben:
<Signatur>
- Der öffentliche Schlüssel wird auf den Stack gelegt:
<Signatur> <publicKey>
. OP_CHECKSIG
entfernt die beiden obersten Stack-Elemente, überprüft die Signatur und gibt bei einem Fehler eine 0 oder bei Erfolg eine 1 aus.
Wenn genau ein Element, das nicht Null ist, auf dem Stack verbleibt und das Script nicht abgebrochen wurde, ist der Witness gültig und der Coin kann ausgegeben werden.
In Bitcoin Script gibt es neben OP_CHECKSIG
noch eine ganze Reihe anderer OP-Codes, mit denen man komplexere Ausgabenbedingungen kodieren kann. Zum Beispiel kannst du mit OP_CHECKMULTISIG
Coins in einem Multi-Signatur-Output locken oder mit OP_CHECKSEQUENCEVERIFY
die Coins für eine bestimmte Zeitspanne sperren.
Ein motivierendes Beispiel für Miniscript
Miniscript hilft Entwicklern, sicherere und effizientere Bitcoin-Scripts zu schreiben, indem es verschiedene Probleme der Bitcoin-Script-Sprache behebt. Schauen wir uns einige einfache Ausgabebedingungen an, die man möglicherweise verwenden möchte, um die Schwierigkeiten bei der direkten Arbeit mit Bitcoin Script zu verdeutlichen. Später werden wir sehen, wie Miniscript diese Probleme löst und es viel einfacher macht, neue Ausgabenbedingungen zu entwickeln und einzusetzen.
Nehmen wir zum Beispiel an, wir wollen, dass eine von zwei Personen einen Coin ausgeben kann. Die einfachste Lösung ist die Verwendung von OP_CHECKMULTISIG
. Das Witness-Script ist:
1 <publicKey1> <publicKey2> 2 OP_CHECKMULTISIG
Die 1
verrät OP_CHECKMULTISIG
, wie viele Unterschriften geliefert werden müssen, und die 2
sagt ihm, wie viele öffentliche Schlüssel es gibt.
Der Witness ist:
<> <Signatur>
(das leere Element am Anfang des Witness ist eigentlich nutzlos und existiert aufgrund eines Fehlers in der ursprünglichen Implementierung von OP_CHECKMULTISIG
, die ein Element zu viel aus dem Stack entfernt).
Das daraus resultierende Verifizierungsscript, <> <Signatur> 1 <publicKey1> <publicKey2> 2 OP_CHECKMULTISIG
, hinterlässt eine 1
auf dem Stack, wenn es eine gültige Signatur gibt, die mit einem der beiden öffentlichen Schlüssel übereinstimmt, und ansonsten eine 0
.
Ändern wir nun die Semantik leicht ab. Anstatt pubkey1 ODER pubkey2
, versuchen wir pubkey1 ODER (pubkey2 in einem Jahr)
: Der Coin kann jederzeit von einer der beiden Personen ausgegeben werden, oder von einer anderen Person, nachdem sie ein Jahr gewartet hat. Da OP_CHECKMULTISIG
nur Signaturen und keine Timelocks prüfen kann, muss das Script komplett geändert werden. Es kann viele Scripts geben, die eine Reihe von Ausgabebedingungen umsetzen. Hier ist eine von vielen möglichen Lösungen für diese:
Lösung
<pubkey1> OP_CHECKSIG OP_IFDUP OP_NOTIF
<pubkey2> OP_CHECKSIGVERIFY <52560 (ein Jahr)> OP_CHECKSEQUENCEVERIFY
OP_ENDIF
52560 ist ein Jahr in Anzahl der Bitcoin-Blöcke, die im Durchschnitt alle zehn Minuten gefunden werden.
Mögliche Witnesses:
- Zu jeder Zeit:
<Signatur für pubkey1>
- Nur wenn ein Jahr vergangen ist:
<Signatur für pubkey2> <>
.
Schauen wir uns nun die Ausführung des Scripts mit dem zweiten Witness an. Wie zuvor werden der Witness und das Witness-Script der Reihe nach ausgeführt, beginnend mit einem leeren Stack. Angenommen, ein Jahr ist vergangen und die Signatur ist gültig:
- Anfänglicher Stack: leer.
- Pushe die Signatur:
<Signatur für pubkey2>
- Pushe leeres Element:
<Signatur für pubkey2> <>
- Pushe pubkey1:
<Signatur für pubkey2> <> <pubkey1>
- OP_CHECKSIG entfernt die beiden Elemente
<>
und<pubkey1>
vom Stack und prüft die Signatur gegen diesen Pubkey. Da die Signatur leer ist, ist es eine ungültige Signatur undOP_CHECKSIG
pusht eine0
:<Signatur für pubkey2> 0
. - OP_IFDUP dupliziert das oberste Stackelement, wenn es nicht Null ist. Da es Null ist, passiert nichts:
<Signatur für pubkey2> 0
. - OP_NOTIF: Entfernt das oberste Stackelement. Wenn es 0 ist, werden die Anweisungen bis OP_ENDIF ausgeführt:
<Signatur für pubkey2>
. - Pushe pubkey2:
<Signatur für pubkey2> <pubkey2>
. - OP_CHECKSIGVERIFY entfernt zwei Elemente (Signatur und Pubkey) und verifiziert die Signatur. Wenn sie gültig ist, passiert nichts, andernfalls wird die Ausführung mit einer Fehlermeldung abgebrochen). Der Stack ist jetzt: leer.
- Pushe 52560 (numerischer Wert, der ein Jahr repräsentiert):
<52560>
. - OP_CHECKSEQUENCEVERIFY sieht sich das letzte Element als Zeitdauer an. Wenn der auszugebende Coin jünger ist, bricht das Script mit einer Fehlermeldung ab. Wenn sie älter ist, passiert nichts:
<52560>
. - OP_ENDIF: Beendet den OP_IF-Block.
- Der Stack hat genau ein Nicht-Null-Element übrig, also ist das Script erfolgreich und der Coin kann ausgegeben werden.
In Schritt 5 wird das leere Signaturelement als "Dissatisfaction" bezeichnet. Es ist erforderlich, um den Teil zu überspringen, der den ersten Pubkey verifiziert, den wir nicht verwenden wollten. Beachte, dass nur das leere Element eine gültige Dissatisfaction für <key> OP_CHECKSIG
gemäß BIP141 ist - jede andere ungültige Signatur führt zu einem Abbruch des Scripts:
Signatur(en) müssen Nullvektor(en) sein, wenn ein OP_CHECKSIG oder OP_CHECKMULTISIG fehlgeschlagen ist (sowohl für das Pre-Segregated Witness Script als auch für P2WSH. Siehe BIP146)
Beachte auch, dass das Script nur erfolgreich ist, weil der Time-Lock von einem Jahr nicht Null ist. Wenn wir dasselbe Script verwenden, aber einfach die Zahl von einem Jahr durch 0 ersetzen, könnte man annehmen, dass sich unsere ursprüngliche Semantik pubkey1 OR (pubkey2 in einem Jahr)
in pubkey1 OR (pubkey2 jederzeit)
ändern würde. Da am Ende eine 0 auf dem Stack stehen würde, würde das Script immer fehlschlagen und die tatsächliche Semantik hat sich versehentlich in nur pubkey1
geändert und pubkey 2 kann nicht mehr ausgeben.
Als praktische Übung kannst du versuchen, das obige Script mit dem ersten Witness auszuführen, unter der Annahme, dass die erste Person signiert und die zweite Person nicht und dich davon überzeugen, dass es funktioniert.
Wie man sehen kann, ist die Erstellung solcher Scripte und gültiger Witnesses in Bitcoin Script ein mühsamer und fehleranfälliger Prozess, sogar für einfache Semantiken. Die Abfolge der Stack-Operationen ist kompliziert zu konstruieren und zu durchdenken. Wenn du die Ausgabebedingungen erweitern willst, beginnt der Entwicklungsprozess im Grunde genommen komplett von vorne. Ein tiefes Verständnis für Bitcoin Script ist erforderlich. Unter anderem musst du die Cleanstack-Regel kennen (die verlangt, dass am Ende der Ausführung nur ein Nicht-Null-Element übrig bleibt) und dass nur das leere Element eine gültige Dissatisfaction für eine Signaturprüfung ist.
Zusammenfassend lässt sich sagen, dass die direkte Arbeit mit Script aus den folgenden Gründen schwierig ist:
- Script lässt sich nicht gut verfassen, d.h. kleine Änderungen der gewünschten Ausgabebedingungen können zu völlig unterschiedlichen Scripts führen.
- Die Script-Op-Codes haben unterschiedliche Semantiken für Erfolg und Misserfolg, was es schwierig macht, sie zusammenzustellen und wiederzuverwenden. Einige von ihnen schieben bei Misserfolg oder Erfolg eine 0 oder 1 auf den Stack, während andere bei Misserfolg die gesamte Ausführung abbrechen und im Erfolgsfall nichts tun.
- Es gibt viele mögliche Lösungsscripts für einen Satz gewünschter Ausgabenbedingungen, so dass es schwierig ist, sich für eines zu entscheiden.
- Es ist schwierig, gültige Witnesses für alle Umstände zu erstellen.
- Es gibt Konsens- und Standardgrenzen für die Größe von Scripten, die Anzahl der Opcodes und Signaturen sowie die Anzahl der Elemente des Witness-Stacks, die ein Entwickler berücksichtigen muss, um eine Ablehnung durch das Netzwerk zu vermeiden.
- Ein komplexes Script zu entwerfen, das am Ende der Ausführung genau ein nicht leeres Element auf dem Stack hinterlässt, ist eine Herausforderung, wie wir im obigen Beispiel gesehen haben.
Im zweiten Teil dieser Serie werden wir uns ansehen, was Miniscript im Detail ist und wie es Bitcoin Script drastisch vereinfacht, so dass es in der Praxis möglich ist, ausgeklügelte Ausgabebedingungen zu verwenden. Bleib dran!
Du hast noch keine BitBox?
Deine Kryptowährungen sicher zu halten muss nicht schwer sein. Die BitBox02 Hardware Wallet speichert die privaten Schlüssel deiner Kryptowährungen offline. So kannst du deine Coins sicher verwalten.
Die BitBox02 gibt es auch als Bitcoin-only-Version mit einer radikal fokussierten Firmware: weniger Code bedeutet weniger Angriffsfläche, was deine Sicherheit weiter verbessert, wenn du nur Bitcoin speicherst.
Hol dir eine in unserem Shop!
Frequently Asked Questions (FAQ)
Was ist Bitcoin Miniscript?
Bitcoin Miniscript ist eine High-Level Programmiersprache für Bitcoin-Scripte, die es Wallet-Entwicklern erleichtert, komplexe Ausgabebedingungen zu erstellen.
Warum ist Bitcoin Script in der Praxis schwierig zu verwenden?
Obwohl Bitcoin Script eine einfache, stackbasierte Sprache zu sein scheint, ist es in der Praxis sehr schwierig zu handhaben und erfordert ein tiefes Verständnis.
Was ist der Vorteil von Miniscript gegenüber dem traditionellen Bitcoin Script?
Miniscript vereinfacht Bitcoin Script, macht es praktikabler und ermöglicht die Verwendung von ausgeklügelten Ausgabebedingungen.
BitBox Hardware-Wallets werden von der Shift Crypto AG, einem privaten Unternehmen mit Sitz in Zürich, in der Schweiz entwickelt und produziert. Unser Team aus Bitcoin-Entwicklern, Krypto-Experten und Sicherheitsingenieuren entwickelt Produkte, die unseren Kunden eine stressfreie Reise vom Anfänger zum Meister der Kryptowährung ermöglichen. Die BitBox02, unsere Hardware-Wallet der zweiten Generation, ermöglicht es den Nutzern, Bitcoin und andere Kryptowährungen zu speichern, zu schützen und mit Leichtigkeit zu handeln - zusammen mit der dazugehörigen Software, der BitBoxApp.