Dies ist ein Gastbeitrag von unserem Community-Mitglied und Foren-Moderator @sutterseba.

Das wichtigste in Kürze:
  • Bitcoin hat eine eigene kleine Programmiersprache, mit denen Ausgabebedingungen programmiert werden können.
  • Jeder Bitcoin Output hat ein solches "Mini-Programm", welches mit den richtigen Eingabewerten gelöst werden muss, um den Output ausgeben zu dürfen.
  • Wenn wir jemandem "Bitcoin schicken", dann erstellen wir eigentlich nur einen Output mit einem Script, welches vom Empfänger gelöst werden kann.
  • Es gibt einige Script Standards, um für bessere und einheitliche Nutzererfahrung zu sorgen.

Es gibt unzählige Programmiersprachen. Ob modern oder altbewährt, objektorientiert oder funktional, nah an der Hardware oder abstrakte Hochsprache: Alle Programmiersprachen haben Eigenschaften, die sie einzigartig machen. Sie sind für manche Dinge besonders gut, für andere eher schlecht geeignet. Viele von euch werden vielleicht sogar selbst schon in Sprachen wie Java, Python oder C++ etwas programmiert haben, doch heute soll es um eine besondere, kleine, aber dennoch sehr wichtige Sprache gehen: Bitcoin Script.

Disclaimer:

Für diesen Beitrag ist es wichtig, verstanden zu haben was ein Unspent Transaction Output (UTXO) ist und wie Bitcoin Transaktionen überhaupt funktionieren, da es sich bei diesem Beitrag um eine Vertiefung eben dieses Themas handelt.

Lese-Tipp: Was ist ein Unspent Transaction Output (UTXO)?

Die Sprache der Bitcoin Outputs

Wann ist eine Transaktion eigentlich gültig? Diese Frage dürfte einem Bitcoiner fast schon trivial vorkommen, aber eigentlich ist sie das nicht. Irgendwie muss schließlich einheitlich und unmissverständlich definiert werden, wann ein Output ausgegeben werden kann, und wann nicht.

Über den "gängigen Output" hinaus, der einfach nur eine gültige Signatur benötigt (wie einfach das wirklich ist sehen wir später), müssen auch mehrere Signaturen, Zeitsperren oder sonstige Bedingungen festgelegt werden können.

Dafür eignet sich eine Mini-Programmiersprache mit eingeschränkten Befehlen perfekt, da so nicht nur die Flexibilität erhalten bleibt, sondern man sich auf einen Standard einigt, der von jedem Netzwerkteilnehmer bzw. jeder Bitcoin Node individuell nachvollzogen werden kann.

Bevor wir weiter um den heißen Brei reden, schauen wir uns direkt an einem kleinen Beispiel an, wie die Bitcoin Script Sprache funktioniert.

Ein einfaches Beispiel

Der ein oder andere hat vielleicht schon einmal etwas von einer "Stack-oriented programming language", also einer Programmiersprache mit Stapelspeicher gehört. Der Stack ist dabei, wie der Name schon sagt, einfach nur ein Stapel auf dem Einträge abgelegt ("push") und von oben auch wieder weg genommen werden können ("pop").

Klingt etwas seltsam, aber für einen Computer ist das eine sehr sinnvolle und praktische Form der Speicherverwaltung. Damit einher geht auch eine, für uns Menschen, eher unintuitive Schreibweise, da man ein Script nicht chronologisch, sondern immer mit dem Stack im Hinterkopf lesen muss.

Um beispielsweise zwei Zahlen zu addieren, schreibt man in einer Stack basierten Sprache nicht wie üblich 5 + 8, sondern 5 8 +, da die beiden Zahlen zuerst auf den Stack gelegt und anschließend vom + Operator wieder weg genommen werden (um die Addition durchzuführen).

Schauen wir uns das an einem konkreten Bitcoin Script an:

Ein einfaches Additionsbeispiel in Bitcoin Script

Neben den drei Zahlen sehen wir hier zwei Befehle bzw. Funktionen, die auch Opcodes ("operation codes") genannt werden und mit OP_ beginnen. Diese Befehle sind Teil eines standardisierten Befehlssatzes, den ihr hier finden könnt.

Das Script wird jetzt von links nach rechts gelesen. Liegt am Ende nur noch die Zahl 1 auf dem Stack, ist das Script gültig.

  1. Die beiden Zahlen 9 und 12 werden nacheinander auf den Stack gelegt.
  2. Der Opcode ADD nimmt die beiden obersten Werte vom Stack (also die 9 und die 12), addiert diese und legt das Ergebnis wieder ab. Auf dem Stack liegt jetzt also nur die Zahl 21.
  3. Jetzt wird die rechts verbleibende 21 auch noch auf den Stack gelegt. Dort liegt jetzt also zweifach die Zahl 21.
  4. Der Opcode EQUAL nimmt die beiden obersten Werte vom Stack (also die 21 und die 21), und vergleicht diese miteinander. Sind beide Werte exakt gleich, wird die 1 auf den Stack gelegt, ansonsten die 0. Auf dem Stack liegt jetzt also die 1 und unser Script ist gültig!

Im Bitcoin Kontext nennen wir die Werte auf der rechten Seite das Locking Script (lock = sperren), welches sich in jedem Output wiederfindet. Darin wird exakt definiert was geschehen muss, damit der Output bzw. die darin "eingesperrten" Bitcoin ausgegeben werden können.

Die Werte in Orange auf der linken Seite bilden das Unlocking Script (unlock = entsperren), also eben die nötigen Eingabewerte um einen Output zu entsperren. Diese Werte stehen im Input einer Transaktion und setzen sich z.B. aus einem öffentlichen Schlüssel und einer digitalen Signatur zusammen.

Wenn man jemandem "Bitcoin schickt" bedeutet das also nichts anderes als einen Output zu erstellen, mit einem Locking Script, welches nur vom Empfänger gelöst werden kann. Theoretisch könnte ich sogar das Additionsbeispiel von oben nutzen und damit meine Bitcoin "sichern". Jeder, der bis 21 zählen kann (und das sollte für Bitcoiner kein Problem sein) könnte diesen Output sofort ausgeben, indem er oder sie einfach zwei Zahlen im Unlocking Script angibt, die zusammen addiert 21 ergeben.

Pay to Public Key

Das Grundprinzip eines digitalen Signaturverfahrens sollte bekannt sein: Mit einem privaten Schlüssel können wir eine Signatur erzeugen, die mit dem zugehörigen öffentlichen Schlüssel verifiziert werden kann.

Dieses Prinzip möchten wir jetzt in einem Bitcoin Script umsetzen. Neben einfachen Befehlen wie oben aus dem Beispiel, stehen uns auch komplexere Opcodes zur Verfügung, die kryptografische Funktionen ausführen können, z.B. eine Hashfunktion oder eben die Verifizierung einer digitalen Signatur.

Ein P2PK Script ist eigentlich ziemlich selbsterklärend:

  1. Die digitale Signatur <SIG> aus dem Unlocking Script wird zusammen mit dem im Locking Script stehenden öffentlichen Schlüssel <PK> auf den Stack gelegt. In der Realität stehen hier natürlich große Zahlen, zur Übersicht verwende ich hier nur farbige Abkürzungen.
  2. Der Opcode CHECKSIG nimmt die beiden obersten Werte vom Stack (bei denen es sich um eine Signatur und einen öffentlichen Schlüssel handeln sollte) und verifiziert, ob die Signatur gegen den öffentlichen Schlüssel gültig ist. In diesem Fall wird die 1 auf den Stack gelegt und das Script ist gültig.

In der Praxis findet P2PK allerdings kaum noch Anwendung. In den ersten Bitcoin Blöcken finden sich z.B. viele P2PK Outputs in den Coinbase Transaktionen, unter anderem auch im Genesis Block. Wahrscheinlich hat aber fast niemand, der diesen Beitrag gerade liest jemals einen P2PK Output erstellt. Doch warum? Einfacher geht es doch kaum, oder?

Wie der Name des Script Standards schon sagt, zahlen wir an einen öffentlichen Schlüssel. Nutzer müssten also lange und unleserliche Schlüssel austauschen, was nicht nur unpraktisch, sondern auch fehleranfällig ist. Wie Satoshi dieses Problem gelöst hat, sehen wir im nächsten Abschnitt.

Pay to Public Key Hash

Anstatt den langen öffentlichen Schlüssel des Empfängers direkt in das Locking Script zu schreiben, verwenden wir hier nur einen kryptografischen Hash ebendieses Schlüssels, also so etwas wie einen Fingerabdruck. Der eigentliche Schlüssel wird dann erst zusammen mit der Signatur beim Ausgeben im Unlocking Script angegeben.

Für die Berechnung dieser Hashsumme kommen zwei Hashfunktionen hintereinander zum Einsatz: SHA-256 gefolgt von RIPEMD-160. Letztere Funktion bildet, wie der Name schon verrät, eine nur 160 Bit große Hashsumme, was deutlich kürzer ist als der 256 Bit große öffentliche Schlüssel.

Die Kombination dieser beiden Hashfunktionen nennt man im Kontext von Bitcoin meistens HASH160 und es gibt, wer hätte es gedacht, einen Opcode der genau so heißt und genau diese Funktionalität hat.

Was haben wir jetzt also gewonnen? Im Locking Script müssen wir nur noch eine kürzere Hashsumme des öffentlichen Schlüssels speichern, aus dem wir (durch Codierung in ein anderes Zahlensystem und hinzufügen einer Prüfsumme) eine Bitcoin Adresse bilden können. Diese Adresse ist relativ kurz, durch die Base58 Codierung gut leserlich und eventuelle Tippfehler fallen durch die Prüfsumme sofort auf. Für den Nutzer sorgt dies also für eine deutlich bessere Erfahrung während wir im Output sogar Platz sparen können.

Funfact: Im Bitcoin Netzwerk bzw. in der Bitcoin Blockchain gibt es also gar keine Adressen, zumindest nicht in der Form, wie wir sie kennen. Diese werden erst auf Nutzerebene von Wallets erzeugt und tauchen in den eigentlichen Transaktionen nirgends auf!

Mit diesem Vorgehen haben wir uns aber ein neues Problem eingeholt: Der öffentliche Schlüssel ist jetzt nicht mehr bekannt, da man aus der Hashsumme schließlich nicht auf diesen zurückrechnen kann. Um die Signatur verifizieren zu können, brauchen wir aber den öffentlichen Schlüssel. Diesen müssen wir also zusätzlich zur Signatur angeben und überprüfen, ob dieser zum Hash im Locking Script passt. Auf den ersten Blick mag das Script ziemlich abschreckend aussehen, aber wir schauen uns das jetzt mal in aller Ruhe an.

  1. Die Signatur <SIG> und der öffentliche Schlüssel <PK> des Unlocking Script werden auf den Stack gelegt.
  2. Jetzt duplizieren, also kopieren, wir das oberste Element auf dem Stack bzw. den öffentlichen Schlüssel mit dem Opcode DUP. Einen brauchen wir für den Vergleich mit dem Hash und den anderen am Ende für die Verifizierung der Signatur.
  3. Der Opcode HASH160 führt jetzt wie oben beschrieben die beiden Hashfunktionen auf dem obersten Element, also einem der öffentlichen Schlüssel, aus. Auf dem Stack liegen jetzt also die Signatur, der öffentliche Schlüssel und ein Hash dieses öffentlichen Schlüssels.
  4. Der im Locking Script stehende Hash wird jetzt auch auf den Stack gelegt. Mit dem Opcode EQUALVERIFY wird jetzt verglichen, ob die beiden Hashsummen, also unser frisch erzeugter Hash und der Hash aus dem Locking Script, identisch sind. Wenn ja, passt unser öffentlicher Schlüssel zur Hashsumme im Locking Script und wir dürfen weiter machen!
  5. Ab jetzt handelt es sich eigentlich nur noch um ein P2PK Script. Auf dem Stack liegen jetzt die Signatur und der öffentliche Schlüssel. Mit CHECKSIG wird die Signatur verifiziert und, wenn gültig, dürfen wir den Output ausgeben.

Hier seht ihr das Auflösen eines P2PKH Scripts animiert in einem GIF:

Quelle: Greg Walker, learnmeabitcoin.com//technical/script

Herzlichen Glückwunsch! Du hast jetzt bis ins Detail verstanden, wie ein P2PKH Script funktioniert. Auch wenn die meisten heutzutage P2WPKH (Pay to Witness Public Key Hash), also "Native Segwit" verwenden werden, ist das grundlegende Prinzip dahinter genau gleich.

Pay to Script Hash

Jetzt wird es etwas abenteuerlicher! Wie in der Einleitung angedeutet können wir mit Bitcoin Script natürlich mehr machen als nur eine einzelne Signatur verifizieren. Dank des Standards, den wir uns jetzt anschauen werden, können wir uns tatsächlich komplett austoben und unsere eigenen Locking Scripts, z.B. auch das Additionsbeispiel vom Anfang, zusammen basteln und ganz gewöhnlich in unseren Transaktionen verwenden.

Bevor wir uns konkret mit P2SH befassen, müssen wir wie so oft zuerst das Problem verstehen, welches mit diesem Standard gelöst wird. Dafür schauen wir uns zunächst ein einfaches Multisig Script an.

Es handelt sich hier um ein klassisches Multisig Script mit einer 2/3 Aufteilung. Es gibt also insgesamt drei Schlüssel, zu denen wir zwei Signaturen vorlegen müssen. An dieser Stelle ist es gar nicht unbedingt wichtig das Script im Detail zu verstehen, trotzdem gehen wir es der Vollständigkeit halber kurz durch:

  1. Die beiden Signaturen, die Zahl 2, die drei öffentlichen Schlüssel und die Zahl 3 landen alle auf dem Stack.
  2. Der Opcode CHECKMULTISIG überprüft dann alles, was es zu überprüfen gilt, also ob erstens ein erforderliches Quorum erfüllt ist (2/3) und ob diese Signaturen auch gültig sind bzw. zu zwei der drei öffentlichen Schlüssel gehören. Die genaue Definition dieses Opcodes findet ihr ausführlich im Befehlssatz des Bitcoin Wikis.

Dieses Script macht zwar genau, was es soll, aber wir stoßen hier auf ähnliche (und weitere) Probleme wie anfangs mit P2PK:

  1. Wenn uns jemand Bitcoin mit diesem Script schicken will, muss er (in diesem Beispiel) drei öffentliche Schlüssel kennen, was maximal unpraktisch ist.
  2. Der Sender muss außerdem das genaue Script kennen. Bei den bisherigen Standards, die wir uns angeschaut haben, ist dies kein Problem, eben weil es verbreitete Standards sind, aber hier ist das natürlich nicht der Fall. Wir wollen uns schließlich kreativ austoben und unsere eigenen Locking Scripts verwenden.
  3. Wir müssen im Kopf behalten, dass der Sender der Transaktion die Outputs erstellt, also auch für den belegten Platz in Form von Netzwerkgebühren bezahlt. Wenn wir also ein riesiges und komplexes Locking Script verwenden wollen, müsste der Sender für unsere Sonderwünsche die Gebühren übernehmen.
  4. Unser benutzerdefiniertes Script wäre öffentlich einsehbar. Das eigene Sicherheitskonzept (also z.B. welche Multisig Aufteilung genutzt wird) wäre kein Geheimnis mehr, wir verlieren also Privatsphäre.

Jedes dieser Probleme wird mit Pay to Script Hash gelöst. Der Name verrät bereits, dass wir, ähnlich wie mit P2PKH, einen Hash im Locking Script festhalten -- allerdings kein Hash irgendeines öffentlichen Schlüssels, sondern der Hash von unserem benutzerdefinierten Locking Script. Das ermöglicht uns wieder das Erzeugen einer Bitcoin Adresse und wir sorgen für einen einheitlichen Script Standard.

Aber irgendwo muss doch unser eigenes Locking Script auftauchen? Richtig, nämlich erst im Unlocking Script! Dort "verpacken" wir zusammen mit dem eigentlichen Unlocking Script (also z.B. die beiden Signaturen) unser benutzerdefiniertes Script, welches wir Redeem Script (redeem = einlösen) nennen. Damit reduzieren wir die Größe des Outputs, können diesen standardisieren und unser Script wird erst beim Ausgeben veröffentlicht!

Das eigentliche Locking Script stellt ausschließlich sicher, ob das angegebene Redeem Script zum Hash im Locking Script passt (ähnlich wie bei P2PKH). Hier findet aber noch keine Verifizierung statt. Das verpackte Redeem Script wird, nachdem das "äußere" P2SH Script erfolgreich beendet wurde, ausgepackt und getrennt ausgeführt. Erst wenn sowohl Locking Script, als auch Redeem Script gelöst wurden, darf der Output ausgegeben werden.

  1. Die beiden Signaturen und das Redeem Script landen auf dem Stack. Hier wird bereits eine Kopie des Stacks erzeugt, um später das Redeem Script zu lösen.
  2. Mit HASH160 wird das Redeem Script gehasht und es liegen, nachdem der Hash aus dem Locking Script auch auf den Stack gelegt wurde, zwei Hashwerte auf dem Stack.
  3. Der Opcode EQUAL überprüft jetzt nur noch, ob diese beiden Werte identisch sind, also ob das Redeem Script tatsächlich zum Script Hash passt.
  4. Jetzt wird das verpackte Redeem Script (auf dem anfangs kopierten Stack) ausgepackt und wird zum neuen Locking Script, welches jetzt, wie oben im Multisig Beispiel beschrieben, gelöst wird.

Hier seht ihr das Auflösen eines P2SH Scripts (mit unserem Multisig Beispiel) animiert in einem GIF:

Quelle: Greg Walker, learnmeabitcoin.com/technical/p2sh

Fazit

Bitcoin Script bietet wahnsinnig viele Möglichkeiten. Es mag zwar keine universell mächtige Sprache sein wie bei diversen Altcoins (z.B. Solidity im Ethereum Netzwerk), aber das ist auch gut so. Eine kontrollierbare und einfach gehaltene Sprache sorgt schlussendlich für mehr Sicherheit und ermöglicht, dass man auch mit relativ günstiger Hardware Bitcoin Transaktionen selbst verifizieren kann.

Diesen Beitrag könnte man zwar noch ewig weiter führen, wir haben schließlich erst an der Oberfläche gekratzt, aber mir war es wichtig, dass die grundlegenden Funktionen klar werden und man ein besseres Gefühl dafür bekommt, was bei einer Bitcoin Transaktion im Hintergrund eigentlich konkret passiert.

Wer jetzt noch nicht genug hat (oder sich noch mehr verwirren will), der kann mal einen Blick auf das Script eines Hash Time Locked Contract (HTLC) werfen:

OP_DUP OP_HASH160 <RIPEMD160(SHA256(revocationpubkey))> OP_EQUAL
OP_IF
    OP_CHECKSIG
OP_ELSE
    <remote_htlcpubkey> OP_SWAP OP_SIZE 32 OP_EQUAL
    OP_IF
        OP_HASH160 <RIPEMD160(payment_hash)> OP_EQUALVERIFY
        2 OP_SWAP <local_htlcpubkey> 2 OP_CHECKMULTISIG
    OP_ELSE
        OP_DROP <cltv_expiry> OP_CHECKLOCKTIMEVERIFY OP_DROP
        OP_CHECKSIG
    OP_ENDIF
OP_ENDIF

Die Lightning Netzwerk Experten werden vielleicht das ein oder andere Konzept hier wieder erkennen, aber für diesen Beitrag würde eine ausführliche Erklärung wirklich den Rahmen sprengen... Ihr seht, ich habe nicht zu viel versprochen mit meiner Behauptung, dass mit Bitcoin Script viel möglich ist!

Hier findet ihr noch weitere nützliche Quellen solltet ihr euch intensiver mit dem Thema beschäftigen wollen:

Wie immer freue ich mich über Feedback, Kritik und Fragen im Forum!