A supporto del live talk per il Digithon Ottobre 2021
Introduzione
Questa guida ha lo scopo didattico di guidare il discente alla realizzazione e invio di una transazione in Bitcoin Testnet, con lo scopo di esplorare i meccanismi interni che fanno funzionare Bitcoin.
Il codice e’ inteso come codice didattico, da non utilizzare in produzione.
Infatti, il tipo di transazione utilizzata e’ una “Legacy P2PKH” oramai in disuso, ma la piu’ semplice per iniziare ad approcciare il funzionamento del network Bitcoin.
Inoltre, il metodo utilizzato per generare le chiavi private e’ considerato davvero poco sicuro, quindi assolutamente da non utilizzare in ambiente di produzione.
Ambiente di lavoro
Utilizzeremo il linguaggio di programmazione javascript con l’engine di esecuzione node. Primo passo è, quindi, installare sul proprio computer il comando node seguendo le istruzioni su questa pagina web https://nodejs.dev/download/
Verificare l’installazione con il seguente comando
$> node -v
v14.17.4
Ora possiamo creare una cartella di lavoro e inizializzare un nuovo progetto node.
cd
mkdir bitcoin_transaction_in_code
cd bitcoin_transaction_in_code
npm init --yes
Utilizzeremo la libreria bitcore-lib che si installa nel progetto node con il comando seguente. NB: Assicurandoci di essere sempre nella cartella di progetto bitcoin_transaction_in_code
npm install bitcore-lib
Scegli un numero casuale
Una chiave privata di Bitcoin e’ un numero casuale a 256 bit. Per comprendere di cosa proviamo a visualizzare questo numero.
Crea un file di codice chiamato 01_scegli_un_numero.js e incolla il contenuto seguente
// 01_scegli_un_numero.js
console.log('una chiave privata e\' un numero casuale a 256 bit');
const min = 0;
const max = BigInt(Math.pow(2, 256));
console.log('ossia un numero da: ', 0);
console.log('a: ', max);
per eseguire il codice usa
$> node 01_scegli_un_numero.js
L’esecuzione ci mostra il range di un numero a 256 bit
una chiave privata e' un numero casuale a 256 bit
ossia un numero da: 0
a: 115792089237316195423570985008687907853269984665640564039457584007913129639936n
https://en.wikipedia.org/wiki/Power_of_two 115,792,089,237,316,195,423,570,985,008,687,907,853,269,984,665,640,564,039,457,584,007,913,129,639,936
un numero con 78 cifre !!!
E’ un numero astronomico, non troppo distante dal numero di atomi che si stima compongano l’universo visibile!
https://www.livescience.com/how-many-atoms-in-universe.html
This gives us 10^82 atoms in the observable universe. To put that into context, that is 10,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000,000 atoms.
un numero 83 cifre !!!
Chiave privata Bitcoin non deterministica
il codice seguente serve a mostrare la creazione di una chiave privata “casuale”. Ad ogni esecuzione del codice la chiave e’ differente.
Crea un altro file chiamato 02_private_key_casuale.js e incolla il contenuto seguente
// 02_private_key_casuale.js
const bitcore = require('bitcore-lib');
// bitcore.Networks.defaultNetwork = bitcore.Networks.livenet;
bitcore.Networks.defaultNetwork = bitcore.Networks.testnet;
// Genera una nuova Private Key
const privateKey = new bitcore.PrivateKey();
// Genera la Public Key dalla private key
const address = privateKey.toAddress();
// Calcola il Bitcoin Address
const btcAddress = address.toString();
console.log('Nuovo Indirizzo BTC casuale:', btcAddress );
In esecuzione
$> node 02_private_key_casuale.js
Nuovo Indirizzo BTC casuale: mhuTK7guQqQmvUyc2BQX8uUyZdkV32u73h
L’indirizzo BTC e’ calcolato come l’applicazione di una doppia funzione di hash sulla public key, seguita da un encoding in Base58 con codice di controllo (check).
"Public Key" => SHA256 => RIPEMD160 => Base58CheckEncoding
Chiave privata Bitcoin deterministica
Il codice seguente produrrà sempre la stessa private key. Il metodo utilizzato e’ davvero poco sicuro, quindi da non utilizzare in produzione.
Crea un altro file chiamato 03_deterministic_private_key.js e incolla il contenuto seguente
// 03_deterministic_private_key.js
const bitcore = require('bitcore-lib');
//bitcore.Networks.defaultNetwork = bitcore.Networks.livenet;
bitcore.Networks.defaultNetwork = bitcore.Networks.testnet;
// Uso una frase per derivare la chiave privata
const mappa_del_tesoro = `
Seconda stella a destra, questo è il cammino
E poi dritto fino al mattino
Poi la strada la trovi da te
Porta all\'isola che non c\'è
`;
const value = Buffer.from(mappa_del_tesoro);
const hash = bitcore.crypto.Hash.sha256(value);
const bn = bitcore.crypto.BN.fromBuffer(hash);
// Deterministic Private Key
const deterministicPrivateKey = new bitcore.PrivateKey(bn);
const deterministicAddress = deterministicPrivateKey.toAddress();
console.log('Indirizzo BTC deterministico:', deterministicAddress.toString());
// La nostra private Key
const wif = deterministicPrivateKey.toWIF();
console.log('La chiave privata generate in formato WIF (importabile nei wallet):');
console.log(wif);
Lanciando piu’ volte questo codice noterete che l’indirizzo prodotto e’ sempre lo stesso.
$> node 03_deterministic_private_key.js
Indirizzo BTC deterministico: mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2
La chiave privata generate in formato WIF (importabile nei wallet):
cVqG3A2GPq1deoR5Snan8B8G24gJwobt1PUxGgb7bWQyYkZaHjrk
Da questo momento in poi utilizza una tua chiave privata deterministica, altrimenti la mia risulterà gi**à svuotata! Basta cambiare la mappa del tesoro.
Regaliamoci bitcoin di test
Puntiamo il nostro browser su https://testnet-faucet.mempool.co/ e “regaliamoci” bitcoin in testnet 😄
Inseriamo il nostro Bitcoin Address deterministico, quello generato nello step precedente:
Verifichiamo il contenuto del nostro address
Ora possiamo usare il block explorer sulla testnet https://live.blockcypher.com/btc-testnet/ e inserendo nella barra di ricerca il nostro address bitcoin potremo vedere il saldo ancora non confermato.
Dobbiamo aspettare che la nostra transazione entri in un blocco per vederla confermata. Dovrebbero bastare 20 / 30 minuti.
Esplorare la blockchain tramite API
Altre modalità per esplorare la blockchain e’ tramite chiamate API. Suggerisco l’utilizzo del comando https://httpie.io/ da riga di comando
Considerando che il mio indirizzo bitcoin deterministico e’ mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2
# leggi il balance dell'indirizzo
# https://api.blockcypher.com/v1/btc/test3/addrs/{address}/balance
http GET https://api.blockcypher.com/v1/btc/test3/addrs/mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2/balance
Qui possiamo vedere che il final balance di questo address e’ 100.000 satoshi (ossia 0.001 BTC)
Costruiamo la nostra transazione
Per costruire la transazione in primis creiamo due private key, una sorgente e una destinazione.
Poi avremo bisogno di un utxo (unspent transaction output, verrà dettagliato dal vivo nel talk) dell’address sorgente.
// 05_transaction.js
var bitcore = require('bitcore-lib');
//bitcore.Networks.defaultNetwork = bitcore.Networks.livenet;
bitcore.Networks.defaultNetwork = bitcore.Networks.testnet;
function deterministicPrivateKey(mappa_del_tesoro){
const value = Buffer.from(mappa_del_tesoro);
const hash = bitcore.crypto.Hash.sha256(value);
const bn = bitcore.crypto.BN.fromBuffer(hash);
return new bitcore.PrivateKey(bn);
}
const mappa_del_tesoro = `
Seconda stella a destra, questo è il cammino
E poi dritto fino al mattino
Poi la strada la trovi da te
Porta all\'isola che non c\'è
`;
const sorgente = deterministicPrivateKey(mappa_del_tesoro);
const destinazione = deterministicPrivateKey('apriti sesamo');
const address_sorgente = sorgente.toAddress();
const address_destinazione = destinazione.toAddress();
// Transazione di tipo: Legacy P2PKH
var tx = bitcore.Transaction();
// devo trovare un output di transazione non speso
// da riga di comando
// http get https://api.bitcore.io/api/BTC/testnet/address/mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2/?unspent=true
var utxo = {
"txId" : "14279f80f48ad4ee19800fc256054f1ed475c715a6f81295475eb23093e6ac6c",
"outputIndex" : 0,
"address" : "mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2",
"script" : "76a914b2e509c9e7ce864c9fd6771e9709cac3eab3938b88ac",
"satoshis" : 100000
};
tx.from(utxo);
// 30000 satoshi di pagamento
tx.to(address_destinazione, 30000);
// 20000 satoshi al miner
tx.fee(20000);
// il resto mettilo sempre nel mio wallet sorgente :)
tx.change(address_sorgente);
// firmo questa transazione con la mia chiave privata
// solo io posso spostare i miei utxo
tx.sign(sorgente);
tx.serialize();
const raw_transaction = tx.toString();
console.log('raw transaction: ', raw_transaction);
Per recuperare gli utxo ossia output non spesi possiamo utilizzare vari metodi.
Con le API da riga di comando
# con blockcypher.com
http get https://api.blockcypher.com/v1/btc/test3/addrs/mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2
# oppure con bitcore.io
http get https://api.bitcore.io/api/BTC/testnet/address/mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2/?unspent=true
Oppure dai block explorer web ad esempio https://live.blockcypher.com/btc-testnet/address/mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2/
In questo momento, abbiamo bisogno di informazioni molto precise quindi utilizzeremo il metodo API a riga di comando
http get https://api.bitcore.io/api/BTC/testnet/address/mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2/?unspent=true
il comando di bitcore.io ha un output molto comodo
devo mappare in questo modo i parametri richiesti per l’oggetto utxo di bitcore-lib
oggetto utxo | output comando |
---|---|
txId | mintTxid |
outputIndex | mintIndex |
address | address |
script | script |
satoshis | value |
ora posso lanciare lo script e visualizzare la transazione in formato raw.
$> node 05_transaction.js
raw transaction: 02000000016cace69330b25e479512f8a615c775d41e4f0556c20f8019eed48af4809f2714010000006b48304502210091f0ae321cddd031a00660df57ce87433d2668d78556671b988d953a9d044e6602202e7286d9ae24d8fd5e73de88ff895390aeb6bb08108906eb7cb9aa8be6281729012102d9af3459a1ddc32428c0af32bc36b0cc1aa6fda033d0d0e715af9cab2e9853acffffffff0230750000000000001976a91450e22df3e9d42563b3482e81d8f66ab090beeb5e88ac50c30000000000001976a914b2e509c9e7ce864c9fd6771e9709cac3eab3938b88ac00000000
Possiamo verificare se la nostra transazione e’ ben costruita e il suo contenuto con lo strumento di verifica online https://live.blockcypher.com/btc/decodetx/ ricordando di selezionare TestNet.
Vedremo in formato JSON la transazione con i suoi input e output.
Puoi notare, ad esempio, i due output uno per il pagamento e uno per il resto.
{
"addresses": [
"mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2",
"mntdJdNHiWSY8vhoFQUb6PxW9rpNE7hrdZ"
],
"block_height": -1,
"block_index": -1,
"confirmations": 0,
"double_spend": false,
"fees": 20000,
"hash": "48137bd79c266f9d01c263489ed0aa05f15be1a3ed20a05e99a8cecfb9ef841c",
"inputs": [
{
"addresses": [
"mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2"
],
"age": 2099861,
"output_index": 1,
"output_value": 100000,
"prev_hash": "14279f80f48ad4ee19800fc256054f1ed475c715a6f81295475eb23093e6ac6c",
"script": "48304502210091f0ae321cddd031a00660df57ce87433d2668d78556671b988d953a9d044e6602202e7286d9ae24d8fd5e73de88ff895390aeb6bb08108906eb7cb9aa8be6281729012102d9af3459a1ddc32428c0af32bc36b0cc1aa6fda033d0d0e715af9cab2e9853ac",
"script_type": "pay-to-pubkey-hash",
"sequence": 4294967295
}
],
"outputs": [
{
"addresses": [
"mntdJdNHiWSY8vhoFQUb6PxW9rpNE7hrdZ"
],
"script": "76a91450e22df3e9d42563b3482e81d8f66ab090beeb5e88ac",
"script_type": "pay-to-pubkey-hash",
"value": 30000
},
{
"addresses": [
"mwprx4B5aTLTD8eFDPBvyfBBrtMk68Cqk2"
],
"script": "76a914b2e509c9e7ce864c9fd6771e9709cac3eab3938b88ac",
"script_type": "pay-to-pubkey-hash",
"value": 50000
}
],
"preference": "medium",
"received": "2021-10-20T09:47:56.398974575Z",
"relayed_by": "54.160.217.150",
"size": 226,
"total": 80000,
"ver": 2,
"vin_sz": 1,
"vout_sz": 2,
"vsize": 226
}
Bitcoin script
Possiamo anche notare le voci script. Questo e’ il codice che determina le logiche della mia transazione (P2PKH) e l’esecuzione di questo script valida oppure no la transazione.
Possiamo visualizzare questi script con questo codice da inserire sotto la riga tx.serialize()
// dopo ... tx.serialize();
var scriptIn = bitcore.Script(tx.toObject().inputs[0].script);
console.log('input scripting: ', scriptIn);
var scriptOut = bitcore.Script(tx.toObject().outputs[0].script);
console.log('\noutput scripting 0 : ', scriptOut);
var scriptOut = bitcore.Script(tx.toObject().outputs[1].script);
console.log('output scripting 1 : ', scriptOut);
eseguendo il codice vedremo un output simile a questo
buf rappresenta un dato mentre opcodenum una “operazione”, un comando come descritto in questa pagina https://en.bitcoin.it/wiki/Script ad esempio 118 e’ OP_DUP
Inviamo la transazione al network
Ora siamo pronti ad inviare la nostra transazione sul network Bitcoin Test
Basta andare su questa pagina https://live.blockcypher.com/btc-testnet/pushtx/ e incollare la nostra transazione raw
vedremo la nostra transazione che e’ in attesa di entrare in un blocco
Possiamo notare che stiamo spostando 80.000 satoshi. 30.000 il pagamento 50.000 il resto Per esaurire completamente il nostro utxo di 100.000 satoshi in questa transazione avanzeranno 20.000 satoshi, che il miner che includerà la nostra transazione nel blocco prenderà come fee.
Di nuovo bastera’ aspettare dal 20 ai 30 minuti per vedere la nostra transazione confermata e finalizzata.