Introductie
Dit is het derde en laatste deel van onze serie over de verbeteringen die we op Liquid hebben geïmplementeerd als onderdeel van onze Taproot-lancering. Onze vorige twee berichten gingen over Taproot zelf en Partially Signed Elements Transactions. In dit deel beschrijven we de nieuwe opcodes die we hebben geïntroduceerd in Elements Script, waarvoor ze kunnen worden gebruikt en wat we ermee doen.
Deze set opcodes is een belangrijke reeks verbeteringen aan Script sinds de lancering van Elements Alpha in 2015. Ons onderzoeksteam heeft ook geïnnoveerd op het gebied van bitcoin-gerelateerde toegepaste cryptografie, met veel werk aan MuSig, de Elements-blockchain en het Lightning Network, en is door blijven werken aan scripting en Simplicity. We hebben met name veel bijgedragen aan Taproot voor bitcoin, en enkele van de wijzigingen in Elements Script zijn daarvan overgenomen, waaronder:
● het verwijderen van vaste numerieke limieten op het aantal opcodes en scriptgroottes;
● de vervanging van ECDSA-signatures door Schnorr;
● de vervanging van CHECKMULTISIG door CHECKSIGADD, waardoor batchverificatie van meerdere signatures mogelijk is;
● talloze OP_SUCCESS-opcodes, die de set opcodes uitbreiden die we in het netwerk kunnen softforken ten opzichte van de oude NOP-opcodes.
We gaan het ook hebben over covenants die Script de mogelijkheid geven om betalingsstromen te beperken om 'smart contracts' te construeren en voorwaarden te verbinden aan munten of andere assets. Covenants zijn in de loop der jaren in vele vormen voor bitcoin voorgesteld, maar waren vanaf het begin aanwezig in Elements. Met Tapscript hebben we feedback van ontwikkelaars ontvangen op onze oorspronkelijke implementatie van covenants en nieuwe opcodes geïntroduceerd die covenants expressiever, efficiënter en ergonomischer maken.
Ten slotte zullen we het kort hebben over Elements Miniscript; onze uitbreiding van bitcoin's Miniscript-taal die gebruikmaakt van deze opcodes om scripts met covenant-functionaliteit te produceren. Dit is in snelle ontwikkeling en we zijn verheugd om te zien dat het nieuwe applicaties op Liquid mogelijk maakt. We gaan dit onderwerp verder bespreken in een toekomstig artikel.
Covenants en assets
Covenants werden aanvankelijk voorgesteld voor bitcoin door Greg Maxwell in 2013 op een enigszins grappende manier, waarbij gebruikers werden gevraagd om slechte maar grappige toepassingen ervoor te bedenken. Hoewel deze slechte ideeën leuk zijn om over na te denken, lijken ze tot op de dag van vandaag het idee van covenants te hebben overschaduwd, ook al zijn de geassocieerde problemen niet bijzonder praktisch of zelfs specifiek voor covenants. Covenants en hun mogelijke risico's zijn een veelbesproken onderwerp in bitcoin gebleven.
Het meest recente en populaire voorstel voor covenants in bitcoin is OP_CTV, dat opzettelijk de functionaliteit heeft geminimaliseerd om mogelijke controverse te voorkomen.
Naar onze mening zijn de zorgen grotendeels overdreven. Ze komen voort uit het idee dat covenants kunnen worden gebruikt om een "smet" op bestaande bitcoins te implementeren, zoals het vereisen van een signature van een derde partij voor hun gebruik die zich door de hele muntvoorraad zou kunnen verspreiden en onmogelijk te verwijderen zou zijn. Maar een dergelijke implementatie kan vandaag de dag ook al worden geïmplementeerd door gebruik te maken van 2-of-2-CHECKMULTISIG-scripts waarin een partij weigert transacties te ondertekenen die de 2-of-2-"covenant"-beperking niet behouden. Niemand heeft dit gedaan, en terecht: deze munten zouden niet worden geaccepteerd door gebruikers in plaats van gewone munten en zouden niet kunnen worden geaccepteerd door gebruikers met een fiduciaire of andere wettelijke verplichting om gewone munten in bewaring te nemen. Bovendien zouden de resulterende munten hogere netwerkkosten vereisen vanwege hun extra gewicht, en de complexiteit van wallets die ze zouden kunnen uitgeven zou toenemen.
Hetzelfde proberen met covenants zou nog duurder zijn en een nog grotere wallet-complexiteit vereisen, wat niemand zou willen implementeren en gebruikers zouden weigeren.
Verder bewijs dat covenants niet schadelijk zijn, is dat ze al sinds de lancering in 2018 op het Liquid-netwerk bestaan en dat er zich geen virale covenants door het systeem hebben verspreid. (Hoewel, zoals we zullen zien, er technische redenen zijn voor een lage acceptatie van covenants voor Liquid in het algemeen.)
Covenants zouden nieuwe mogelijkheden voor bitcoin introduceren, zoals vaults (kluizen) of uitgavelimieten, waardoor gebruikers meer flexibiliteit krijgen in de manier waarop ze hun munten bewaren.
In Elements, waar we asset-uitgave ondersteunen, is de functionaliteit die wordt geboden door covenants veel groter. We kunnen:
● limit orders met een open einde of andere algoritmische transacties maken;
● financiële derivaten zoals opties maken;
● "control tokens" maken, ofwel NFT's die functionaliteit in andere contracten mogelijk maken.
In een multi-assetscenario met een rijke set rekenkundige opcodes, zijn we in staat om de functieset van Ethereum en concurrenten te evenaren, en door Turingvolledigheid te vermijden, wordt het mogelijk om sterke vormen van analyse te kunnen implementeren. Maar zoals we zullen zien, is het verhaal complexer dan dit.
Elements en bitcoin-script
Elements werd in 2015 geschreven als een fork van bitcoin met extra functies en werd dat jaar gelanceerd als een testnetwerk genaamd Elements Alpha. Dit netwerk was ouder dan Segwit, en dus ook Taproot (het dateert van vóór Issued Assets) en had een tweetraps peg-schema dat uiteindelijk onnodig complex en vatbaar voor bugs bleek te zijn en is opnieuw ontworpen voordat het op Liquid in gebruik werd genomen. De belangrijkste functies waren Confidential Transactions, een latere uitbreiding met Issued Assets, een systeem voor het scheiden van zogeheten "witness data" dat werd vereenvoudigd en overgezet naar bitcoin als Segwit, en een set extra opcodes om covenants mogelijk te maken. Dit laatste is waar we ons op zullen concentreren.
Toen we de originele Elements- en Liquid-opcodes introduceerden, vertrouwde bitcoin-script op handmatige, low-level programmering en was het ook moeilijk formeel te analyseren. Onze extensies verhoogden de expressiviteit van de taal, maar gingen niet in op deze onderliggende beperkingen. Gebruikers konden enkele geavanceerde toepassingen ontwikkelen, zoals Lending on Liquid, maar deze ontwikkeling was meer een bewijs van menselijk vernuft en vastberadenheid dan het gemak van programmeren op ons scriptplatform. Andere toepassingen, zoals Bitmatrix, konden alleen worden ingezet na de upgrades naar het scriptsysteem dat in dit artikel wordt beschreven. Sindsdien heeft Blockstream Research, onafhankelijk van Elements, Miniscript ontwikkeld, een nieuwe manier om bitcoin-script te modelleren die deze analyseproblemen kan oplossen.
Met Miniscript is het mogelijk om automatisch alle sleutels in een script te inventariseren en te bepalen welke sets nodig zijn om een transactie tot stand te brengen, een bovengrens te vinden voor de omvang van een dergelijke transactie voordat signatures worden verzameld, semantische vragen te beantwoorden over aan welke voorwaarden moet worden voldaan om munten uit te geven, en de exacte coderingen te berekenen voor scripts en witnesses. Miniscript is een aanvulling op Partially Signed Bitcoin Transactions, wat ook is ontwikkeld door Andrew Chow van Blockstream Research. Pieter Wuille was betrokken bij het oorspronkelijke concept en de ontwikkeling. Het is een protocol voor het verzamelen van de gegevens die nodig zijn om een transactie te produceren. Hiermee kunnen wallets transacties signen, zelfs als ze het exacte script niet begrijpen (of ouder zijn dan het script zelf); ze hoeven alleen maar te controleren of de transactie logisch is, een ECDSA- of Schnorr-signature te produceren, en de Miniscript-tooling doet de rest.
Maar zelfs met onze Miniscript-ervaring vonden we geen eenvoudig pad naar "gemakkelijke covenants" op Elements. Onze covenant-constructies uit het 2016-tijdperk waren moeilijk op een generieke manier te gebruiken, en hun grote omvang maakt het in de praktijk moeilijk te vermijden dat ze worden beperkt door de 201-opcode-limiet die we van bitcoin hebben geërfd. Dus zelfs toen bitcoin-script krachtiger werd door Miniscript, PSBT en Tapscript (ontworpen met Miniscript in gedachten), konden de extensies van Elements voor Script niet meegaan.
Doorbraak: nieuwe abstracties
In februari 2021 hebben Blockstream-onderzoekers Andrew Poelstra en Sanket Kanjalkar eindelijk een manier gevonden om de covenants van Elements te combineren met Miniscript. We creëren scripts met covenant-functionaliteit in "de machine", een script template met een vaste grootte die de CHECKSIGFROMSTACK (CSFS)-opcode gebruikt om alle transactiegegevens te extraheren en op de stack te plaatsen op vaste locaties waar de "echte code'' er efficiënt toegang toe krijgen. Dit stelt ons in staat de hoge kosten van covenants in CSFS-stijl te reduceren voor veel voorwaarden van de uitgavestransactie.
We zijn later afgestapt van deze initiële benadering, maar de stap bracht ons voorbij de conceptbeperking van hoe praktisch generieke covenants kunnen worden opgesteld, en laten zien wat de echte beperkingen van Elements Script waren:
● 31-bits big-endian rekensommen van bitcoin die niet gemakkelijk kunnen worden gebruikt met de 64-bits little-endian-bedragen die door transacties worden gebruikt.
● Er waren bovendien geen vermenigvuldiging- of deling-opcodes; Elements had bitshifts en andere rekenkundige bewerkingen, maar niet de basis die nodig was om bijvoorbeeld transactiekosten (fees) te berekenen.
● Voor kostenberekeningen hebben covenants in CSFS-stijl geen toegang tot het transactiegewicht en kunnen daarom de kosten niet berekenen.
● Het onvermogen om meer dan 520 bytes aan gegevens te hashen wordt veroorzaakt door een combinatie van de limiet van de 520-byte stackelementgrootte van bitcoin en de inflexibele SHA256-opcode. In combinatie met de behoefte van CSFS om grote hoeveelheden transactiegegevens tegelijk te hashen, creëerde dit limieten voor de totale transactiegroottes, waardoor het leven van wallet-auteurs veel moeilijker werd.
● Het was onmogelijk om scripts uit te voeren met meer dan 201 opcodes, met tientallen vereist door de CSFS-machine en tientallen meer die nodig zijn om 64-bits getallen om te zetten in 32-bits en terug.
● Het was onmogelijk om met elliptische krommen te rekenen, behalve voor signature-validatie, wat kan worden gebruikt om van alles te doen, maar niet om te verifiëren dat Taproot outputs correct zijn gevormd. Als we deze beperking in Tapscript hadden behouden, zou het de functionaliteit hebben verminderd en de complexiteit van onze scripts hebben vergroot.
Toen we deze problemen eenmaal zagen, waren ze eenvoudig aan te pakken:
● We hebben nieuwe opcodes toegevoegd voor het manipuleren van 64-bits waarden, inclusief vermenigvuldiging, evenals opcodes om ze om te zetten in de oude 32-bits waarden voor gebruik met de gewone bitcoin-opcodes.
● We hebben opcodes voor "directe introspectie" toegevoegd om toegang te krijgen tot transactiegegevens, waardoor "de machine" niet meer nodig is en er minder opcodes nodig zijn. We hebben ook een TXWEIGHT-opcode toegevoegd om toegang te krijgen tot het gewicht van de transactie voor het berekenen van transactiekosten.
● We hebben "streaming hash"-opcodes toegevoegd om het invoeren van gegevens in een hash-engine mogelijk te maken zonder dat alles in een enkel stackelement hoeft te worden geplaatst. Dit vermijdt netjes de limiet van 520-byte-stackelementen zonder dat er meer wordt verwacht van de scriptinterpreter.
● We hebben een ECMULSCALARVERIFY-opcode toegevoegd voor het afhandelen van Taproot-outputs, pay-to-contract-commitments en andere cryptografische operaties met elliptische krommen.
● We volgden het voorbeeld van bitcoin en hebben de limiet van 201 opcodes weggehaald, hoewel het met de bovenstaande verbeteringen mogelijk wel te doen was.
Eenmaal voltooid, hadden we een kleine set nieuwe opcodes die gemakkelijk in het bitcoin-script-model passen (d.w.z. geen introductie van schijftoegang, onbeperkt gebruik van bronnen of complexe controlestromen zoals looping) die grote problemen oplosten waardoor covenants van productiekwaliteit op Elements nu mogelijk zijn.
Hier is een volledige lijst met nieuwe opcodes.
Nu we deze barrière hebben overwonnen, zijn we klaar om een aantal diepere problemen aan te pakken met covenants die niet uniek zijn voor Elements.
Hier volgt een concreet voorbeeld van nieuwe dingen die we met deze opcodes kunnen doen: een vault-schema waarin we beperken dat MAX_WITHDRAW alleen kan worden ingetrokken binnen 60 blocks.
Om dit covenant te creëren, moeten we de volgende voorwaarden respecteren (beschreven in Miniscript-notatie, die we in een toekomstig artikel in detail zullen behandelen).
● Gedeeltelijke opname (partial_withdraw): Als er meer dan MAX_WITHDRAW sats in het covenant zitten, dan moet het covenant ten minste total_value - MAX_WITHDRAW over hebben.
● "num64_gt(curr_inp_v,MAX_WITHDRAW)": Totale sats input meer dan MAX_SATS
● "asset_eq(curr_inp_asset,out_asset(0))": Input assets en output asset 0 zijn hetzelfde
● "num64_geq(out_v(0),sub64(curr_inp_v,MAX_WITHDRAW))": De waarde van de output moet ten minste total_value - MAX_WITHDRAW zijn
● "spk_eq(curr_inp_spk,out_spk(0))": De overige munten worden naar het covenant gestuurd
● Volledige opname (full_withdraw): Als het covenant minder dan MAX_WITHDRAW sats heeft, hebben de outputs geen beperking
● num64_leq(curr_inp_v,MAX_WITHDRAW)
● Sleutelbeheer (keys): Wacht 60 blocks voordat je het opnieuw uitgeeft en vereis sleutel K om het uit te geven.
● pk(K): Vereist signature van sleutel K
● older(60): 60 blocks wachttijd voor de volgende opname
● curr_idx_eq(0): De huidige uitgave-index moet 0 zijn. Dit voorkomt het zogenaamde "half spend"-probleem waarbij we twee covenant-outputs samen kunnen uitgeven en een van hen aan het covenant ontsnapt
● Ten slotte combineren we alle bovenstaande voorwaarden: and(keys,or(full_withdraw,partial_withdraw))
Het is mogelijk om deze uit te breiden met sleutels voor nooduitgave, uitgave in meerdere stappen, whitelisting en multi-sig custodies.
Openstaande problemen en toekomstig werk
Door Miniscript uit te breiden met covenantondersteuning en opcodes te leveren om dit efficiënt te doen, hebben we een framework gecreëerd waarin gebruikers eenvoudig covenantscripts kunnen construeren, het gedrag van deze scripts kunnen begrijpen en volledige transacties kunnen creëren die deze covenantcontracten uitvoeren. Maar daarmee ondermijnen we de recursieve structuur van Miniscript en breken we sommige vormen van analyse.
Het is bijvoorbeeld mogelijk om twee miniscripts te "componeren" door een and_b node te maken waarvan de twee afstammelingen de twee originele scripts zijn. (In het script voegen we gewoon de originele scripts samen en voegen aan het einde een BOOLAND-opcode toe.) Als een gebruiker ervan overtuigd is dat elk van de originele scripts afzonderlijk kan worden gebruikt, dan kan ook het gecombineerde script worden gebruikt.
Omgekeerd, als de vervulbaarheid van een complex script moet worden bepaald, kan men recursief door het script stappen: wanneer men een 'and'-node ziet, moet men aan beide subscripts voldoen, terwijl er bij een 'or'-node aan een van de condities moet worden voldaan.
Bij covenants is deze samenstelling niet meer geldig. Hoewel je nog steeds twee scripts kunt combineren met and_b om een syntactisch geldig script te krijgen, kan het resultaat onbevredigend zijn, zelfs als aan beide subscripts kan worden voldaan. Bijvoorbeeld: een script dat zowel vereist dat de eerste output van de transactie asset A vernietigt als een script dat vereist dat de eerste output van de transactie asset B vernietigt. Het is duidelijk dat niet aan beide tegelijk kan worden voldaan.
In het algemeen kunnen individuele scriptfragmenten niet-lokale effecten hebben op andere scriptfragmenten, omdat covenants globale voorwaarden introduceren voor de transactie in aanbouw. In bitcoin-miniscript daarentegen kunnen we onze scripts zien als monotone functies van uitgavevoorwaarden. Een script vertegenwoordigt een regel om te beslissen welke sets uitgavevoorwaarden (signatures, hash-preimages, enz.) geldig zijn om een munt uit te geven en welke sets ongeldig zijn. Deze regels zijn monotoon, wat betekent dat als een reeks voorwaarden geldig is, elke superset dat ook is.
Met covenants in beeld is dit model qua monotone functies simpelweg niet van toepassing, en dus verliezen we de algemene en krachtige analysetools die Miniscript biedt. In plaats daarvan moeten Elements Miniscript-programma's met covenants op een ad-hoc manier worden geanalyseerd, waarbij specifieke vragen worden gesteld en beantwoord.
Simplicity, een vervanger voor script waar we parallel aan werken, heeft ook deze beperking. Om dit soort ad-hocanalyse praktisch te maken, heeft Simplicity's interpreter een referentie-implementatie in de Coq theorem proving assistant, wat betekent dat hoewel mensen de juiste vragen moeten bedenken om te stellen, de antwoorden in elk geval solide en machinaal-controleerbaar zullen zijn. Met Elements Miniscript zijn programma's volledig afhankelijk van menselijke "code review". We hopen dat we het zo hebben ontworpen dat krachtige programma's toch eenvoudig en duidelijk correct kunnen zijn.
In de toekomst zijn we van plan om de functionaliteit van Elements Miniscript uit te breiden, ondersteunende tooling en wallet-bibliotheken te schrijven die ermee covenants kunnen produceren en gebruiken, en verder te gaan met Simplicity, dat nog expressiever zal zijn dan de nieuw uitgebreide Elements Script.
Veel plezier met hacken!