Introduzione
Con questo terzo e ultimo post chiudiamo la serie di aggiornamenti sui miglioramenti apportati a Liquid come parte del lancio del nostro Taproot. Nei due post precedenti vi avevamo parlato sia di Taproot sia delle Partially Signed Elements Transactions. In questo post vi descriveremo i nuovi opcode che abbiamo introdotto all’interno di Elements Script e del loro utilizzo presente e futuro.
Questo set di opcode fa parte di una serie di importanti miglioramenti apportati a Script dal lancio di Elements Alpha nel 2015. Il nostro team di ricerca ha continuato a lavorare per introdurre nuovi elementi innovativi alla criptografia applicata relativa a Bitcoin, con un focus particolare su MuSig, la blockchain di Elements, e Lightning Network. Il team sta inoltre proseguendo il suo lavoro su scripting e Simplicity. In particolare, abbiamo dato un grande contributo a Taproot su Bitcoin, da cui hanno preso forma alcune delle modifiche apportate a Elements Script, tra cui:
- Rimozione di limiti numerici predefiniti sui conti opcode e dimensioni di script;
- Sostituzione delle firme ECDSA con Schnorr;
- Sostituzione di CHECKMULTISIG con CHECKSIGADD, consentendo la verifica dei batch di firme multiple;
- Diversi opcode OP_SUCCESS, i quali ampliano il set di opcode con cui effettuare soft-fork nella rete relativamente ai vecchi opcode NOP.
Parleremo inoltre dei covenant, che consentono a Script di costringere i flussi di pagamento a creare “smart contract” che stabiliscano condizioni per coin o altri asset. Nel corso degli anni, sono state molte e diverse le proposte di covenant avanzate per Bitcoin, mentre in Elements sono presenti sin dagli inizi. Con Tapscript, siamo partiti dal developer feedback della nostra implementazione di covenant iniziale e introdotto nuovi opcode che renderanno i covenant più espressivi, efficienti ed ergonomici.
Infine, vi parleremo brevemente anche di Elements Miniscript, la nostra estensione del linguaggio Miniscript di Bitcoin, la quale utilizza questi opcode per produrre script covenant enabled. L’estensione è attualmente in fase di forte sviluppo. Non vediamo l’ora di vedere come Elements Miniscript aprirà la strada a nuove applicazioni su Liquid. Approfondiremo l’argomento in un post dedicato.
Covenant e Asset
I covenant erano stati inizialmente proposti per Bitcoin da Greg Maxwell nel 2013 quasi per gioco, scherzando con gli utenti, chiedendogli di pensare a delle potenziali applicazioni sbagliate per questi strumenti. Sebbene queste cattive idee siano state un’iniziativa spiritosa, sfortunatamente sembrano aver eclissato l’idea dei covenant fino a oggi, sebbene a preoccupare non sia qualcosa di particolarmente pratico o specifico relativo ai covenant. In parte a causa di questa linea di pensiero, i covenant sono rimasti un argomento fortemente dibattuto nel mondo Bitcoin.
La proposta più recente e popolare per i covenant nello spazio Bitcoin è OP_CTV, che ha una funzionalità volutamente ridotta per evitare potenziali controversie.
Crediamo che queste paure siano decisamente esagerate. L’idea di base è che i covenant potrebbero essere utilizzati per “contaminare” i Bitcoin esistenti, come ad esempio il bisogno di una firma di terza parte per poterli utilizzare, la quale andrebbe poi a diffondersi lungo la fornitura di coin diventando impossibile da rimuovere. Tuttavia, tale schema potrebbe essere implementato già a partire da oggi utilizzando gli script CHECKMULTISIG 2 di 2, per i quali una parte rifiuta di apporre la propria firma su transazioni che non preservano la restrizione “covenant” 2 di 2. Tutto ciò, però, non è ancora stato fatto, e per una buona ragione: queste coin, ovviamente, non verrebbero accettate dagli utenti al posto di normali coin e non potrebbero essere accettate dagli utenti aventi un obbligo fiduciario o legale di custodia di tali normali coin. Inoltre, le coin risultanti richiederebbero fee di rete più elevate da spendere a causa del loro peso aggiuntivo, nonché aumenterebbero la complessità dei wallet che potrebbero utilizzarle.
Tentare di fare la stessa cosa con i covenant diventerebbe ancora più costoso e richiederebbe wallet persino più complessi che nessuno sarebbe interessato a utilizzare e che gli utenti rifiuterebbero.
A ulteriore sostegno dell’innocuità dei covenant vi è il fatto che siano presenti su Liquid Network sin dal loro lancio nel 2018 e che all’interno del sistema non si siano diffusi covenant virali (anche se, come vedremo, esistono delle motivazioni tecniche per lo scarso utilizzo dei covenant su Liquid in generale).
I covenant introdurrebbero nuove capacità all’interno di Bitcoin, come vault o limiti di velocità, garantendo agli utenti maggiore flessibilità nella custodia delle loro coin.
In Elements, dove abbiamo introdotto un supporto asset, le funzionalità garantita dai covenant è di gran lunga maggiore. Siamo infatti in grado di:
- Ideare ordini di limiti open ended o altre trade algoritmiche;
- Ideare strumenti/derivati finanziari come ad esempio opzioni;
- Creare “control token”, ossia NFT che consentono di avere tale funzionalità in altri contratti.
In uno scenario multi asset con una vasta opzione di opcode aritmetici saremmo in grado di allineare le funzionalità di Ethereum e i competitor; mentre evitando la Turing equivalenza dovremmo essere in grado di implementare un’analisi approfondita/avanzata. Ma come vedremo, la questione è molto più complicata di quanto sembra.
Elements e Script Bitcoin
Elements venne scritto nel 2015 come fork di Bitcoin con funzionalità extra e lanciato come test network lo stesso anno con il nome di Elements Alpha. La rete ha aperto la strada a Segwit e Taproot, così come agli issued asset. Il network aveva un peg scheme a due fasi che si rivelò inutilmente complesso nonché soggetto a bug e venne pertanto rivisto prima dell’utilizzo su Liquid. Le sue funzionalità principali erano Confidential Transactions, che successivamente vennero estese per supportare issued asset, uno schema nascente per segregating witness che venne semplificato e trasferito su Bitcoin come Segwit, e opcode extra per l’abilitazione dei covenant. In questo post, ci concentreremo su quest’ultima funzionalità.
Quando introducemmo il set originale di opcode Elements e Liquid, Bitcoin Script si appoggiava a una programmazione manuale e di scarso livello ed era inoltre difficile da analizzare. Le nostre estensioni hanno reso il linguaggio più espressivo ma non hanno affrontato questi limiti sottostanti. Gli utenti sono riusciti a sviluppare delle applicazioni avanzate, come Lending su Liquid, ma questo sviluppo si è rivelato più una conferma dell’ingenuità umana che un modo per semplificare la programmazione sulla nostra scripting platform. Altre applicazioni, come ad esempio Bitmatrix, potevano essere utilizzate solamente dopo gli upgrade al sistema di script descritto nel post. Da allora, indipendentemente da Elements, Blockstream Research ha sviluppato Miniscript, un nuovo modo per modellare Bitcoin Script, che avrebbe risolto questi problemi di analisi.
Nello specifico, con Miniscript è possibile elencare automaticamente tutte le chiavi in uno script e determinare quali set siano necessari per produrre una transazione, trovare un upper bound sulla dimensione di tale transazione prima di raccogliere le firme, rispondere a domande semantiche su quali condizioni debbano essere soddisfatte per poter spendere le coin e calcolare le codifiche esatte per script e witness. Miniscript va a completare le Partially Signed Bitcoin Transactions, anche sviluppate da Andrew Chow del team di ricerca di Blockstream, insieme a Pieter Wuille, essendo parte dell’idea e dello sviluppo originale. Miniscript fornisce un protocollo finalizzato a mettere insieme tutti i dati necessari per produrre una transazione. Questo consente ai wallet di firmare transazioni anche se questi non capiscono (o precedono) il determinato script soddisfatto. I wallet hanno solo bisogno di controllare che la transazione abbia senso, produrre un ECDSA o firma di Schnorr e lasciar fare il resto a Miniscript.
Tuttavia, anche con Miniscript in tasca, non abbiamo trovato un modo diretto per garantire “covenant facili” su Elements. Per come erano fatti nel 2016, i nostri covenant erano difficili da utilizzare in modo generico e le loro grandi dimensioni sono praticamente difficili da evitare, avendo un limite di 201 opcode che abbiamo ereditato da Bitcoin. Pertanto, anche se Bitcoin Script è diventato più potente attraverso Miniscript, PSBT e Tapscript (plasmato sulla base di Miniscript), le estensioni di Element per/verso Script non sono riuscite a entrare in gioco.
La svolta: nuove idee
A febbraio 2021, i ricercatori di Blockstream Andrew Poelstra e Sanket Kanjalkar sono finalmente riusciti a trovare un modo per mettere insieme i covenant di Element e Miniscript. Essenzialmente, avremmo dovuto ideare script covenant enabled all’interno della “macchina”, un template di Script a dimensione fissa/prestabilita che avrebbe utilizzato l’opcode CHECKSIGFROMSTACK (CSFS) per estrarre tutti i dati delle transazioni e lasciarli su stack in posti fissi/prestabiliti dove il “codice reale” avrebbe potuto accedervi in modo funzionale. Questo ci consente di ammortizzare l’elevato costo dei covenant in stile CSFS in diverse condizioni sulle transazioni di spesa.
Infatti, successivamente ci siamo discostati da questo approccio iniziale, ma il passo ci ha spinto oltre il limite del concetto di come creare concretamente dei covenant generici e ci ha permesso di capire che i reali limiti di Elements Script erano:
- Aritmetica big endian a 31 bit ereditata da Bitcoin, che non poteva essere utilizzata facilmente con le cifre little endian a 64 bit utilizzate dalle transazioni.
- A complicare la situazione, l’assenza di opcode di multiplicazione o divisione. Elements aveva aggiunto bitshifts e altre operazioni aritmetiche ma non le basi per calcolare le fee, ad esempio.
- Per il calcolo delle fee, i covenant in stile CSFS non possono accedere al peso della transazione e di conseguenza non possono calcolare la sua fee rate.
- L’impossibilità di eseguire un hash per più di 520 byte di dati, dovuta alla combinazione tra limite di dimensioni di 520 byte dello stack element di Bitcoin e il suo inflessibile opcode SHA256. Insieme alla necessità di CSFS si eseguire l’hash di un significativo numero di dati di transazioni nello stesso momento, ciò ha portato a dei limiti sulle dimensioni delle transazioni totali, notevolmente complicando la vita ai creatori di wallet.
- L’impossibilità di eseguire script con più di 201 opcode, con decine di questi richiesti dalla macchina CSFS e altre decine necessarie per convertire 64 bit di numeri primi in 32 bit di numeri primi e viceversa.
- L’impossibilità di funzioni aritmetiche ellittiche, fatta eccezione per la validazione delle firme, che può obbligare a fare molte cose ma non verificare che gli output di Taproot vengano formati correttamente. Se avessimo mantenuto questo limite in Tapscript, questo avrebbe ridotto la funzionalità, allo stesso tempo aumentando la complessità nei nostri script.
Una volta riconosciuti questi problemi, è diventato chiaro come affrontarli:
- Abbiamo aggiunto nuovi opcode per manipolare i valori a 64 bit, inclusa la moltiplicazione, così come opcode per convertirli nei valori a 32 bit di vecchio stampo per l’utilizzo con i comuni opcode di Bitcoin.
- Abbiamo aggiunto opcode di “introspezione diretta” per accedere ai dati delle transazioni, rendendo “la macchina” non più necessaria e riducendo il numero dei nostri opcode. Abbiamo inoltre aggiunto un opcode TXWEIGHT per accedere al peso della transazione per il calcolo della fee rate.
- Abbiamo aggiunto opcode di “streaming hash” per consentire di portare dati a un motore hash senza il bisogno di riunirli tutti in un singolo stack element. Ciò consente di evitare il limite di uno stack element a 520 byte senza il bisogno di utilizzare altre risorse da parte dello script interpreter.
- Abbiamo aggiunto un opcode ECMULSCALARVERIFY per gestire gli output di Taproot, impegni al pay to contract e altre operazioni di crittografia ellittica.
- Abbiamo seguito l’esempio di Bitcoin eliminando il limite opcode 201, sebbene, grazie ai miglioramenti descritti, saremmo riusciti a gestirlo.
Una volta completati, avevamo un piccolo set di nuovi opcode che si integravano facilmente nel modello di Bitcoin Script (nessun accesso disco introdotto, utilizzo illimitato delle risorse o flow di controllo complesso come looping) che hanno potuto risolvere i problemi principali, consentendo di introdurre covenant production grade in Elements.
Ecco una lista completa dei nuovi opcode.
Avendo superato questo ostacolo, siamo pronti per affrontare alcune questioni più profonde relative ai covenant, che non valgono solo per Elements.
Come esempio concreto di quanto di nuovo possiamo fare con questi opcode, basta considerare il vaulting scheme in cui abbiamo inserito una restrizione per cui solo una MAX_WITHDRAW possa essere prelevata entro 60 blocks time.
Per creare questo covenant, abbiamo bisogno di rispettare le seguenti condizioni (descritte nella notazione di Miniscript, che approfondiremo prossimamente in un nuovo post).
- Partial Withdrawal(partial_withdraw): se sono presenti più di MAX_WITHDRAW sats nel covenant, allora il covenant dovrebbe avere almeno total_value - MAX_WITHDRAW rimanente.
- "num64_gt(curr_inp_v,MAX_WITHDRAW)": input sats totali superiori a MAX_SATS
- "asset_eq(curr_inp_asset,out_asset(0))": uguali/stessi input asset e output asset 0
- "num64_geq(out_v(0),sub64(curr_inp_v,MAX_WITHDRAW))": il valore di output dovrebbe corrispondere ad almeno total_value - MAX_WITHDRAW
- "spk_eq(curr_inp_spk,out_spk(0))": le monete rimanenti vengono inviate al covenant
- Full Withdrawal(full_withdraw): se il covenant ha meno di MAX_WITHDRAW sats, gli output non hanno vincoli/limiti
- num64_leq(curr_inp_v,MAX_WITHDRAW)
- Key Control(keys): attende 60 block prima di utilizzare ancora coin e richiede una key K per poterle spendere
- pk(K): richiede una firma da key K
- older(60): tempo di attesa di 60 block prima del prelievo successivo
- curr_idx_eq(0): lo spending index attuale deve essere pari a 0. Questo previene il cosiddetto problema di “mezza spesa”, in cui è possibile spendere due utxos covenant insieme e uno di questi più sfuggire al covenant.
- Infine, mettiamo insieme tutte queste condizioni: e(keys,o(full_withdraw,partial_withdraw))
È possibile estendere queste opzioni con chiavi di prelievo di emergenza, prelievi multi step, whitelisting e custodia multisig.
Problemi in sospeso e progetti futuri
Estendendo le funzionalità di Miniscript con il supporto covenant e fornendo opcode per farlo in modo funzionale, abbiamo creato una cornice in cui gli utenti possono facilmente creare script covenant, capire come questi si comportano e creare transazioni complete che possano utilizzare/applicare questi contratti covenant. Nel farlo però, andiamo a minare la struttura ripetuta/ricorrente di Miniscript e di conseguenza interrompiamo alcune forme di analisi.
Per esempio, è possibile “comporre” due Miniscript creando un and_b node i cui due figli sono i due script originali. (In termini di Scripts, stiamo solo concatenando gli script originali e aggiungendo un opcode BOOLAND alla fine). Se un utente è convinto che possa soddisfare tutti gli script originali in modo indipendente, può essere sicuro di poter soddisfare lo script combinato.
Al contrario, se l’utente vuole stabilire quanto uno script complesso possa essere soddisfacente, può passare attraverso lo script regolarmente: ogni volta che si imbatte in un and node deve soddisfare entrambi i sub script, mentre ogni volta che si imbatte in un an or node deve soddisfare uno di questi.
Con i covenant, questo tipo di composizione non è più valido. Se da un lato si può ancora combinare due script utilizzando and_b per ottenere uno script valido a livello sintattico, il risultato potrebbe essere impossibile da soddisfare anche se entrambi i sub script possono essere soddisfatti. Per esempio, proviamo a considerare di combinare uno script che richiede che il primo output della transazione bruci l’asset A con uno script che richiede che il primo output della transazione bruci l’asset B. Chiaramente non possono essere soddisfatti entrambi allo stesso tempo.
In generale, dal momento che i covenant introducono condizioni a livello mondale per le transazioni in corso di creazione, i singoli frammenti di script/i frammenti del singolo script possono avere effetti a livello non locale su altri frammenti di script. Oppure, vediamo come in Bitcon Miniscript possiamo pensare ai nostri script come funzioni uniformi di condizioni di spesa. Uno script rappresenta una regola per decidere quali set di condizioni di spesa (firme, controimmagini hash, ecc.) siano valide per spendere una moneta, e quali set invece non siano validi. Essendo queste regole uniformi, viene stabilito che se qualche set di condizioni è valido, allora lo è anche qualsiasi superset.
Con i covenant, in termini di funzioni uniformi, questo modello non è applicabile e pertanto andiamo a perdere i potenti strumenti generali di analisi che garantisce Miniscript. I programmi Elements Miniscript covenant enabled, invece, necessitano di essere analizzati ad hoc con precise domande e risposte.
Anche Simplicity, un sostituto su larga scala/completo per Script su cui stiamo lavorando in parallelo, ha questo limite. Per rendere questa sorta di analisi ad hoc sostenibile, l’interprete di Simplicity ha un’implementazione di riferimento nell’assistente di prova del teorema Coq. Ciò significa che se da una parte gli umani devono riuscire a trovare le domande giuste da fare, le risposte saranno almeno solide e a prova di macchina. Con Elements Miniscript, i programmi sono completamente dipendenti dalla code review umana. Ci auguriamo di aver ideato Simplicity in modo tale che i programmi più potenti possano essere non solo semplici ma anche corretti, ovviamente.
Per il futuro abbiamo intenzione di continuare ad ampliare le funzionalità di Elements Miniscript, ideare dei tool di supporto e wallet library che possano utilizzarli per produrre e interagire con i covenant e infine continuare a lavorare su Simplicity, che diventerà ancora più espressivo di quanto non sia ora Elements Script con le sue nuove estensioni.
Happy hacking!