giovedì 20 febbraio 2014

Bitcoin: come funziona il protocollo (Parte 3)

Premessa

Questo post è la parte 3 della traduzione del post di Michael Nielsen sul funzionamento del protocollo alla base di Bitcoin. Le puntate precedenti le trovi a questi links: parte 1, parte 2.

Proof-of-work (1/2)

Supponiamo che Alice voglia attuare una truffa e spendere doppiamente il suo infocoin nel protocollo appena descritto. Potrebbe fare ciò "impossessandosi" della rete Infocoin. Ad esempio, supponiamo che Alice abbia messo in piedi un sistema automatizzato che le abbia consentito di creare un gran numero, diciamo un milione, di differenti identità nella rete Infocoin. Come prima, ella prova a spendere doppiamente il suo infocoin con Bob e Charlie. Ora, quando Bob e Charlie chiederanno alla rete di validare le loro transazioni con Alice, accadrà che la rete sarà composta da numerosissime false identità con a capo Alice. Queste, sotto il comando di Alice, risponderanno ovviamente sia a Bob che a Charlie che le loro transazioni sono valide e non fraudolente, ingannando così una o entrambe le vittime.

Fortunatamente esiste un modo ingegnoso di evitare questo problema. Tale metodo si basa su una tecnica conosciuta come proof-of-work. Quest'idea può sembrare controintuitiva e coinvolge una combinazione di altre due idee: (1) rendere (artificialmente) computazionalmente costoso per gli utenti della rete validare le transazioni; e (2) premiare gli utenti che aiutano il processo di validazione. Il premio è usato per incentivare gli utenti della rete allo svolgere l'attività di validazione delle transazioni, nonostante sia ora diventato un processo computazionalmente costoso. Il beneficio ottenuto nel rendere costoso il processo di validazione delle transazioni è che tale validazione non sarà più influenzata dal numero di identità nella rete che qualcuno controlla, bensì dal potere computazionale offerto. Come vedremo, usando un design ingegnoso, si può far si che un eventuale truffatore avrebbe bisogno di un enorme potere computazionale per mettere in pratica la sua truffa, rendendo il tutto impraticabile nella realtà.

Questa è l'essenza della tecnica del proof-of-work. Tuttavia, per meglio comprendere tale tecnica, abbiamo bisogno di esaminare qualche dettaglio.

Ipotizziamo che Alice trasmetta in broadcast all'intera rete il messaggio "Io, Alice, do a Bon un infocoin, con numero seriale 1234567".

Appena un utente della rete riceve questo messaggio, lo aggiunge ad una propria coda di transazioni pendenti, cioè transazioni di cui ha ricevuto notifica ma che non sono ancora state validate dalla rete. Ad esempio, un altro utente della rete chiamato David potrebbe avere la seguente lista/coda di transazioni pendenti:

"Io, Tom, do a Sue un infocoin, con numero seriale 1201174"
"Io, Sydney, do a Cynthia un infocoin, con numero seriale 1295618"
"Io, Alice, do a Bob un infocoin, con numero seriale 1234567"

David, usando la propria copia della block chain, può verificare che ogni transazione è valida. Quindi, egli trasmetterà in broadcast la notizia della validità di queste transazioni.

Tuttavia, prima di far ciò, come parte del protocollo di validazione, a David è richiesto di risolvere un difficile rompicapo/problema computazionale (la proof-of-work). Senza la soluzione a questo problema (che sarà quindi trasmessa insieme al messaggio di accettazione delle transazioni), il resto della rete non accetterà il suo messaggio di validazione.

Quale problema far risolvere David? Sia h una funzione hash fissa e conosciuta da tutta la rete (è parte del protocollo). Bitcoin usa la ben conosciuta SHA-256, ma ogni funzione hash crittograficamente sicura fa al caso. Assegniamo l'etichetta l alla lista delle transazioni pendenti di David (giusto per avere un nome a cui riferirci). Supponiamo che David appena in coda a l un numero x (chiamato nonce) e che valuti la funzione hash su tale combinazione. Ad esempio, se l="Hello, world!" (ovviamente questa non è una lista di transazioni, ma solamente una stringa usata a scopi illustrativi) e x=0, allora l'output esadecimale sarebbe:

h("Hello, world!0") = 1312af178c253f84028d480a6adc1e25e81caa44c749ec81976192e2ec934c64

Il problema che David deve risolvere (la proof-of-work) è quindi quello di trovare un numero/nonce x tale che quando si appende x ad l e si valuta la funzione hash di questa combinazione, l'hash ottenuto inizi con una lunga sequenza di zeri. Il problema può essere reso più o meno difficile variando il numero di zeri richiesti nella soluzione. Una proof-of-work relativamente semplice potrebbe richiedere giusto 3 o 4 zeri all'inizio dell'hash, mentre un problema più difficile potrebbe richiederne 15. In ogni caso, l'esempio precedente con x=0 non rappresenta una soluzione alla proof-of-work dato che l'hash ottenuto non inizia con nessun zero. Provando x=1 non funziona lo stesso, infatti:

h("Hello, world!1") = e9afc424b79e4f6ab42d99c81156d3a17228d6e1eef4139be78e948a9332a7d8

Possiamo provare vari valori di nonce, x=2,3,.... Alla fine, al valore x=4250 otteniamo:

h("Hello, world!4250") = 0000c3af42fc31103f1fdc0151fa747ff87349a4714df7cc52ea464e12dcd4e9

Quindi, tale nonce (4250), ci fornisce una stringa di 4 zeri all'inizio dell'hash. Questo è sufficiente per risolvere una semplice proof-of-work, ma non sufficiente a risolverne una difficile (con molti zeri iniziali richiesti).

Ciò che rende questo problema difficile da risolvere è il fatto che l'output di una funzione hash crittografica si comporta come un numero casuale: modificando l'input anche di pochissimo si ottiene un output completamente modificato, così da rendere (praticamente) impossibile ogni previsione. Così, se vogliamo un valore hash che inizi con 10 zeri, allora David avrà bisogno, in media, di provare 16^10 (circa 10^12, cioè circa mille miliardi!) differenti valori per x prima di trovarne uno adatto. E' per questo che tale problema richiede molta potenza computazionale.

Ovviamente, è possibile rendere questo problema più o meno difficile richiedendo più o meno zeri iniziali nell'output della funzione hash. In pratica, il protocollo Bitcoin possiede un livello di controllo sulla difficoltà del problema proof-of-work usando una leggera variante del metodo appena descritto. Invece di richiedere degli zeri iniziali, il proof-of-work usato da Bitcoin richiede che l'hash dell'header di un blocco (della block chain) sia minore o uguale ad un numero noto come target. Tale numero è automaticamente aggiustato per assicurare che un blocco Bitcoin richieda in media 10 minuti per essere validato (si deve aggiustare perché per la legge di Moore la potenza computazionale di un utente medio aumenta negli anni).

(Nella pratica c'è della variabilità/varianza del tempo richiesto per validare un blocco. A volte un nuovo blocco è validato in 1 o 2 minuti, altre volte potrebbero essere necessari 20 o più minuti. E' tuttavia semplice modificare il protocollo Bitcoin di modo da avere un tempo di validazione con meno varianza intorno ai 10 minuti. Invece di risolvere un singolo proof-of-work si potrebbe richiederne di risolverne di più (con difficoltà più piccola) e con un po' di attento design è possibile ridurre considerevolmente la varianza del tempo di validazione di un blocco di transazioni).

... continua in un post futuro ...

1 commento:

  1. http://www.economist.com/news/briefing/21677228-technology-behind-bitcoin-lets-people-who-do-not-know-or-trust-each-other-build-dependable?frsc=dg%7Ca

    RispondiElimina