Einführung
Dies ist der dritte und letzte Beitrag unserer Serie über Verbesserungen, die wir als Teil unseres Taproot Launches auf Liquid ausgerollt haben. Unsere vorigen beiden Beiträge besprachen Taproot selbst und Partially Signed Elements Transactions. In diesem Artikel gehen wir auf neue Opcodes ein, die wir Elements Script hinzugefügt haben, wofür diese benutzt werden können, und was wir mit ihnen tun.
Dieser Satz an Opcodes bringt einige signifikante Verbesserungen an Script seit dem Start von Elements Alpha im Jahr 2015. Unsere Forschungsgruppe hat auch im Bereich Bitcoin-bezogener Kryptographie Innovationen erarbeitet und erhebliche Arbeit an MuSig, der Elements Blockchain und dem Lightning Netzwerk geleistet und an Scripting und Simplicity weitergearbeitet. Insbesondere haben wir stark zu Taproot beigetragen und einige der Änderungen an Elements Script sind daraus hervorgegangen, unter anderem:
- Die Entfernung fester numerischer Grenzen der Anzahl Opcodes und Script-Größen;
- Die Ersetzung der ECDSA-Signaturen mit Schnorr;
- Die Ersetzung von CHECKMULTISIG durch CHECKSIGADD, was gebündelte Verifikation mehrerer Signaturen erlaubt;
- Zahlreiche OP_SUCCESS Opcodes, welche die Menge an Opcodes erweitern, die wir relativ zu den NOP-Opcodes per Soft Fork in das Netzwerk einbringen konnten.
Wir werden auch über Covenants sprechen, mit denen Script die Fähigkeit zur Einschränkung von Zahlungsflüssen erhält, um “Smart Contracts” zu gestalten, die Coins oder andere Assets mit angehefteten Bedingungen versehen. Covenants wurden über die Jahre in verschiedener Form für Bitcoin vorgeschlagen, waren aber bei Elements von Anfang an dabei. Bei Tapscript haben wir Rückmeldungen der Entwickler bezüglich unserer ursprünglichen Covenant-Implementierung aufgenommen und neue Opcodes eingeführt, die Covenants ausdrucksstärker, effizienter und ergonomischer machen.
Schliesslich sprechen wir noch kurz über Elements Miniscript, unsere Erweiterung von Bitcoins Miniscript-Sprache, welche diese Opcodes zur Erstellung von Covenant-fähigen Scripts verwendet. In diesem Bereich wird gerade schnell entwickelt, und wir freuen uns über die Eröffnung neuer Anwendungen auf Liquid. In einem zukünftigen Beitrag werden wir näher auf dieses Thema eingehen.
Covenants und Assets
Covenants wurden für Bitcoin ursprünglich 2013 von Greg Maxwell auf eine nur halb ernst gemeinte Weise vorgeschlagen, wobei er Benutzer bat, sich amüsant schlechte Anwendungen für sie auszudenken. Obwohl es lustig ist, über solche Ideen zu sinnieren, haben diese die Idee von Covenants bis heute überschattet, auch wenn die Bedenken nicht sonderlich praktisch oder auch nur spezifisch für Covenants sind. Teilweise aufgrund dieser risikoaversen Denkweise sind Covenants ein heiß diskutiertes Thema im Bereich Bitcoin geblieben.
Der jüngste und populärste Vorschlag für Covenants ist OP_CTV, was eine freiwillig verringerte Funktionalität hat, um potentielle Kontroversen vermeiden.
In unseren Augen sind diese Ängste übertrieben. Sie entstammen der Idee, dass Covenants benutzt werden könnten, um existierende Bitcoins mit einem Makel zu versehen, wie etwa die Notwendigkeit einer Signatur durch eine Drittpartei, was sich durch die verfügbaren Bitcoins fortpflanzen würde und nicht mehr zu entfernen wäre. Aber ein solches Schema kann tatsächlich heute bereits umgesetzt werden, indem 2-aus-2 CHECKMULTISIG Scripts benutzt werden, deren eine Partei die Signatur verweigert, wenn die Transaktion nicht die 2-aus-2 “Covenant” Beschränkung beibehält. Niemand hat dies umgesetzt, und mit gutem Grund: solche Coins würden von den Benutzern offensichtlich nicht anstelle normaler Coins akzeptiert, und könnten gar nicht von Benutzern angenommen werden, die einer treuhänderischen oder anderweitigen rechtlichen Verpflichtung zur Verwahrung normaler Coins unterliegen. Ausserdem bräuchten die so entstehenden Coins höhere Netzwerkgebühren zu deren Ausgabe, weil sie mehr Ballast mit sich herumtragen, und sie verkomplizieren die Wallets, die sie ausgeben können.
Dasselbe mit Covenants anzugehen, wäre sogar noch teurer und erfoderte noch höhere Wallet-Komplexität, an deren Implementierung niemand ein Interesse hätte und das die Benutzer wahrscheinlich nicht annehmen würden.
Weiterer Beweis dafür, dass Covenants harmlos sind, ist deren Existenz auf dem Liquid Netzwerk seit dessen Start im Jahr 2018, und das Ausbleiben jeglicher viraler Ausbreitung von Covenants im System. (Obwohl es, wie wir noch sehen werden, technische Gründe für die geringe Akzeptanz von Covenants auf Liquid im Allgemeinen gibt.)
Covenants würden neue Fähigkeiten zu Bitcoin bringen, wie Vaults oder Geschwindigkeitsbegrenzungen, was den Benutzern mehr Flexibilität bei der Art der Verwahrung ihrer Coins gibt.
In Elements wo wir Unterstützung für herausgegebene Assets haben, ist die von Covenants angebotene Funktionalität viel grösser; wir können:
- Unbefristete Limit Orders oder andere algorithmische Trades konstruieren.
- Finanzderivate wie Optionen konstruieren.
- “Control Tokens” erzeugen, also NFTs, die Funktionalitäten in anderen Kontrakten freischalten.
In einem Multi-Asset-Szenario mit einer großen Menge an arithmetischen Opcodes wären wir in der Lage, die Menge an Features von Ethereum und anderen Wettbewerbern abzubilden, und unter Vermeidung von Turing-Vollständigkeit sollten wir strenge Analysen implementieren können. Aber wie wir sehen werden, ist die Geschichte noch komplexer.
Elements und Bitcoin Script
Elements entstand 2015 als Fork von Bitcoin mit zusätzlichen Features, und wurde im selben Jahr als Testnetzwerk namens Elements Alpha gestartet. Dieses Netzwerk ging Segwit voraus, ganz zu schweigen von Taproot; es entstand vor der Möglichkeit der Asset-Herausgabe; es hatte ein zweistufiges Pfandverfahren, das sich letztendlich als unnötig komplex und fehlerträchtig herausstellte und vor dem Ausrollen auf Liquid neu gestaltet wurde. Seine grössten Features waren Confidential Transactions, welche später erweitert wurden, um herausgegebene Assets zu unterstützen, ein aufkeimendes Verfahren zur Trennung von kryptografischen Zeugnissen (Witness), das vereinfacht und als Segwit auf Bitcoin portiert wurde, und einen Satz zusätzlicher Opcodes zur Ermöglichung von Covenants. In diesem Artikel konzentrieren wir uns auf das Letztere.
Als wir das ursprüngliche Elements und die Gruppe von Liquid Opcodes eingeführt haben, basierte Bitcoin Script auf manueller Programmierung auf niedriger Ebene und war ausserdem kompliziert zu analysieren. Unsere Erweiterung erhöhte die Ausdrucksfähigkeit der Sprache, adressierte aber nicht diese grundlegenden Begrenzungen. Benutzer konnten zwar einige fortgeschrittene Anwendungen entwickeln, wie Lending on Liquid, aber diese Entwicklung war mehr Zeugnis für entschlossene menschliche Genialität als für die Einfachheit unserer Scripting-Plattform. Andere Anwendungen wie Bitmatrix konnten erst nach den in diesem Artikel behandelten Upgrades des Scripting-Systems ausgerollt werden. Seitdem hat Blockstream Research Miniscript entwickelt, eine neue Art Bitcoin Script zu modellieren, die diese Probleme behebt.
Insbesondere ist es mit Miniscript möglich, alle Keys in einem Script automatisch aufzulisten und herauszufinden, welche Mengen zur Erstellung einer Transaktion benötigt werden; eine Obergrenze für die Größe solch einer Transaktion zu finden, bevor die Signaturen eingesammelt werden; semantische Fragen darüber zu beantworten, welche Bedingungen zur Ausgabe der Coins eingehalten werden müssen; und die genauen Codierungen für Scripte und Witnesses zu berechnen. Miniscript ergänzt Partially Signed Bitcoin Transactions, ebenfalls entwickelt von Blockstream Research’s Andrew Chow, mit Pieter Wuille als Mitarbeiter am ursprünglichen Konzept und der Entwicklung, was ein Protokoll zum Zusammenbau der für eine Transaktion nötigen Daten liefert. Dies erlaubt es Wallets, Transaktionen auch dann zu signieren, wenn diese das genaue zu erfüllende Script nicht verstehen oder ihm vorausgehen; sie müssen lediglich überprüfen, dass die Transaktion sinnvoll ist, eine ECDSA- oder Schnorr-Signatur erzeugen, und den Miniscript Werkzeugen den Rest überlassen.
Aber auch mit Miniscript in der Tasche haben wir keinen Weg zu “einfachen” Covenants auf Elements gefunden. Unsere Covenant-Konstruktionen aus der 2016er Ära waren kaum auf eine generische Weise zu verwenden, und es ist schwer zu umgehen, dass deren grosser Platzbedarf in das 201-Opcode Limit hineinläuft, das wir von Bitcoin geerbt haben. Obwohl also Bitcoin-Script durch Miniscript, PSBT und Tapscript (welches mit Miniscript im Hinterkopf entworfen wurde) leistungsfähiger wurde, konnten die Script-Erweiterungen für Elements nicht daran teilhaben.
Der Durchbruch: Neue Abstraktionen
Im Februar 2021 fanden die Blockstream Forscher Andrew Poelstra und Sanket Kanjalkar endlich einen Weg, die Covenants von Elements mit Miniscript zu verbinden. Und zwar würden wir im Grunde Covenant-fähige Scripts innerhalb “der Maschine” konstruieren, einer Script-Schablone fester Grösse, die den CHECKSIGFROMSTACK (CSFS) Opcode zum Extrahieren aller Transaktionsdaten benutzt und diese an feste Stellen auf dem Stack legt, wo der “echte Code” effizient auf sie zugreifen kann. Dies lässt uns die hohen Kosten von CSFS-artigen Covenants über viele Bedingungen der ausgebenden Transaktion hinweg amortisieren.
Tatsächlich gingen wir später von diesem Ansatz ab, aber dieser Schritt hatte uns die konzeptionelle Grenze dessen überschreiten lassen, wie man in der Praxis generische Covenants bauen kann, und uns ermöglicht zu sehen, was die wahren Beschränkungen von Elements Script waren:
- Die von Bitcoin geerbte 31-bit big-endian Arithmetik, die nicht auf einfache Weise mit den von Transaktionen verwendeten 64-bit little-endian Beträgen benutzt werden konnte
- Hinzu kam, dass es weder Multiplikations- noch Divisions-Opcodes gab; Elements hatte bitweises Verschieben und andere arithmetische Operatoren eingebracht, aber nicht die Grundlagen für z.B. Gebührenberechnungen.
- Bei Gebührenberechnungen können CSFS-artige Covenants das Transaktionsgewicht nicht feststellen und daher die Gebührenrate nicht berechnen.
- Die Unfähigkeit, mehr als 520 Datenbytes zu hashen, was aus einer Kombination von Bitcoins 520-Byte grossem Stack und seinem unflexiblen SHA256 Opcode resultiert. Zusammen mit dem Bedarf von CSFS, erhebliche Mengen an Transaktionsdaten auf einmal zu hashen, führte dies zu Begrenzungen der Gesamt-Transaktionsgrösse, was den Wallet-Entwicklern das Leben sehr schwer machte.
- Die Unfähigkeit, Scripts mit mehr als 201 Opcodes auszuführen, wovon Dutzende bereits von der CSFS Maschine benötigt wurden, und Dutzende weitere zu Konversion zwischen 64-bit und 32-bit Integerwerten.
- Die Unfähigkeit für arithmetische Berechnung auf elliptischen Kurven abgesehen von der Signaturverifikation, die zu allem möglichen gezwungen werden kann, aber nicht dazu, festzustellen, dass Taproot Outputs wohlgeformt sind. Hätten wir diese Beschränkung in Tapscript beibehalten, so hätte es die Funktionalität unserer Scripts verringert und gleichzeitig deren Komplexität erhöht.
Nachdem wir diese Probleme identifiziert hatten, waren sie leicht zu adressieren:
- Wir fügten Opcodes zur Behandlung von 64-bit Werten hinzu, einschliesslich deren Multiplikation, sowie Opcodes zu deren Konvertierung in 32-bit Werte zwecks Verwendung mit normalen Bitcoin Opcodes.
- Wir fügten Opcodes zur “direkten Introspektion” hinzu, um auf Transaktionsdaten zuzugreifen, wodurch der Bedarf für “die Maschine” entfiel und die Anzahl verwendeter Opcodes sank. Wir brachten dazu noch einen Opcode TXWEIGHT ein, um das Gewicht einer Transaktion zum Zwecke der Gebührenberechnung abzufragen.
- Wir erstellten “streaming Hash” Opcodes, um das Einleiten von Daten in die Hash Engine zu ermöglichen, ohne sie alle in ein einziges Stack Element zu packen. Das umgeht sauber das 520-byte Stack Element Limit, ohne dem Script Interpreter einen höheren Ressourcenverbrauch abzufordern.
- Wir erzeugten einen ECMULSCALARVERIFY Opcode zur Behandlung von Taproot Output, Pay-to-Contract Commitments und andere Operationen auf der elliptischen Kurve.
- Wir folgten der Führung von Bitcoin bei der Eliminierung des 201-Opcode Limits, obwohl es mit den obigen Verbesserungen auch so geklappt hätte.
Nachdem dies abgeschlossen war, hatten wir einen kleinen Satz neuer Opcodes, die sich leicht in das Bitcoin Script Modell einfügen liessen (d.h. kein zusätzlicher Diskzugriff, unbegrenzter Ressourcenverbrauch oder komplexe Kontrollflüsse wie Schleifen) und grosse Probleme lösten, was produktionsreife Covenants auf Elements erlaubte.
Hier ist eine komplette Liste der neuen Opcodes.
Nachdem diese Hürde genommen war, sind wir nun bereit, einige tiefere Probleme mit Covenants anzugehen, die nicht auf Elements beschränkt sind.
Als ein konkretes Beispiel für die neuen Dinge, die wir mit diesen Opcodes erledigen können, stell dir das Vaulting Konzept vor, bei dem wir einschränken, dass nur MAX_WITHDRAW innerhalb von 60 Blocks ausgezahlt werden können.
Um diesen Covenant zu erzeugen, müssen wir die folgenden Bedingungen berücksichtigen (beschrieben in Miniscript-Notation, die wir in einem späteren Beitrag genauer betrachten werden).
- Partial Withdrawal(partial_withdraw): Wenn mehr als MAX_WITHDRAW sats im Covenant liegen, dann sollte der Covenant mindestens total_value - MAX_WITHDRAW übrig behalten.
- "num64_gt(curr_inp_v,MAX_WITHDRAW)": Summe der Input sats grösser als MAX_SATS
- "asset_eq(curr_inp_asset,out_asset(0))": Input Assets und Output Asset 0 sind gleich
- "num64_geq(out_v(0),sub64(curr_inp_v,MAX_WITHDRAW))": Der Wert des Outputs sollte mindestens total_value - MAX_WITHDRAW betragen
- "spk_eq(curr_inp_spk,out_spk(0))": Die verbliebenen Coins werden an den Covenant geschickt
- Full Withdrawal(full_withdraw): Wenn der Covenant weniger als MAX_WITHDRAW sats hat, dann trifft die Outputs keine Beschränkung
- num64_leq(curr_inp_v,MAX_WITHDRAW)
- Key Control(keys): Warte 60 Blöcke vor erneuter Ausgabe, und verlange einen Key K vor der Ausgabe
- pk(K): Braucht Signatur von Key K
- older(60): 60 Blöcke Wartezeit vor der nächsten Auszahlung
- curr_idx_eq(0): Der derzeitige Ausgabe-Index muss 0 sein. Dies verhindert das sogenannte half-spending Problem, bei dem wir zwei Covenant UTXOs gemeinsam ausgeben und einer davon dem Covenant entfliehen kann
- Schliesslich kombinieren wir alle obigen Bedingungen: and(keys,or(full_withdraw,partial_withdraw))
Man kann dies noch um Notfall-Auszahlungs-Keys, mehrstufige Auszahlungen, Whitelisting und MultiSig Treuhänder erweitern.
Offene Probleme und zukünftige Arbeit
Indem wir Miniskript um Covenant-Unterstützung erweitert und Opcodes für deren effiziente Abarbeitung definiert haben, haben wir ein Framework erzeugt, in dem die Benutzer einfach Covenant-Skripte konstruieren können, das Verhalten dieser Skripte verstehen, und vollständige Transaktionen erzeugen, welche diese Covenant-Verträge ausführen. Aber dadurch untergraben wir die rekursive Struktur von Miniscript und zerstören damit einige Analysemöglichkeiten.
Zum Beispiel ist es möglich, zwei Miniscripte zu komponieren, in dem man einen and_b Node erzeugt, dessen beide Kinder die zwei ursprünglichen Skripte sind. (Aus Sicht von Skript hängen wir schlicht die beiden Original-Skripte aneinander und fügen einen BOOLAND Opcode hinten an.) Wenn der Benutzer sicher ist, jedes der Original-Skripte unabhängig voneinander erfüllen zu können, so kann er sicher sein, dass er auch das kombinierte Skript befriedigen kann.
Wenn er umgekehrt feststellen will, ob er ein komplexes Skript erfüllen kann, kann er rekursiv durch das Skript gehen. Immer wenn er einen and Node sieht, muss er beide Sub-Skripts erfüllen, während er bei einem or Node nur einen der beiden erfüllen muss.
Bei Covenants ist diese Form der Komposition nicht mehr gültig. Obwohl man nach wie vor zwei Skripts mittels and_b kombinieren kann und ein syntaktisch gültiges Skript erhält, kann das Resultat unerfüllbar sein, selbst wenn beide Sub-Skripte erfüllt werden können. Man stelle sich zum Beispiel ein Skript vor, das vom ersten Output einer Transaktion verlangt, Asset A zu verbrennen, mit einem Skript zu kombinieren, das vom ersten Output der Transaktion verlangt, Asset B zu verbrennen. Es sollte sofort klar sein, dass beide nicht gleichzeitig erfüllt werden können.
Da Covenants einer im Bau befindlichen Transaktion globale Bedingungen auferlegen, können verallgemeinert gesagt einzelne Skript-Fragmente nicht-lokale Effekte auf die anderen Skript-Fragmente haben. Alternativ können wir beobachten, dass wir in Bitcoin Miniscript unsere Skripts als monotone Funktionen der Ausgabebedingungen betrachten können. Ein Skript repräsentiert eine Regel zur Entscheidung, welcher Satz von Ausgabebedingungen (Signaturen, Hash Pre-Images, etc.) zur Ausgabe einer Coin gültig ist, und welche Sätze ungültig. Dass diese Regeln monoton sind, bedeutet, dass wenn ein Satz von Bedingungen gültig ist, auch jeder übergeordnete Satz gültig ist.
Bei Covenants gilt dieses Modell in Bezug auf monotone Funktionen schlicht nicht, weshalb wir das allgemeine und leistungsfähige Analysetool, dass wir in Miniscript haben, verlieren. Stattdessen müssen Covenant-fähige Elements Miniscript-Programme ad-hoc analysiert werden, wobei spezielle Fragen gestellt und beantwortet werden.
Simplicity, ein vollständiger Ersatz für Skript, an dem wir parallel arbeiten, hat ebenfalls diese Begrenzung. Um diese Art von an ad-hoc Analyse gangbar zu machen, hat der Interpreter von Simplicity eine Referenz-Implementation in dem Coq Theorem-Beweis-Assistenten; das bedeutet, dass auch wenn Menschen mit den richtigen Fragen stellen müssen, doch wenigstens die Antworten solide und maschinell überprüfbar sind. Mit Elements Miniscript sind Programme vollständig auf menschlichen Code-Review angewiesen. Wir hoffen, dass wir es so entworfen haben, dass leistungsfähige Programme trotzdem einfach und offensichtlich korrekt sind.
Für die Zukunft haben wir vor, die Funktionalität von Elements Miniskript zu erweitern, unterstützendes Tooling und Wallet Libraries zu schreiben, so dass man es zur Produktion und Interaktion mit Covenants benutzen kann, und Simplicity weiter voranzutreiben, was noch ausdrucksstärker als das neu erweiterte Elements Script sein wird.
Happy Hacking!