Transazione Bitcoin in codice

Transazione Bitcoin in codice

Di Giancarlo Valente


bitcoin tutorial

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 e’ 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 utilizzate una vostra 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.

Potete 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.

Impara a programmare in 3 mesi con il Corso di Coding Hackademy su Laravel PHP

Diventa Sviluppatore web in 3 mesi

Scopri il coding bootcamp Hackademy

Programma Completo