A volte,
Bitcoin viene definito come
denaro programmabile. Grazie alla sua natura digitale, offre agli utenti un alto grado di flessibilità per quanto riguarda la configurazione di condizioni che descrivono le modalità in cui possono essere spesi dei fondi.
Quando parliamo di Bitcoin, parliamo di
wallet e
monete, ma potremmo anche pensare agli wallet come chiavi, alle monete come assegni e alla
blockchain come file e file di casseforti chiuse. Ogni cassaforte presenta una sottile fessura, di modo che chiunque può depositare assegni o guardarci dentro per vedere quanto valore contiene. Tuttavia, solo il possessore della chiave sarà in grado di accedervi.
Quando un possessore di chiave vuole dare soldi a qualcun altro, sblocca la sua cassaforte. Crea un nuovo assegno che rimanda al precedente (che viene poi distrutto) e lo chiude in una cassaforte che il destinatario può aprire. Per spenderlo, il nuovo destinatario ripete il processo.
In questo articolo, daremo un'occhiata da vicino a Script, il linguaggio di programmazione interpretato dai
nodi sul network di Bitcoin. Script è ciò che controlla il meccanismo di apertura/chiusura delle casseforti nell'esempio indicato sopra.
Continuando la nostra analogia, possiamo dire che in ogni transazione ci sono due parti – una chiave (per sbloccare la tua cassaforte) e un lucchetto. Usi la tua chiave per aprire la cassaforte che contiene l'assegno che vuoi inviare, poi aggiungi un nuovo assegno a una nuova cassaforte con un lucchetto diverso. Per spendere dalla nuova cassaforte, ti serve un'altra chiave.
Piuttosto semplice. Puoi anche aggiungere qualche variazione sui tipi di lucchetti nel sistema. Forse alcune casseforti richiedono di fornire
più chiavi, e forse per aprire altre devi dimostrare di conoscere un segreto. Ci sono un sacco di condizioni che possono essere impostate.
La nostra chiave è ciò che chiamiamo uno scriptSig. Il lucchetto è la nostra scriptPubKey. Se analizziamo queste componenti un po' più nel dettaglio, scopriremo che sono composti semplicemente da dati e blocchi di codice. Quando combinati, creano un piccolo programma.
Quando effettui una transazione, stai trasmettendo questa combinazione al network. Ogni nodo che la riceve controllerà il programma, e così facendo potrà sapere se la transazione è valida o no. In caso negativo, verrà semplicemente scartata, e non potrai spendere i fondi bloccati.
Gli assegni (monete) che possiedi sono denominati
output di transazione non spesi (UTXO). I fondi possono essere usati da chiunque sia in grado di fornire la chiave abbinata al lucchetto. Nello specifico, la chiave è lo scriptSig e il lucchetto è lo scriptPubKey.
Se le UTXO si trovano nel tuo wallet, avranno probabilmente una condizione che dichiara
solo la persona che può dimostrare la proprietà di questa chiave pubblica può sbloccare questi fondi. Per far ciò, fornisci uno scriptSig che include una
firma digitale, usando la
chiave privata associata alla chiave pubblica specificata in scriptPubKey. Tutto questo sarà più chiaro a breve.
Script è conosciuto come un linguaggio basato su stack. Questo significa solo che, quando leggiamo una serie di istruzioni, le piazziamo in una struttura simile a una colonna verticale. La lista A, B, C, per esempio, risulterebbe in uno stack con A in fondo e C in cima. Quando le istruzioni ci dicono di fare qualcosa, operiamo su uno o più elementi iniziando dalla cima dello stack.
Gli elementi A, B e C vengono aggiunti e “presi” dallo stack.
Possiamo distinguere tra i dati (cose come le
firme, le
hash e le chiavi pubbliche) e le istruzioni (o
opcode). Le istruzioni rimuovono dati e ne fanno qualcosa. Facciamo un esempio molto semplice di come potrebbe apparire uno script:
<xyz> <md5 hasher> <d16fb36f0911f878998c136191af705e> <check if equal>
In rosso, abbiamo i dati, e in blu, abbiamo gli opcode. Leggiamo da sinistra a destra, quindi mettiamo prima la stringa <xyz> sulla stack. Il prossimo è l'opcode <md5 hasher>. Anche se non esiste in Bitcoin, ma supponiamo che il suo compito sia quello di rimuovere l'elemento in cima allo stack (<xyz>) e produrne una hash usando l'algoritmo MD5. Dopodichè, l'output viene aggiunto di nuovo allo stack. L'output in questo caso è d16fb36f0911f878998c136191af705e.
Che coincidenza! Il prossimo elemento da aggiungere è <d16fb36f0911f878998c136191af705e>, quindi ora il nostro stack ha due elementi identici. Infine, <check if equal> prende due elementi dalla cima e controlla se sono uguali. In caso positivo, aggiunge <1> allo stack. In caso negativo, aggiunge <0>.
Siamo arrivati alla fine della nostra lista di istruzioni. Il nostro script avrebbe potuto fallire in due modi – se l'elemento rimanente è zero, oppure se uno degli operatori ne avesse causato il fallimento quando alcune condizioni non sono state soddisfatte. In questo esempio, non erano presenti operatori di questo tipo, e alla fine ci ritroviamo un elemento non-zero (<1>), quindi il nostro script è valido. Queste regole valgono anche per le reali transazioni Bitcoin.
Questo era solo un programma inventato. Ora andiamo a osservarne alcuni veri.
Pay-to-Pubkey (P2PK) è incredibilmente semplice. Consiste nel bloccare fondi su una particolare chiave pubblica. Se vuoi ricevere fondi in questo modo, devi fornire la tua chiave pubblica al mittente, invece dell'indirizzo Bitcoin.
La
prima transazione in assoluto tra
Satoshi Nakamoto e Hal Finney nel 2009 è stata una transazione P2PK. Questa struttura era molto diffusa nelle prime fasi di Bitcoin, ma attualmente è stata in gran parte sostituita da Pay-to-Pubkey-Hash (P2PKH).
Lo script di blocco per una transazione P2PK segue il formato di <public key> OP_CHECKSIG. Piuttosto semplice. Forse hai già indovinato che OP_CHECKSIG controlla se la firma è associata alla chiave pubblica fornita. Quindi, il nostro scriptSig sarà un semplice <signature>. Ricorda, lo scriptSig è la chiave del lucchetto.
Non potrebbe essere più semplice. Una firma viene aggiunta allo stack, seguita da una chiave pubblica. OP_CHECKSIG estrae entrambe e verifica la firma confrontandola con la chiave pubblica. Se corrispondono, aggiunge un <1> allo stack. Altrimenti, aggiunge uno <0>.
Per motivi che approfondiremo nella prossima sezione, la P2PK non è più molto utilizzata.
La Pay-to-Pubkey-Hash (P2PKH) è ora la tipologia di transazione più comune. Se non stai usando un software arcaico, è probabile che il tuo wallet utilizzi questo tipo di default.
La scriptPubKey in P2PKH è la seguente:
OP_DUP OP_HASH160 <public key hash> OP_EQUALVERIFY OP_CHECKSIG
Prima di introdurre lo scriptSig, scomponiamo cosa fanno i nuovi opcode:
OP_DUP
OP_DUP estra il primo elemento, e lo duplica . Quindi, aggiunge entrambi allo stack. In genere, lo scopo di questo passaggio è quello di creare un duplicato su cui eseguire operazioni senza modificare l'originale.
OP_HASH160
Questo opcode estrae il primo elemento e ne genera la hash due volte. Nel primo round usa l'algoritmo SHA-256. L'output SHA-256 viene poi elaborato con l'algoritmo RIPEMD-160. L'output risultante viene aggiunto di nuovo allo stack.
OP_EQUALVERIFY
OP_EQUALVERIFY combina altri due operatori – OP_EQUAL e OP_VERIFY. OP_EQUAL estrae due elementi e controlla se sono identici. In caso positivo, aggiunge un 1 allo stack. Altrimenti, aggiunge uno 0. OP_VERIFY estrae l'elemento in cima e controlla se è True (cioè, non-zero). Se non lo è, la transazione fallisce. Combinati, OP_EQUALVERIFY causa il fallimento della transazione se i due elementi in cima allo stack non corrispondono.
Questa volta, lo scriptSig appare così:
<signature> <public key>
Devi fornire una firma e la chiave pubblica corrispondente per sbloccare output P2PKH.
Puoi vedere cosa succede nella GIF qui sopra. Non è molto diverso da uno script P2PK. Stiamo solo aggiungendo un passaggio extra per controllare che la chiave pubblica corrisponda alla hash nello script.
C'è però qualcosa da sottolineare. In uno script di blocco P2PKH, la chiave pubblica non è visibile – possiamo solo vederne la hash. Se utilizziamo un
blockchain explorer e osserviamo un output P2PKH non ancora speso, non possiamo determinare la chiave pubblica. Questa viene rivelata solo quando il destinatario decide di trasferire i fondi.
Questo offre un paio di vantaggi. Il primo è che la hash della chiave pubblica è semplicemente più facile da far girare rispetto a una chiave pubblica intera. Satoshi ha introdotto questa funzione nel 2009 per questo esatto motivo. La hash della chiave pubblica è ciò che oggi conosciamo come un indirizzo Bitcoin.
Il secondo vantaggio è che le hash della chiave pubblica possono fornire un ulteriore livello di sicurezza contro il
quantum computing. Dato che la nostra chiave pubblica non è nota finché non spendiamo i fondi, è ancora più difficile per gli altri calcolare la chiave privata. Dovrebbero invertire i due round di hashing (RIPEMD-160 e SHA-256) per ottenerla.
La Pay-to-Script-Hash (P2SH) è stato uno sviluppo molto interessante per
Bitcoin. Consente al mittente di bloccare fondi alla hash di uno script – non è necessario sapere cosa fa effettivamente lo script. Prendiamo come esempio la seguente hash SHA-256:
e145fe9ed5c23aa71fdb443de00c7d9b4a69f8a27a2e4fbb1fe1d0dbfb6583f1
Non ti serve sapere l'input della hash per bloccare fondi in essa. Chi spende, però, deve fornire lo script che è stato usato per crearne la hash e deve soddisfare le condizioni dello script.
La hash riportata sopra è stata creata dal seguente script:
<multiply by 2> <4> <check if equal>
Se vuoi spendere le monete legate a questa scriptPubKey, non solo devi fornire questi comandi. Hai anche bisogno di uno scriptSig che completi lo script in modo che risulti True. In questo esempio, è un elemento che devi <multiply by 2> per dare un risultato di <4>. Ovviamente, questo significa che il nostro scriptSig è semplicemente <2>.
Nel contesto reale, la scriptPubKey per un output P2SH è:
OP_HASH160 <redeemScript hash> OP_EQUAL
Nessun nuovo operatore. Ma, abbiamo <redeemScript hash> come nuovo elemento. Come suggerisce il nome, si tratta di una hash dello script che dobbiamo fornire per riscattare i fondi (chiamato redeemScript). Lo scriptSig cambierà a seconda di ciò che trova nel redeemScript. In genere, comunque, troverai una qualche combinazione di firme e le chiavi pubbliche correlate, seguite dal redeemScript (obbligatorio):
<signature> <public key> <redeemScript>
Questa valutazione è un po' diversa dall'esecuzione a stack che abbiamo visto finora. Avviene in due parti. La prima controlla semplicemente se hai fornito la hash corretta.
Noterai che non succede nulla agli elementi che precedono il redeemScript. Non vengono usati in questa parte. Abbiamo raggiunto la conclusione di questo mini-programma, e l'elemento in cima è non-zero. Questo significa che è valido.
Non abbiamo ancora finito, però. I
nodi del network riconoscono questa struttura come P2SH, quindi possiedono gli elementi dello scriptSig in attesa in un altro stack. E' qui che verranno usate la firma e la chiave pubblica.
Finora, abbiamo trattato il redeemScript come un elemento. Adesso, invece, verrà interpretato come istruzioni, che potrebbero essere qualsiasi. Prendiamo l'esempio di uno script di blocco P2PKH, a cui dobbiamo fornire la <signature> e la <public key> che corrisponde alla <public key hash> all'interno del <redeemScript>.
Una volta che il redeemScript è stato completato, puoi vedere che abbiamo una situazione che appare esattamente come una normale transazione P2PKH. Da qui, la eseguiamo con il solito procedimento.
Abbiamo dimostrato ciò che viene definito uno script P2SH(P2PKH), ma è poco probabile trovarlo nella blockchain. Niente ti impedisce di crearne uno, ma non offre benefici aggiuntivi e finisce per occupare più spazio in un blocco (e, di conseguenza, costa di più).
Generalmente, la P2SH è utile per cose come transazioni
multisignature o
SegWit-compatible. Le transazioni multisig possono avere grandi dimensioni in quanto potrebbero richiedere diverse chiavi. Prima dell'implementazione della Pay-to-Script-Hash, un mittente avrebbe dovuto elencare tutte le possibili chiavi pubbliche nel proprio script di blocco.
Con P2SH, invece, non importa quanto sono complesse le condizioni di spesa. La hash del redeemScript ha sempre una dimensione fissa. I costi sono quindi trasferiti all'utente (o utenti) che vuole sbloccare lo script di blocco.
La compatibilità SegWit è un altro caso in cui la P2SH torna utile (entreremo nei dettagli di come la struttura della transazione si differenzia nella prossima sezione). SegWit è stato un soft fork risultato in una modifica ai formati di blocco/transazione. Essendo un aggiornamento opt-in, non tutti i software wallet riconoscono le modifiche.
Questo però non ha importanza se i client avvolgono la hash dello script SegWit in una P2SH. Come per tutte le transazioni di questo tipo, non devono sapere cosa sarà il redeemScript di sblocco.
Per comprendere il formato di transazione in SegWit, devi semplicemente sapere che non abbiamo più solo uno scriptSig e una scriptPubKey. Ora, abbiamo anche un nuovo campo chiamato witness. I dati che prima erano contenuti nello scriptSig vengono spostati nel witness, quindi lo scriptSig è vuoto.
Se hai trovato degli indirizzi che iniziano con ‘bc1’, questi vengono chiamati SegWit-native (invece di SegWit-compatible, che iniziano con un ‘3’ essendo indirizzi P2SH).
Pay-to-Witness-Pubkey-Hash (P2WPKH)
Pay-to-Witness-Pubkey-Hash (P2WPKH) è la versione SegWit di P2PKH. Il nostro witness appare in questo modo:
<signature> <public key>
Noterai che è lo stesso dello scriptSig della P2PKH. Qui, lo scriptSig è vuoto, mentre la scriptPubKey è la seguente:
<OP_0> <public key hash>
Sembra un po' strana, no? Dove sono gli opcode che ci permettono di confrontare firma, chiave pubblica e hash?
Non mostriamo operatori aggiuntivi qui, perché i nodi che ricevono la transazione sanno cosa farne in base alla lunghezza di <public key hash>. Calcolano la lunghezza e sanno di doverla eseguire nello stesso stile di una regolare transazione P2PKH.
I nodi non aggiornati non sanno come interpretare la transazione in questo modo, ma non importa. Secondo le vecchie regole, non c'è uno witness, quindi leggono uno scriptSig vuoto e alcuni dati. Dopo averli valutati, li marcano come validi – per quanto li riguarda, chiunque può spendere l'output. Per questo motivo SegWit è considerato un
soft fork retrocompatibile.
Pay-to-Witness-Script-Hash (P2WSH)
Pay-to-Witness-Script Hash (P2WSH) è il nuovo P2SH. Se sei arrivato fin qui, puoi probabilmente capire come apparirà, ma la illustreremo comunque. Il nostro witness contiene ciò che normalmente inseriremmo nello scriptSig. In una P2WSH che avvolge una transazione P2PKH, per esempio, potrebbe avere questo aspetto:
<signature 1> <public key>
Ecco la nostra scriptPubKey:
<OP_0> <script hash>
Valgono le stesse regole. I nodi SegWit leggono la lunghezza della hash dello script e determinano che si tratta di un output P2WSH, che viene valutato in modo simile alla P2SH. Nel frattempo, i vecchi nodi vedono semplicemente un output spendibile da chiunque.
In questo articolo, abbiamo imparato alcune cose sugli elementi costitutivi di Bitcoin. Ripetiamo una sintesi rapida:
Tipo di script | Descrizione |
---|
Pay-to-Pubkey (P2PK) | Blocca fondi a una determinata chiave pubblica |
Pay-to-Pubkey-Hash (P2PKH) | Blocca fondi a una determinata hash di chiave pubblica (ovvero, un indirizzo) |
Pay-to-Script-Hash (P2SH) | Blocca fondi alla hash di uno script che il destinatario può fornire |
Pay-to-Witness-Pubkey-Hash (P2WPKH) | La versione SegWit della P2PK |
Pay-to-Witness-Script-Hash (P2WSH) | La versione SegWit della P2SH |
Studiando Bitcoin più a fondo, inizi a capire le ragioni per cui ha così tanto potenziale. Le transazioni possono essere composte da molti componenti diversi. Manipolando questi elementi, gli utenti hanno a disposizione una grande flessibilità per quanto riguarda la configurazione di condizioni sulle modalità in cui i fondi possono essere spesi.