Dentro Bitcoin: transazioni e script. Parte 8
Bitcoin

Dentro Bitcoin: transazioni e script. Parte 8

By Alessio Salvetti - 30 Nov 2019

Chevron down

Questo articolo è l’ottavo – stavolta dedicato a transazioni e script – di una serie di approfondimenti sulla parte più tecnica di Bitcoin, accessibile anche a coloro a digiuno di codice. 

Il testo continua una sorta di guida pensata per entrare gradualmente in quella che molti definiscono “la tana del bianconiglio”.

A livello bibliografico è d’obbligo citare il testo “Mastering Bitcoin” di Andreas Antonopoulos, riferimento costante, dal quale sono state tratte le immagini. Chi fosse interessato ad approfondire questi temi, con o senza integrazione delle dinamiche legate al codice, può trovare supporto nei corsi formativi e nella attività consulenziali erogate da Bcademy.

Multisignature

Gli script “multifirma” (multisignature o più semplicemente multisig) fissano una condizione dove N chiavi pubbliche sono memorizzate nello script e almeno M di queste devono firmare per sbloccare un pagamento. Sono conosciuti anche come schema M-su-N, dove N è il numero totale delle chiavi e M è la soglia di firme richiesta per la convalida. Attualmente, gli script multisig standard sono limitati al massimo a 3 chiavi pubbliche, tuttavia tale limite si applica solo agli script multisig standard (conosciuto come “bare”), non agli script multisig racchiusi (wrapped) in uno Pay-to-Script-Hash (P2SH), limitati a 15 chiavi (ergo permettono di arrivare fino a 15-su-15).

La forma generale di uno script di blocco che fissa le condizioni di multifirma M-su-N è:

M <Public Key 1> <Public Key 2> … <Public Key N> N CHECKMULTISIG

Dove N è il numero totale delle chiavi pubbliche listate e M la soglia di firme richieste per spendere l’output. Uno script di blocco che fissa la condizione 2-su-3 ha questa forma:

2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

E può essere risolto con uno script di sblocco contenente coppie di firme e chiavi pubbliche: <Signature B> <Signature C> o una combinazione di due firme dalle chiavi private corrispondenti alle tre chiavi pubbliche listate. I due script insieme formerebbero la combinazione dello script di validazione:

<Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

A causa di un bug (divenuto parte delle regole del consensus, ergo replicato per sempre), viene applicato un workaround che modifica lo script per aggirare il bug, (semplicemente aggiungendo uno 0 all’inizio), ergo questa la forma corretta:

0 <Signature B> <Signature C> 2 <Public Key A> <Public Key B> <Public Key C> 3 CHECKMULTISIG

Pay-to-Script-Hash (P2SH)

P2SH è stato introdotto nel 2012 come una potente nuova tipologia di transazioni che semplifica notevolmente l’utilizzo di complessi script di transazione.

Si immagini di utilizzare uno script multisig per ciascun proprio cliente (AR o account receivable nel linguaggio fiscale), che blocca i pagamenti così da richiedere almeno due firme per sbloccarli (ad esempio il proprietario e un avvocato, o un partner) di modo da offrire garanzie contro attacchi, furti, etc.

2 <Owner’s Public Key> <Partner1 Public Key> <Partner2 Public Key> <Partner3 Public Key> <Attorney Public Key> 5 CHECKMULTISIG

Con questo genera problemi:

  • il proprietario dovrebbe comunicare questo script a tutti i clienti prima che effettuino il pagamento;
  • ciascun cliente dovrebbe utilizzare un wallet “speciale” capace di creare script di transazione customizzati (ed essere in grado di farlo);
  • la transazione risultante (o meglio l’indirizzo di destinazione) sarebbe probabilmente 5 volte più pesante di una semplice transazione di pagamento (questo script contiene chiavi pubbliche molto lunghe);
  • gli oneri della transazione, in termini di fee, sarebbero a carico del cliente;
  • uno script di transazione pesante (lungo) rimarrebbe nel set di UTXO nella RAM (anche se non necessariamente) di ogni full node (fino alla sua spesa).

P2SH è stato costruito per risolvere (parte di) queste problematiche, sostituendo script complessi con un hash. Quando una transazione tenta di spendere l’UTXO, deve contenere lo script che matcha con l’hash, oltre allo script di sblocco, ergo “paga allo script che si accoppia con questo hash, uno script che verrà presentato dopo, quando questo output è speso”. Lo script di blocco è sostituito con un hash presentato al sistema al momento della redemption invece che come script di blocco (redeem script). 

Redeem Script 2 PubKey1 PubKey2 PubKey3 PubKey4 PubKey5 5 CHECKMULTISIG
Locking Script HASH160 <20-byte hash of redeem script> EQUAL
Unlocking Script Sig1 Sig2 <redeem script>

Lo script complesso che definisce le condizioni di spesa dell’output (redeem script) non è presente nello script di blocco: al suo posto c’è soltanto un hash ed il redeem script è presentato in seguito, come parte dello script di sblocco quando l’output viene speso (ciò modifica l’onere delle commissioni etc).

P2SH Addresses

Proseguendo, BIP 13 ha definito la capacità di codificare l’hash dello script come un indirizzo in Base58Check, utilizzando il prefisso 5 che in Base58Check diventa 3, come ad esempio 39RF6JqABiHdYHkfChV6USGMe6Nsr66Gzw che permette di ricevere pagamenti (e dunque al pagante di costruirli) come un normale indirizzo bitcoin. Importante ricordare che l’onere delle commissioni viene spostato dal pagante al ricevente, che lo sosterrà nel momento in cui includerà lo script per spendere l’output.

Non è possibile inserire un P2SH dentro uno script di redeem P2SH, poiché la specifica non è ricorsiva, mentre è tecnicamente possibile includere RETURN in uno script di redeem (nessuna regola lo preclude) ma la transazione viene segnalata come invalida. Da notare che, visto che lo script di redeem viene presentato al network a latere (al momento della spesa), se si blocca un output con un hash di uno script di redeem invalido sarà comunque processato.

Data Recording Output (RETURN)

La blockchain può avere altri usi oltre ai pagamenti, e in molti hanno cercato di implementare l’utilizzo del linguaggio di script per applicazioni come notarizzazione, smart contract, etc. I primi tentativi di utilizzare il linguaggio di script di Bitcoin per questi propositi hanno implicato la creazione di output di transazione per memorizzare dati sulla blockchain (proof-of-existence). Molti developer considerano questo un abuso e scoraggiano tale uso, poiché “gonfia” la blockchain caricando il costo di storaggio dei dati su coloro che fanno girare un full node. Inoltre, queste transazioni creano UTXO che non possono essere spesi, utilizzando l’indirizzo bitcoin di destinazione come un campo di 20-byte free: i dati inseriti non corrispondono a nessuna chiave privata e il risultante UTXO non potrà mai essere speso, ergo non saranno mai rimossi dal database di set di UTXO che è destinato appunto a “gonfiarsi”.

Nella versione 0.9 del client Bitcoin Core si è raggiunto un compromesso introducendo l’operatore RETURN che permette di aggiungere 80 bytes di nonpayment data ad un output. A differenza dell’utilizzo dei “fake UTXO”, RETURN crea un explicitly provably unspendable, che non ha bisogno di essere salvato nel set UTXO. Gli output di RETURN sono salvati sulla blockchain (consumano spazio e aumentano le dimensioni della blockchain) ma non sono salvate nel set UTXO perciò non gonfiano la memory pool delle UTXO caricando i nodi dei costi della RAM. Uno script di RETURN figura come:

RETURN <data> dove lo spazio per i dati è limitato a 80 bytes e spesso rappresenta un hash: molte applicazioni aggiungono un prefisso per identificare il tipo di applicazione: Proof-of-Existence utilizza il prefisso da 8 byte DOCPROOF, che è codificato in ASCII con l’esadecimale 44 4f 43 50 52 4f 4f 46. Va ribadito che non c’è alcuno script di sblocco per spendere un output RETURN (e di solito si tratta di un output di zero bitcoin). Se RETURN è riferito ad un input di transazione, il motore di validazione degli script ferma l’esecuzione e segnala (marca) la transazione come invalida.

Una transazione standard, conforme con IsStandard() può avere solo un output RETURN, ma un singolo output RETURN può essere associato in una transazione con output di altro tipo.

Due nuove opzioni command-line sono state aggiunte in Bitcoin Core 0.10, datacarrier controlla l’inoltro e il mining delle transazioni RETURN, settando di default 1 per permetterle; datacarriersize accetta un argomento numerico che specifica la dimensione massima in byte di uno script RETURN, 83 bytes per default, il quale permette un massimo di 80 bytes di dati di RETURN più 1 byte dell’opcode RETURN e 2 bytes dell’opcode PUSHDATA.

Timelock

I timelock (blocchi temporali) sono restrizioni sulle transazioni (o output) per renderli spendibili solo dopo un certo tempo. Il timelock a livello della transazione è presente fin dall’inizio in Bitcoin, implementato nel campo nLocktime. Nel 2015 e 2016 sono state introdotte due nuove caratteristiche di timelock a livello degli UTXO:  CHECKLOCKTIMEVERIFY e CHECKSEQUENCEVERIFY. I timelock sono utili per postdatare transazioni e bloccare valore ad una certa data futura: ancor più, ampliano gli script bitcoin alla dimensione temporale, aprendo le porte a complessi smart contract a più fasi.

Transaction Locktime (nLocktime)

Il Locktime delle transazioni è appunto una configurazione a livello delle transazioni (un campo nella struttura dati delle transazioni) che definisce il primo momento dopo il quale una transazione è valida e può essere inoltrata al network o aggiunta alla blockchain. E’ noto anche come nLocktime dal nome della variabile utilizzata nel codebase di Bitcoin Core. Nella maggior parte delle transazioni è configurata (settata) a 0, che indica l’immediata propagazione ed esecuzione: se è non 0 e sotto i 500.000.000, il valore è interpretato come l’altezza (height) del blocco, che significa che la transazione non è valida e non è inoltrata o inclusa in blockchain prima di uno specifico blocco. Se la transazione è trasmessa al network prima del nLocktime specificato, viene respinta come non valida dal primo nodo che la riceve, e non inoltrata ad altri.

Il limite di nLocktime è che sebbene renda possibile spendere alcuni output in futuro, non rende impossibile spenderli fino a quel momento: rimane possibile inviare una transazione a 3 mesi e fare double-spending dello stesso UTXO da parte del mittente senza che il ricevente si accorga di nulla prima del tempo. L’unica garanzia è che il ricevente non può spendere prima del tempo.

Check Lock Time Verify (CLTV)

Nel dicembre 2015 una nuova forma di timelock è stata introdotta grazie ad un soft-fork, basata su una specifica in BIP-65, il nuovo operatore di script CHECKLOCKTIMEVERIFY (CLTV) è stato aggiunto al linguaggio di script. CLTV è un timelock pre-output (invece che un timelock per-transaction): aggiungendo l’opcode CLTV nello script di redeem di un output viene ristretto l’output stesso, così da non poter essere speso prima del tempo stabilito. CLTV non sostituisce nLocktime, ma restringe specifiche UTXO così da non poter essere spese finché il valore non è uguale o superiore a nLocktime. L’opcode di CLTV prende un parametro come input, espresso come numero nello stesso formato di nLocktime (altezza del blocco o Unix epoch time), e come indicato dal suffisso VERIFY è il tipo di opcode che blocca l’esecuzione di script se il risultato (outcome) è FALSE. Per bloccare un output, si inserisce CLTV nello script di redeem dell’output nella transazione che crea quell’output. Se normalmente (P2PKH) abbiamo:

DUP HASH160 <Public Key Hash> EQUALVERIFY CHECKSIG per bloccare l’output per tre mesi da ora, la transazione dovrebbe essere una P2SH con uno script di redeem:

<now + 3 months> CHECKLOCKTIMEVERIFY DROP DUP HASH160 <Public Key Hash> EQUALVERIFY CHECKSIG dove <now + 3 months> è l’altezza del blocco o il valore temporale stimato a 3 mesi dal momento in cui la transazione è stata minata: current block height + 12,960 (blocks) or current Unix epoch time + 7,760,000 (seconds).

Quando si proverà a spendere questo UTXO si costruirà una transazione che si riferisce all’UTXO come un input. Si utilizza la firma e la chiave pubblica nello script di sblocco di quell’input e si configura nLocktine della transazione per essere uguale o superiore al timelock configurato nel CHECKLOCKTIMEVERIFY. La transazione viene allora “broadcastata” nel network bitcoin. Se il parametro CHECKLOCKTIMEVERIFY configurato dal mittente è inferiore o uguale a Nlocktime della transazione di spesa, l’esecuzione dello script continua (opera come se un no operation o un opcode NOP fosse eseguito). Diversamente l’esecuzione dello script si ferma e la transazione è ritenuta invalida. Nello specifico, CLTV blocca l’esecuzione, marcando la transazione come invalida se (BIP-65):

  • la pila (stack) è vuota;
  • l’oggetto (item) in cima (on top) è inferiore a 0;
  • il tipo di lock-time (altezza del blocco vs timestamp) dell’oggetto in cima alla pila e il campo nLocktime non sono uguali;
  •  l’oggetto in cima alla pila è maggiore del campo di nLocktime della transazione;
  • Il campo nSequence dell’input è 0xffffffff.

Dopo l’esecuzione, se CLTV è soddisfatto, il parametro temporale rimane in cima alla pila e può essere necessario che sia “droppato”, con DROP, per una corretta esecuzione degli script di opcode seguenti.

nLocktime e CLTV sono timelock “assoluti” (absolute timelocks) che specificano un punto assoluto nel tempo. Ci sono anche timelock “relativi” (relative timelocks) che specificano come condizione della spesa di un output il trascorrere di un certo tempo dalla conferma dell’output nella blockchain. Sono utili perché permettono che una catena di due o più transazioni interdipendenti siano tenute off-chain, mentre si impone un vincolo di tempo su una transazione che dipende dal tempo trascorso dalla conferma di una transazione precedente. 

In altre parole, l’orologio non parte finché l’UTXO è registrata sulla blockchain. Questa funzionalità è particolarmente utilizzata negli stati dei canali bi-direzionali e in Lightning Network. Come i timelock assoluti, i relativi sono implementati sia a livello delle transazioni che a livello delle opcode di script. Il livello delle transazioni è implementato come una regola di consenso sul valore di nSequence, un campo della transazione che è configurato in ogni input di transazione. Al livello di script i timelock relativi sono implementati con l’opcode CHECKSEQUENCEVERIFY (CSV) (BIP-68 e BIP-112, attivati in maggio 2016 con un soft-fork delle regole di consenso).

nSequence

I timelock relativi possono essere configurati in ogni input di transazione tramite il campo nSequence, originariamente (ma mai implementato in questo senso) per permettere modifiche delle transazioni nella mempool: una transazione contenente input con nSequence inferiore a 232-1(0xFFFFFFFF) indica una transazione non ancora “finalizzata” (finalized). Questa transazione sarebbe mantenuta nella mempool fino alla sua sostituzione con un’altra transazione “spendente” lo stesso input con un valore di nSequence superiore. Una volta che la transazione i cui input avevano un valore di nSequence di  232-1(0xFFFFFFFF)  è stata ricevuta sarebbe considerata finalizzata e minata. Il senso originale non è mai stato implementato e il valore di nSequence è di base configurato a (0xFFFFFFFF) nelle transazioni che non utilizzano timelock. Per le transazioni con nLocktime o CLTV il valore deve essere configurato come inferiore a 231di modo che il timelock abbia effetto.

Come CLTV e nLocktime c’è un opcode dello script per i timelock relativi che sfrutta il valore di nSequence nello script. L’opcode è CHECKSEQUENCEVERIFY (CSV). I timelock relativi con CSV sono particolarmente utilizzati  quando molte transazioni sono create e firmate, ma non propagate, ergo mantenute “off-chain”. Una transazione “figlia” non può essere utilizzata finché il “genitore” non è stato propagato, minato, e “invecchiato” il tempo specificato nel timelock relativo. Un’applicazione di questo caso d’uso può essere vista in Lightning Network.

Median-Time-Past

In Bitcoin c’è una sottile ma significante differenza tra il wall time e il consensus time, poiché in un network decentralizzato ciascuno ha la propria prospettiva temporale: gli eventi non sono simultanei e la latenza deve essere considerata da questa stessa prospettiva. La sincronizzazione tra tutti che crea un registro comune è raggiunta ogni 10 minuti su come lo stesso registro esisteva in passato. Il timestamp (marca temporale) nell’header del blocco viene configurato dai miner, ma esiste un certo grado di libertà permesso dalle regole di consenso per tenere conto delle differenze nella precisione dell’orologio tra i nodi decentralizzati. 

Sfortunatamente ciò crea un incentivo per i miner a mentire sul tempo all’interno di un blocco per guadagnare extra-fee includendo transazioni “timelockate” ancora non “mature”. BIP-113 definisce una nuova misurazione del tempo di consenso (consensus time) chiamata Median-Time-Past: è calcolato prendendo il timestamp degli ultimi 11 blocchi e facendo la media, che diviene il consensus time ed è usato per tutti i calcoli dei timelock. Prendendo un punto medio da approssimativamente due ore indietro, l’influenza del timestamp di un blocco è ridotta. Incorporando 11 blocchi, nessun miner può influenzare il timestamp per guadagnare fee inserendo transazioni ancora non mature.

Median-Time-Past cambia l’implementazione del calcolo del tempo per nLocktime, CLTV, nSequence e CSV (il consensus time è approssimativamente un’ora indietro rispetto all’orologio). Se si creano transazioni con timelock occorre tenerne conto quando si stima il valore desiderato da codificare in nLocktime, CLTV, nSequence e CSV.

Alessio Salvetti
Alessio Salvetti

Co-founder di Bcademy e board member (VP), Alessio è partner e board member di Impact Hub Trentino, uno dei 102 nodi del network mondiale. Dopo un'esperienza come docente di filosofia, abbandona l’insegnamento per coordinare un team di ricerca sui temi della connessione tra neuroscienze ed economia, prima di dedicarsi alla vera e propria creazione d’impresa. Business developer e consulente per numerose startup, bitcoiner per passione ed esperto di modeling e lean startup, è co-founder di Inbitcoin e responsabile per l’erogazione dei prodotti di Bcademy (CPO).

Utilizziamo i cookie per essere sicuri che tu possa avere la migliore esperienza sul nostro sito. Se continui ad utilizzare questo sito noi assumiamo che tu ne sia felice.