Bitcoin Smart Contract 101
Affronteremo la teoria e la pratica che ci permette di utilizzare il linguaggio Bitcoin Script per creare uno Smart Contract che consegna i suoi bitcoin solo a chi conosce una password.
Il codice Bitcoin Script che scriveremo e', anche se banale, uno Smart Contract!
NB: e' una demo esclusivamente a fini ludici per introdurre il funzionamento del linguaggio Bitcoin Script.
NBB: si sconsiglia vivamente di utilizzare in mainnet questo tipo di Smart Contract!

TL;DR

Entrare in Hansel, eseguire
1
cd p2sh_password
2
./main.sh passwordSceltaDaHansel passwordTentataDaGretel
3
4
# per attivare il debugger
5
DEBUG=1 ./main.sh passwordSceltaDaHansel passwordTentataDaGretel
Copied!
interpretare l'stdout ed i sorgenti dello script per capire cosa succede
🔦

Teoria di Smart Contract Bitcoin

Prima di procedere siamo costretti ad introdurre tre aspetti fondamentali
  1. 1.
    i passaggi necessari per sviluppare lo Smart Contract
  2. 2.
    cosa sono le UTXO
  3. 3.
    cosa significa PS2H

Bitcoin Smart Contract development process

Paragonare il processo da svolgere per creare ed eseguire uno Smart Contract (SC) Bitcoin al processo di una web app e' arduo, proviamoci!
  1. 4.
    (SC build/compile) Trasformare il redeem script
    1. 2.
      In Script Hash tramite l'applicazione di SHA256+RIPEMD-160
  2. 5.
    (SC deploy) Pubblicazione di una transazione che invia bitcoin all'indirizzo (4.3) vincolandoci dei bitcoin.
L'esecuzione dello Smart Contract avviene quando si vuole accedere ai fondi - eg. inviandoli ad un nuovo indirizzo Bitcoin - a lui assegnati: si crea una transazione contenente il sorgente del redeem script assieme ai valori di input e la si invia ai nodi della rete.
Se il sorgente del redeem script presentato per l'esecuzione corrisponde allo Script Hash dal quale e' stato derivato il Bitcoin address, e la sua esecuzione utilizzando i valori di input ha esito positivo, abbiamo a disposizione i bitcoin assegnati allo Smart Contract e possiamo consegnarli ad un altro indirizzo Bitcoin.
Nei passaggi seguenti alterneremo teoria e coding pratico, gli snippet che trovate colorati eseguiteli dentro Hansel o Gretel.
Le nozioni che seguono sono una sintesi spinta per fornire il minimo indispensabile alla realizzazione del primo Smart Contract Bitcoin.
Non vengono volutamente approfonditi argomenti come: ScriptPubKey, serializzazione/deserializzazione dello ScriptSig e costruzione di input/output associati ad una transazione con conseguenti modalita' di firma tramite le opzioni SIGHASH.
Nella sezione Studiare Bitcoin a fondo trovate il materiale per approfondire.

UTXO - Unspent Transaction Output

Un concetto, anomalo rispetto ai sistemi di pagamento ai quali siamo abituati, riguarda la gestione del saldo in Bitcoin.
Parlando di pagamenti classici, quando ne riceviamo uno, che sia un bonifico o contante, l'ammontare ricevuto va a sommarsi ad un conto unico al quale abbiamo accesso. Quando dobbiamo inviare denaro preleviamo dal conto unico la quantita' che ci interessa e la consegnamo al beneficiario.
In Bitcoin l'ammontare di un pagamento ricevuto non va ad unirsi ad un conto unico, rimane indipendente, per questo si utilizza il termine UTXO. Quando vogliamo creare un pagamento in Bitcoin, invece che prelevare dal conto, si seleziona una gruppo di UTXO i quali saldi sono sufficienti per colmare il pagamento verso il beneficiario desiderato.
Potenzialmente, ed e' anche prassi diffusa e caldamente consigliata, tutte le volte che ricevete un pagamento potete generare un indirizzo Bitcoin dedicato. Essendo le UTXO visibili pubblicamente sulla blockchain di Bitcoin, la differrenzazione dei beneficiari - anche facenti capo ad una sola entita' - permette di migliorare la privacy finanziaria.
Ogni UTXO e' vincolata ad un solo Bitcoin address.

P2SH - Pay to Script Hash

In Bitcoin, nel momento che si crea una UTXO, si deve indicare il beneficiario associandovi un identificativo chiamato Bitcoin address.
Esistono differenti tipologie di Bitcoin address, P2SH e' una di queste.
Il beneficiario di una UTXO P2SH e' un listato di codice Bitcoin Script.
Ebbene si, in Bitcoin si puo' effettuare un pagamento utilizzando come destinatario uno snippet di codice.
Solo chi e' a conoscenza di tale snippet ed, eventualmente, aggiungendo parametri di input (nel nostro use case il parametro di input sara' una password), e' in grado di ottenere come risultato il valore 1 dalla sua esecuzione potra' riscattare tale pagamento.
PS2H e' l'acronimo di Pay to Script Hash
Tramite i passaggi che vedremo piu' avanti il codice sorgente dello script viene trasformato in Script Hash e successivamente in Bitcoin address.
Grazie a questo approccio solo chi e' a conoscenza del codice sorgente dello script, e degli eventuali parametri di input, potra' riscattare i bitcoin che hanno come beneficiario tale script.
Finche' la UTXO PS2H non viene reclamata l'unico dato visibile e' lo Script Hash. NB: non bisogna dimenticare che quando si pubblica l'eventuale transazione di reclamo della UTXO, che rende visibile il sorgente dello script e dei suoi parametri di input, questa risiedera' nella cosiddetta mempool di ogni nodo della rete Bitcoin.
In quel frangente di tempo chiunque abbia un nodo Bitcoin puo' analizzare il vostro script e tentare di attaccarlo!
Per questo motivo, in mainnet, dove i bitcoin hanno valore, si utilizzano P2SH che richiedono come parametri di input firme digitali, cosi' da vincolare inequivocalbilmente la UTXO al/ai beneficiari/o, e non una semplice password!

Identificare lo use case

Dopo aver sottratto con un sotterfugio i bitcoin dalla strega, Hansel decide di bloccare la parte destinata a Gretel utilizzando una password.
Per far questo Hansel crea un address P2SH prevedendo
  • come parametri di input un unico valore, lo SHA256 della loro parola segreta che risulta essere "fegatini"
  • come business logic (redeem script) l'esecuzione di un ulteriore SHA256 sul parametro di input e il confronto del risultato con il valore SHA256(SHA256('fegatini')) da lui precalcolato
Solo chi fornisce la giusta password può sbloccare interamente il tesoro!
Riuscirà la nostra Gretel a ricordarsi dei fegatini, sbloccare i bitcoin e vivere felice e contenta con Hansel e suo padre taglialegna a bordo di una LAMBO? 🚘

Specifiche in pseudo codifica

Abbiamo detto che Hansel vuole realizzare uno script che verifica se e' stato fornito un parametro di input che corrisponde ad un determinato digest.
A grandi linee potremmo rappresentare la funzione che verifica la password cosi'
1
function is_password_valid(password_sha256) {
2
password_valid = SHA256(SHA256('fegatini'))
3
return SHA256(password_sha256) === password_valid)
4
}
Copied!
A differenza di molti linguaggi di programmazione, in Bitcoin script, i valori precedono le istruzioni che ne fanno uso; vedi RPN - Reverse Polish.
Il corpo della nostra funzione diventerebbe qualcosa del genere
1
'fegatini' SHA256 SHA256 password_valid
2
password_sha256 SHA256 password_valid is_equal
Copied!
Il primo doppio SHA256 e' costante quindi possiamo calcolarlo
1
PWD_DOUBLE_SHA256=`printf 'fegatini' | sha256sum | xxd -r -p | sha256sum -b | awk '{print $1}'`
2
echo $PWD_DOUBLE_SHA256
Copied!
Tra il primo SHA256 ed il secondo facciamo uso di xxd perche' in Bitcoin tutti i valori nello stack sono considerati array di byte.
Il corpo della nostra funzione diventa quindi
1
cef813adf3b25b5f92ab0ced01fcbaa5bc6a6cdb64693566ca775f44192799d7 password_valid
2
password_sha256 SHA256 password_valid is_equal
Copied!
per essere piu' concisi potremmo
1
password_sha256 SHA256 cef813adf3b25b5f92ab0ced01fcbaa5bc6a6cdb64693566ca775f44192799d7 is_equal
Copied!
Essendo Bitcoin Script stack-based la pseudo codifica conviene descriverla seguendo la stessa prassi
1
password_sha256
2
SHA256
3
cef813adf3b25b5f92ab0ced01fcbaa5bc6a6cdb64693566ca775f44192799d7
4
is_equal
Copied!
Infine non ci resta che utilizzare i nomi convenzionali degli operatori a disposizione in Bitcoin Script detti opcodes.
1
password_sha256
2
OP_SHA256
3
cef813adf3b25b5f92ab0ced01fcbaa5bc6a6cdb64693566ca775f44192799d7
4
OP_EQUAL
Copied!

ScriptSig design - Suddivisione dello script in due parti

Business logic imposta denominata redeem script

Come detto in precedenza nel redeem script non dobbiamo descrivere i parametri di input, quindi sara' composto da
1
OP_SHA256
2
cef813adf3b25b5f92ab0ced01fcbaa5bc6a6cdb64693566ca775f44192799d7
3
OP_EQUAL
Copied!
Visto cosi' sembra incompresibile!
Vediamo come si comportano le opcodes OP_SHA265 e OP_EQUAL riguardo allo stack.
OP_SHA256, il quale ricade nella sezione crypto, ha il compito di effettuare un'operazione di pop (estrazione dello stack), applicare la funzione crittografica SHA256 ed effettuare l'operazione di push (inserimento nello stack) del risultato.
OP_SHA256 avendo necessita' di effettuare l'operazione di POP sottoindende che servira' un parametro di input da inserire nello stack prima della sua esecuzione!
Successivamente abbiamoOP_EQUAL facente parte delle opcodes di Bitwise logic.
OP_EQUAL esegue due volte l'operazione di pop. Successivamente esegue l'operazione di push con valore 1 se i valori estratti tramite pop sono uguali oppure 0 se sono diversi.
OP_EQUAL ha necessita' di svolgere due operazioni di pop.
Nel nostro caso il primo elemento dello stack estratto e' il valore inserito tramite push da OP_SHA256. Il secondo elemento dello stack estratto e' il doppio SHA256 di 'fegatini' posizionato sopra OP_EQUAL!
Salviamolo in una variabile per comodita'
1
REDEEM_SCRIPT="OP_SHA256 $PWD_DOUBLE_SHA256 OP_EQUAL"
Copied!

Parametri di input del redeem script necessari ad eseguire la business logic

Come descritto poco sopra, la transazione è bloccata da una password che Gretel deve forinire.
Hansel, nella sua business logic, ha hardcodato il doppio SHA256 quindi la password da fornire e' lo SHA256 di 'fegatini'
1
PWD_SHA256=`printf 'fegatini' | sha256sum | awk '{print $1}'`
2
echo $PWD_SHA256
Copied!
con il quale otteniamo 273c25751f15926d6064916b8765461d77a6d525ee3fd25a37b88755c6d4db20

Verifica funzionamento redeem script con btcc/btcdeb

Per una rapida verifica possiamo dare in pasto a btcc redeem script e parametri di input.
Successivamente possiamo verificare tutta l'esecuzione con l'ausilio di btcdeb.

btcc e' un Bitcoin Script compiler

1
COMPILED=`btcc $PWD_SHA256 $REDEEM_SCRIPT`
Copied!

btcdeb e' un Bitcoin Script debugger

1
btcdeb $COMPILED
Copied!
A sinistra vi comparira' una rappresentazione dello script di Hansel, compreso dei parametri di input; sulla destra viene visualizzato lo stack che all'avvio di ogni Smart Contract Bitcoin e' vuoto.
Digitate step e premete invio per eseguire lo script un passaggio alla volta!
fcffeec024be4987b1381a9cf2779a63.mp4
2MB
Binary
btcdeb in action!
Per uscire da btcdeb usate la shortcut CTRL+D
Se l'ultimo elemento rimanente nello stack e' 01 significa che l'esecuzione e' terminata con successo!

Trasformare la business logic (redeem script) in esadecimale poi in Script Hash ed infine in indirizzo Bitcoin

Trasformare la business logic (redeem script) in esadecimale

Possiamo usare btcc, questa volta pero' utilizzeremo solo il redeem script senza il parametro di input
1
REDEEM_SCRIPT_COMPILED=`btcc $REDEEM_SCRIPT`
2
echo $REDEEM_SCRIPT_COMPILED
Copied!
otteniamo a820cef813adf3b25b5f92ab0ced01fcbaa5bc6a6cdb64693566ca775f44192799d787
Per chi vuole scoprire cosa btcc svolge sotto il cofano consigliamo la lettura del file main.sh disponibile nella cartella /opt/wald/ps2h_password.

SHA256+RIPEMD-160

1
SCRIPT_SHA256=`printf $REDEEM_SCRIPT_COMPILED | xxd -r -p | openssl sha256 | sed 's/^.* //'`
2
SCRIPT_RIPEMD160=`printf $SCRIPT_SHA256 | xxd -r -p | openssl ripemd160 | sed 's/^.* //'`
3
echo $SCRIPT_RIPEMD160
Copied!
Risultato 0482e32347749e785e203e1c663b190c1db89071.
Ehy ma prima del RIPEMD-160 viene svolto uno SHA256, che succede?! The exact reason why SHA-256 was used in combination with RIPEMD-160 isn't known. [...]

Indirizzo Bitcoin

Per completare il Bitcoin address bisogna prependere un prefisso e codificare il tutto in Base58Check.
Il prefisso serve ad indicare se operiamo in mainnet o testnet/regtest e che tipo di Bitcoin address stiamo utilizzando.
Il playground opera in regtest, il prefisso che ci interessa e' Testnet script hash che corrisponde al valore esadecimale c4.
Possiamo svolgere quindi l'encoding utilizzando base58
1
P2SH_ADDR=`printf c4$SCRIPT_RIPEMD160 | xxd -p -r | base58 -c`
2
echo $P2SH_ADDR
Copied!
e finalmente ottenere il nostro indirizzo P2SH!
2Msf5VFsuGZBthnwXvUGxajtJKgiGd5GDQi

Pubblicazione di una transazione che invia bitcoin all'indirizzo Bitcoin vincolando la UTXO allo Script Hash

Adesso abbiamo bisogno di inviare bitcoin al nostro indirizzo P2SH.
1
bitcoin-cli generatetoaddress 101 $P2SH_ADDR
Copied!
Aprendo http://localhost:8094/regtest/address/2Msf5VFsuGZBthnwXvUGxajtJKgiGd5GDQi possiamo verificare di aver ricevuto UTXO al nostro address.
Aprendo dal blockchain regtest explorer una qualsiasi delle transazioni associate possiamo verificare che lo Script Hash e' quello desiderato ovvero 0482e32347749e785e203e1c663b190c1db89071
cliccate il pulsante evidenziato di rosso per visualizzare i dettagli

Esecuzione dello Smart Contract

Siamo arrivati all'ultimo passaggio! L'esecuzione dello Smart Contract!
Adesso dobbiamo creare una transazione che, fornendo password e redeem script, permette di utilizzare la UTXO associata al P2SH ed invia i bitcoin ad un nuovo indirizzo.

Indirizzo del beneficiario

Partiamo con il passaggio semplice, creiamo un indirizzo
1
BENEFICIARIO=`bitcoin-cli getnewaddress`
Copied!

Selezione della transazione che contiene la UTXO P2SH

Adesso dobbiamo selezionare la transazione, fra tutte quelle ricevute dal nostro indirizzo P2SH, che ha raggiunto la coinbase maturity.
Questo passaggio riguardante la coinbase maturity e' necessario solo perche' stiamo usando la transazione coinbase come UTXO, ottenuta durante il mining dei blocchi con il comando generatetoaddress.
1
bitcoin-cli importaddress $P2SH_ADDR fegatini
2
TXID_WITH_MATURITY=`bitcoin-cli listunspent 100 101 "[\"$P2SH_ADDR\"]" | jq -r '.[0].txid'`
Copied!

Creazione della transazione che esegue lo Smart Contract

Adesso viene il bello! Dobbiamo costruire una transazione che fa uso di
  1. 1.
    La UTXO di TXID_WITH_MATURITY
  2. 2.
    REDEEM_SCRIPT_COMPILED
  3. 3.
    BENEFICIARIO
Quando si lavora con Bitcoin Script custom come questo, purtroppo (o per fortuna :D), non esistono tool che permettono la costruzione interattiva della transazione.
A scopo dimostrativo abbiamo predisposto un file bash che svolge i passaggi necessari
1
cd /opt/wald/p2sh_password
2
SC_RAW_TX=`./create_raw_tx.sh $TXID_WITH_MATURITY $PWD_SHA256 $REDEEM_SCRIPT_COMPILED $BENEFICIARIO`
3
echo $SC_RAW_TX
Copied!
L'output del file bash e' la transazione, gia' pronta nel suo formato esadecimale.
NB: prima di inviare una transazione che esegue uno smart contract custom e' sempre bene verificarne l'esito!

QA transazione che esegue Smart Contract prima di inviarla ai nodi

Ci viene in soccorso btcdeb!
1
RAW_TX_WITH_MATURITY=`bitcoin-cli getrawtransaction $TXID_WITH_MATURITY`
2
btcdeb --txin=$RAW_TX_WITH_MATURITY --tx=$SC_RAW_TX
Copied!

Invio della transazione alla rete

La transazione da inviare potere recuperarla con
1
echo $SC_RAW_TX
Copied!
Questo passaggio si puo' fare utilizzando la chiamata RPC sendrawtransaction oppure tramite http://localhost:8094/regtest/tx/push.
NB: importante ripetere che, una volta inviata la transazione, tutti i nodi della rete sono a conoscenza del codice sorgente del redeem script e dei valori di input.
Ogni nodo eseguira' il nostro Smart Contract e se l'esecuzione completera' con successo inoltrera' la transazione ai nodi vicini.

Mining di blocchi per confermare la transazione e farla uscire dalla mempool

Attualmente la transazione si trova in mempool, possiamo visualizzare la mempool tramite i link qui sulla sinistra.
Per confermare la transazione e farla uscire dalla mempool bastera' minare un blocco.
Se proseguite con l'esempio successivo Attacare uno Smart Contract non effettuate il mining!!