Salta al contenuto principale
  1. Articoli/

Il mondo ha bisogno di Lightning Network

·66 minuti

Facciamo una promessa: proverò a spiegarti cos’è Lightning Network senza entrare (troppo) nel dettaglio, ma tu dovrai concedermi un’ora del tuo tempo. Sarà un viaggio lungo e complesso (ma c’è anche la gioia).

Se vuoi supportare il mio lavoro, puoi visitare la pagina delle donazioni. Ogni contributo, grande o piccolo che sia, mi aiuta a dedicare più tempo alla stesura, revisione e aggiornamento di questi articoli. Grazie per il supporto!

Il priorato è benevolo

Achtung 1: non scrivo da tanto. L’ultima volta che ho modificato un articolo è stato il 4 marzo 2023. Nel frattempo ho iniziato una nuova carriera come software engineer mentre ricomponevo il puzzle della mia vita. Quando mi sento in equilibrio torno qui, a scrivere. Mi piace.

Achtung 2: questo articolo è il frutto di una pesante rielaborazione di Mastering the Lightning Network del buon Antonopoulos.

Achtung 2: per leggere questo articolo senza grosse difficoltà suggerisco di leggere o rileggere Bitcoin 101. Potresti incontrare termini che non spiegherò, alcune volte perché li ritengo propedeutici a questa lettura, altre perché non posso entrare nell’estremo dettaglio di ogni concetto.

Si parte sempre dalle basi #

Lightning Network (come Bitcoin) è un sistema trustless, permette lo scambio di valore senza la necessità di doversi fidarsi degli altri partecipanti di questa rete.

Ma questo sistema, com’è fatto? Dove si trova? Come funziona?

Supponiamo che io sia un gestore di una pizzeria. Ogni due giorni, il mio amico Kris viene a comprare una pizza. A Kris piace pagare in bitcoin ma usare la timechain per una spesa rapida è faticoso. Non scala abbastanza, quindi il pagamento non è rapido e ci sono le fees.

Io e Kris ci mettiamo d’accordo e stabiliamo un canale di comunicazione immaginario nel quale entrambi depositiamo qualche soldino (satoshi) all’interno di una cassaforte. Nella cassaforte è presente un foglietto nel quale c’è scritto esattamente quanti satoshi abbiamo depositato io e Kris. Questo gli permette di poter ordinare tutte le pizze che vuole finché la sua parte di satoshi non viene erosa completamente.

Quello che succede al foglietto è che ogni volta che Kris acquista una pizza, viene aggiornato, togliendo un pò di sats a lui e aggiungendoli al mio nome. Adesso che io e Kris abbiamo stabilito un canale di comunicazione, qualsiasi amico di Kris che ha un canale diretto con lui può sfruttare il nostro nuovo collegamento.

L’obiettivo di Lightning è dunque permettere un routing di pagamento efficiente sfruttando i canali già esistenti e cercando il percorso minimo per completarlo.

Il routing è il processo di ricerca del percorso migliore in qualsiasi sistema di trasporto (che sia fisico o digitale).

Calato nella realtà: quando usi le mappe sul tuo smartphone cer cercare il percorso migliore, stai effettivamente facendo routing.

L’esempio della pizza è molto banale e non è assolutamente completo o realistico rispetto a come davvero funziona Lightning ma è un buon esempio di partenza per poterci addentrare nell’argomento.

Fairness protocol #

Un protocollo fairness è per definizione un sistema in cui c’è equità tra i partecipanti.

Ma come è possibile ambire all’equità se non richiedendo fiducia, imponendo ad esempio uno stato di Diritto o assumendo la presenza di terze parti fidate?

Domanda stupida: ci ho scritto un intero articolo: grazie alla teoria dei giochi e alla crittografia!

Nei sistemi crittografici riponiamo fiducia nel protocollo. Un protocollo non è nient’altro che un sistema con un insieme di regole e grazie agli incentivi o disincentivi presenti nella teoria del giochi potremmo ambire ad un protocollo affidabile senza le necessità di intermediari. Ma l’equità la raggiungiamo solo se le regole sono ben scritte.

Facciamo un esempio per fare chiarezza e trasportare questa definizione nel mondo reale.

Durante l’ora di pranzo, due fratellini litigano perché non vogliono condividere un piatto di patatine fritte. Classico. La mamma, prendendo in mano la situazione è con fare autoritario impone la sua legge dividendo i piatti di entrambi i fratellini a suo giudizio. Potrebbe dare, volontariamente o meno, più patatine ad uno dei due. Risultato? Il sistema non è equo, abbiamo una forma di Stato di Diritto che prende una decisione in maniera totalmente autonoma.

Marmocchi che frignano

Un metodo più intelligente potrebbe essere quello di stabilire delle regole, come ad esempio: uno dei due fratellini divide i piatti ma, l’altro, può scegliere per primo quale piatto mangiare.

Che effetti porta questa nuova dinamica tra i due? Abbiamo introdotto degli incentivi e dei disincentivi perché se il fratellino che ha il compito di dividere i piatti prova ad imbrogliare, suo fratello può punirlo scegliendo il piatto più grande.

Primitive di sicurezza #

L’esempio di questi due fratellini per funzionare deve basarsi su alcune primitive indispensabili come l’ordinamento sequenziale delle azioni e il non ripudio intenzionale.

  • La scelta del piatto da parte di uno dei due non può avvenire prima del porzionamento.
  • Entrambi devono impegnarsi a non non ripudiare la scelta di un piatto.

Perché Lightning? #

Come già accennato nell’esempio della pizzeria, Bitcoin non scala bene. Ma non è un problema, è una feature. Bitcoin sul layer 1 non deve scalare e non dovrà farlo.

Ricordo che le transazioni sono registrate sulla timechain a livello globale e all’aumentare della domanda di transazioni un blocco si satura molto velocemente. Nel frattempo, tutte le altre, vengono messe in attesa. Per scavalcare la coda e uscire dall’attesa è possibile pagare una fees maggiore, così facendo fees salgono per tutti indistintamente.

Se la domanda di poter eseguire transazioni continua ad aumentare, sempre più transazioni restano in attesa rendendo le micro transazioni non economiche perché si spenderebbero più satoshi di fees che di effettivo importo della transazione.

Per risolvere questo “problema” –che non è tale– qualche “genio” ha pensato bene di aumentare la dimensione del blocco, ma.. ne abbiamo già parlato in Bitcoin 101. Corri a rileggere la block-size war.

Come potremmo permettere delle transazioni scalabili, off-chain, senza perdere la sicurezza del layer 1 di Bitcoin?

Nel febbraio 2015, Joseph Poon e Thaddeus Dryja hanno presentato una possibile soluzione a questo quesito, pubblicando il whitepaper –ormai obsoleto– The Bitcoin Lightning Network: Scalable Off-Chain Instant Payments.

Il concetto di Lightning Network prevede la nascita di una nuova rete, tecnicamente chiamata di secondo livello (layer 2), all’interno della quale gli utenti possono pagare in modalità peer-to-peer senza dover registrare una transazione sulla timechain. L’unico momento in cui si fa utilizzo della timechain è per aprire un canale di comunicazione e per fare il settlement di questo canale, cioè l’accordo per tirar fuori i bitcoin da Lightning e riportarli sul layer 1.

Oltre ad una naturale riduzione di carico sui nodi Bitcoin, con i pagamenti Lightning le fees di transazione sarebbero bassissime e con una privacy maggiore (in parte, lo vedremo) perché questi pagamenti non sono visibili a tutto il layer 1 come le transazioni standard.

Caratteristiche messe in evidenza dal whitepaper #

Premetto che analizzeremo con calma ogni punto nei prossimi capitoli, per ora potrebbe sembrare un elenco fumoso ma mi è necessario per introdurre i concetti e poi descriverli.

  • Routing di pagamenti a bassissimo costo e in tempo reale.
  • Scambio di valore senza attendere la canonica conferma del blocco come su L1.
  • I pagamenti sono definitivi, non invertibili e rimborsabili solo dal destinatario.
  • I pagamenti non sono visibili a tutta la rete.
  • I pagamenti non vengono archiviati in modo permanente come su L1.
  • Lightning utilizza il concetto di onion routing, in modo che i nodi intermedi coinvolti nel trasporto di un pagamento conoscano solo il nodo predecessore e quello successivo. Non conoscono mittente e destinatario.
  • I bitcoin utilizzati su Lightning sono bitcoin reali, questa caratteristica prevede la custodia del valore e il pieno controllo del saldo come nei bitcoin presenti on-chain.

Come funziona Lightning #

Da qui in avanti smetteremo di giocare: se sei stanco, vai a preparare un caffè perché stai per scoprire le meraviglie della rete folgore (questa intro è il bonus per chi leggerà l’articolo in italiano).

Prerequisiti tecnici di base #

  • Firme digitale: è un metodo teso a dimostrare l’autenticità di un documento digitale.

Spieghiamo con un esempio: hai bisogno di scrivere in segreto ad uno tuo caro amico e vuoi essere certo che lui sappia che sei davvero tu l’autore di questa lettera. Hai una chiave magica che permette in modo matematico di firmare la lettera, dando la prova che solo tu puoi averla scritta. Grazie all’uso di questa chiave magica stai creando una firma digitale.

Il tuo amico ha un’altra chiave magica che deriva dalla tua, ma può utilizzarla esclusivamente per verificare la tua firma. Non può creare una firma a tuo nome.

Questa è una firma digitale.

La chiave “magica” non è nient’altro che una chiave privata che ti permette di spendere i fondi, dimostrando all’intera rete che tu sei il proprietario in quell’esatto momento.
  • Chiave digitale: è composta da un insieme di numeri che possono essere utilizzati sia per cifrare che per decifrare informazioni.

Spieghiamo con un esempio: hai una chiave speciale e unica che può aprire solo una cassaforte particolare. Questa chiave digitale è molto potente perché ti permette anche di firmare documenti o accedere a risorse protette. Solo tu dovresti possederla e, quando la usi, tutti gli altri sanno che sei davvero chi dici di essere.

È composta da due parti, una pubblica e una privata. Quella pubblica permette di verificare che tu sei tu, ma non dice nulla su come usarla e non funziona per aprire la cassaforte. Quella privata invece è il segreto che devi custodire.

  • Hash: è una funzione matematica che trasforma dati di dimensioni variabili in una stringa di lunghezza fissa.

Spieghiamo con un esempio: immagina di dover trasformare una ricetta di cucina che ha delle dosi precisissime in un numero unico al mondo. Esiste una funzione matematica che trasforma “qualcosa” in numero, assegnando un’impronta digitale.

Cambiando anche solo un grammo di un ingrediente della ricetta e riapplicando la funzione di hash il risultato finale cambia drasticamente, il numero che otterrai sarà totalmente differente. Se qualcuno modificasse la tua ricetta te ne accorgeresti immediatamente che non è più la stessa di prima. Sintetizzando è un modo per garantire l’integrità e la sicurezza nel mondo digitale.

Per i più pragmatici, un hash si può calcolare da terminale in questo mondo

$ echo -n "musclesatz" | shasum -a 256 

Darebbe come risultato:
fb4584b61ffaee257347cbff270e3c0bc9c504317685b68a72c1a47c400984f3
  • Transazione Bitcoin: è una struttura dati che codifica il trasferimento di valore tra i vari partecipanti al network.

Una transazione che spende input, crea degli output. Gli input delle transazioni è come se fossero i riferimenti agli output di transazioni precedenti e ogni transazione genera a sua volta nuovi output.

Ez

I nodi Bitcoin tengono traccia di tutti questi output disponibili e spendibili. Proprio per questo motivo vengono chiamati unspent transaction output o per abbreviare UTXO. La raccolta di tutti gli UTXO costituisce l’UTXO set e questo insieme cresce o si riduce man mano che vengono creati o consumati nuovi UTXO.

Gli output prodotti sono unità di valore discrete ed indivisibili, questo implica che un output non speso deve essere consumato nella sua interezza.

Quindi mi stai dicendo che se devo pagare 10k satoshi ma ho un UTXO da 3 bitcoin spendo tutto l’importo?

Sì.

La buona notizia è che pagherai l’intero UTXO come input, ma otterrai due output:

  • uno che paga i 10k satoshi
  • l’altro che ti restituisce la differenza come resto.

È brutto da dire, ma funziona esattamente come quando spendi moneta fiat. Se devi pagare 0,10 centesimi per una goleador ma hai una banconota da 10 euro, dovrai spenderla nella sua interezza.

L’unica transazione che non ha input è la transazione coinbase, una transazione speciale utilizzata dai miner per tenere in piedi l’intero processo di mining.

Ogni transazione ha poi un identificatore chiamato ID di transazione, o più brevemente TxID. Questo ID viene prodotto da una funzione di hash sui dati della transazione. Invece, per identificare un output specifico di una TxID si usano gli outpoint, semplici numeri apposti alla fine di un TxID e preceduti dai due punti (:) per fissare a quale input ci stiamo riferendo.

  • Bitcoin Script: conclude questa carrellata di definizioni ed è il linguaggio di scripting utilizzato in Bitcoin per definire le condizioni di rilascio dei fondi in una transazione. Detto in altri termini, determina le regole che devono essere soddisfatte affinché i fondi in una transazione possano essere spesi.

Bitcoin Script è composto da due parti: - Script di blocco: sono incorporati negli output delle transazioni e stabiliscono le condizioni necessarie per spendere un output. - Script di sblocco: sono incorporati negli input, soddisfacendo le condizioni stabilite dallo script di blocco.

Per semplificare con un esempio, se tu avessi un output bloccato da uno script di blocco che dice:

3 + x = 5

È facile intuire che possiamo spendere usando lo script di sblocco 2 in un input di transazione.

Chiunque convalidi questa transazione concatenerebbe il nostro script di sblocco (2) allo script di blocco (3 + x = 5) con una risposta affermativa, permettendo di spendere l’output. Naturalmente l’aritmetica di base non è contemplata in questi script, nella pratica si richiede la dimostrazione della conoscenza di un segreto..ed ecco che torniamo al concetto di chiave digitale.

Ora che abbiamo analizzato gli script di blocco e sblocco, rifacciamo l’esempio della pizza cercando di avvicinarci al funzionamento reale:

  1. Kris paga a musclesatz 10k bitcoin per acquistare una pizza.
  2. Lo script di blocco più semplice, richiede una firma di musclesatz per sbloccare i fondi.
  3. Lo script è qualcosa del genere <signature> <pubkey> CHECKSIG.

CHECKSIG prende due elementi, una firma e una chiave pubblica e verifica che sia la mia. La chiave pubblica è già nello script di blocco, quello che manca è la firma di musclesatz corrispondente a quella chiave pubblica. Solo io possiedo (o dovrei possedere) la chiave privata. Solo io posso generare una firma valida tale da permettermi di spendere quei satoshi.

Devo fornire uno script di sblocco contenente la mia firma digitale. Il risultato di questa operazione sarà TRUE!

<musclesatz signature> <musclesatz pubkey> CHECKSIG

Ci sono ovviamente altri tipi di script, molto più complessi. Qui un po di esempi.

Cos’è un canale di pagamento? #

Lightning Network è una rete peer-to-peer di canali di pagamento implementati come degli smart contract (non pensar male) sulla timechain di Bitcoin. Ma questa definizione sarebbe riduttiva perché è anche un protocollo di comunicazione che definisce il modo in cui i partecipanti a questa rete eseguono questi smart contract.

Un canale di pagamento è una relazione tra due nodi su Lightning. Questa relazione permette di definire un saldo (in millisatoshi) tra questi due nodi Lightning.

Un nodo Lightning è un software in grado di parlare il protocollo LN. Hanno tre caratteristiche di base:

  • Sono wallet che inviano e ricevono pagamenti su rete Lightning.
  • Devono comunicare in modo peer-to-peer con altri nodi.
  • Devono poter accedere alla timechain per proteggere i fondi usati per i pagamenti.

Il canale di comunicazione è protetto da un protocollo crittografico che garantisce l’equità facendo uso della crittografia e rendendolo di fatto un sistema fairness. Il protocollo in questione è stabilito quando entrambi i partecipanti contribuiscono al fondo comune in un indirizzo multisig 2-di-2. Contribuire ad un address 2-di-2 implica che entrambe le parti devono essere d’accordo.

In un indirizzo multisig 2-di-2 ogni partecipante detiene una chiave privata. Ciò significa che nessuno dei due partecipanti può autorizzare una transazione in autonomia; entrambe le firme devono essere presenti e verificate per garantire che la transazione sia eseguita correttamente.

Io e Kris negoziamo una serie di transazioni private che spendono questo saldo, ma senza mai pubblicarle sulla timechain. L’ultima transazione delle varie sequenze che ci sono state nel nostro canale rappresenta lo stato attuale del canale e definisce come questo saldo è suddiviso tra me e Kris.

Fare una transazione Lightning equivale a spostare una parte del saldo verso di me se sono pagato, verso Kris se devo pagarlo.

Perfettamente bilanciato.

Ogni spostamento di fondi da una parte o dall’altra è gestito da uno smart contract impostato per penalizzare un membro del canale nel caso in cui provasse ad inviare uno stato vecchio, che appartiene al passato, dunque non più valido.

Inoltro dei pagamenti #

Quando più partecipanti al network dispongono di più canali di pagamento, questi ultimi possono essere inoltrati (ricordi il routing?) da un canale all’altro, definendo di fatto un percorso spaziale nella rete che collega più canali.

Abbiamo detto che i canali sono costruiti a partire da indirizzi multisig; quello che non abbiamo detto è che le transazioni di aggiornamento del saldo del canale sono transazioni Bitcoin pre firmate. Questo implica che la fiducia necessaria per far funzionare LN proviene dalla fiducia della rete decentralizzata per eccellenza: il layer 1, Bitcoin.

Dove voglio arrivare?

Lightning è un’applicazione al di sopra di Bitcoin che fa uso delle transazioni Bitcoin e del suo linguaggio di scripting. È un modo creativo e furbo per consentire una quantità arbitraria di pagamenti istantanei a fees molto basse, senza la necessità di doversi fidare di qualcuno, tranne che di Bitcoin stesso.

Dijkstra non è divertito

Tornando al discorso dei canali di pagamento, parliamo dei possibili limiti:

  • Il tempo impiegato da internet per trasferire qualche centinaio di byte (trascurabile).
  • La capacità del canale, cioè la quantità di bitcoin impegnata all’apertura del canale stesso.
  • Il limite massimo della dimensione di una transazione Bitcoin: dal momento che ogni pagamento Lightning è supportato da una transazione Bitcoin che potrebbe essere ancora in corso, la dimensione del blocco influisce sulla quantità di pagamenti che possono essere attivi simultaneamente su un singolo canale di pagamento.

la transazione di funding #

L’elemento fondante di un canale abbiamo detto che è un address multisig 2-di-2. Uno dei peer coinvolti nell’apertura del canale di pagamento può finanziarlo inviando dei satoshi all’indirizzo multisig. Questa transazione prende il nome di funding transaction e non è possibile distinguerla sulla timechain rispetto ad una qualsiasi altra transazione. Si può scoprire che si trattava di un canale Lightning solo al momento del settlement della transazione, cioè alla chiusura del canale.

L’importo depositato con la funding transaction viene denominato capacità del canale e definisce l’importo massimo inviabile su quel canale finanziato.

Achtung. La capacità del canale non definisce il limite massimo di quanto valore può fluire nel canale, perché ti ricordo che i fondi possono inviati in una direzione ma anche nella direzione opposta.

La transazione di refund #

Supponiamo che io adesso crei un canale di pagamento con Kris. Kris però è dispettoso. Mi ha fatto depositare dei fondi in un address multisig 2-di-2 ma adesso si rifiuta di collaborare con me e di firmare la sua transazione.

Come sblocco i fondi?

Per evitare questi impicci ho bisogno di creare in anticipo una refund transaction che spenda dall’indirizzo multisig rimborsando i miei satoshi. Devo far firmare questa refund transaction prima di trasmettere la funding transaction all’address, cioè prima di finanziare il canale.

Ora sono protetto!

La refund transaction è una delle transazioni che appartengono alla classe delle commitment transaction.

La transazione di commitment #

Questa transazione è un accordo tra i peer del canale che paga a ciascun peer il proprio saldo, garantendo che i due non debbano fidarsi l’uno dell’altro.

Firmando la transazione di commitment ci si impegna per il saldo presente in questo esatto momento sul canale. Se volessi recuperare i miei fondi dal canale, posso farlo in qualsiasi momento, proprio grazie alla stipula di questo contratto.

Tra l’altro, ogni volta che il saldo del canale “cambia”, vengono create queste commitment transaction, aggiornando il nuovo stato del canale e dividendo il saldo tra quanto spetta a me e quanto spetta al mio peer.

Se il mio partner di canale scompare? Nessun problema. Se il mio partner di canale si rifiuta di collaborare? Nessun problema. Se il mio partner cerca di fregarmi? Nessun problema.

Per ora la risposta “nessun problema” può apparire fumosa e priva di significato, successivamente analizzeremo puntualmente quello che ho affermato.

Si può barare? #

Torniamo al mio amico Kris, apro un canale con lui depositando 100k satoshi in un address multisig 2-di-2. Ci scambiamo le firme e trasmetto la transazione sulla timechain.

Abbiamo detto che le commitment transaction vengono create anche ogni volta che il saldo del canale cambia, quindi se ipotizziamo che io spedisca 30k a Kris, nella nuova transazione di commitment ci sarà scritto che l’address paga a 70k sats a me e 30k sats a Kris.

Ma adesso io ho due transazioni di commitment, la prima che definiva lo stato iniziale al tempo t0 da 100k sats e la seconda al tempo t1 –che rappresenta lo stato attuale– da 70k sats.

Mi viene in mente un’idea malsana: ma se pubblicassi la mia precedente transazione di commitment da 100k sats vorrebbe dire che adesso l’address mi pagherebbe 100k satoshi?

Sorry, Kris.

Bitcoin è resistente alla censura e nulla mi vieta di pubblicare un vecchio stato non più valido. Nulla tranne la crittografia, ovviamente.

Per prevenire questo tipo di furti, le transazioni di commitment sono realizzate in modo che se ne viene trasmessa una vecchia, volontariamente o meno, posso essere punito.

Questo è il disincentivo della teoria dei giochi, proprio come nell’esempio dei fratellini con il piatto di patatine fritte.

Il mio disincentivo a barare è altissimo, perché nel momento in cui io pubblico un vecchio stato del canale, Kris può bastonarmi e ha l’opportunità di reclamare l’intero saldo depositato dell’address.

Se avessi pochissimi satoshi sarei più incentivato a barare perché il mio disincentivo è bassissimo, posso perdere qualche satoshi al massimo. Non sarebbe doloroso. Per risolvere questo problema LN richiede a ciascun partecipante un saldo minimo nel canale chiamato skin in the game.

Analizzeremo successivamente come funziona la penalità perché dovrò introdurre i concetti di timelock delay e di revocation secret.

Annunciamo il canale! #

Come faccio ad avvisare l’intera rete della presenza del mio nuovo canale di pagamento? Come lo rendo pubblico?

Esiste un protocollo, chiamato gossip per comunicare ad altri nodi l’esistenza, la capacità e la commissione del mio canale.

Il fattore positivo nell’annunciare un canale è che diventa utilizzabile da altri nodi per eseguire il routing dei pagamenti, generando anche qualche fees di credito nei miei confronti. D’altro canto però un canale non annunciato ha un certo grado di privacy, almeno fino al momento della chiusura del canale sulla timechain.

A proposito di chiusura di canale, secondo te quando è bene chiudere un canale LN?

Ogni volta che faccio un pagamento?

No, tendenzialmente è meglio non chiudere mai i canali.

Chiuderli comporta eseguire una transazione on-chain, fees, rivelazione della presenza di un canale ma sopratutto..non ha tanto senso se non per ragioni specifiche.

Mantenere i canali aperti è ottimo anche perché nel momento in cui dovessi esaurire la capacità di invio, potrò comunque ricevere grazie al ribilanciamento.

Un canale si può chiudere in tre modi differenti:

  • Con una chiusura concordata, che è il modo corretto di farlo:
    • Il mio nodo LN informa il nodo LN del mio peer della mia intenzione di chiusura.
    • Entrambi i nodi lavorano per la chiusura.
    • Non vengono più accettati nuovi tentativi di routing, mentre quelli in corso vengono risolti.
    • I nodi si preparano per la transazione di chiusura: viene codificato l’ultimo stato per definire quando saldo assegnare ad entrambi i peer.
    • Ci si accorda su come dividere le fees di chiusura sulla timechain.
    • Ciascun partner del canale riceve la propria quota di saldo residuo.
  • Con una chiusura forzata, che è il modo scorretto:
    • Tento di chiudere il canale senza il consenso dell’altro partecipante.
    • Pubblico l’ultima transazione di commitment del mio nodo.
    • Dopo la conferma, saranno presenti due output spendibili: uno mio, l’altro del peer.

Qui la questione inizia a farsi più delicata, perché se che ho chiuso forzatamente il canale avrò l’output bloccato da un timelock delay e non potrò spendere i miei bitcoin fino ad un certo istante futuro (solitamente due settimane, misurato in altezza di blocco sulla timechain).

  • Con una violazione di protocollo, modo molto molto scorretto:

Una violazione di protocollo si verifica quando tento di imbrogliare pubblicando una transazione di commitment che rappresenta un vecchio stato del canale. Il mio peer per accorgersi di questo tentativo di furto deve essere online e tenere sotto osservazione i nuovi blocchi sulla timechain e le transazioni che li compongono.

Anche se ho pubblicato un vecchio stato ho un timelock mi impedisce di spendere il saldo quindi per il mio peer c’è tempo necessario per agire e punirmi. Sanzionandomi, il mio peer è in grado prelevare l’intera somma depositata (si nota il disincentivo nel tentare di fregare?) e la chiusura diventa molto molto rapida, perché non c’è nessuna negoziazione di chiusura.

Per finire in bellezza il mio peer vuole che la transazione che mi punisce sia accettata quanto prima in un blocco, quindi è anche disposto a pagare le fees massime su L1: tanto può pagarle con la mia parte di saldo 😁.

Ma se invece il timelock fosse scaduto e non si fosse accorto che ho pubblicato un vecchio stato?

Purtroppo perderà i fondi, totalmente o in base a quanto previsto nell’ultimo stato di commitment che sono riuscito a pubblicare e a portare a termine, superando il timelock delay.

Come si fa ad accorgersi di una eventuale violazione di protocollo ai nostri danni?

Con un nodo Lightning gestito a regola d’arte in esecuzione 24/7 oppure con una watchtower personale o di terze parti.

Una watchtower è un servizio di sicurezza che permette di monitorare il canale quando l’utilizzatore è offline. Se viene rilevata una attività sospetta, può intervenire proteggendo i fondi.

Invoice #

La maggior parte dei pagamenti Lightning inizia da una invoice generata da chi deve riscuotere, il destinatario del pagamento. Questa invoice contiene al suo interno le informazioni essenziali per eseguire un pagamento:

  • L’hash di pagamento.
  • Il destinatario.
  • L’importo effettivo ed eventualmente una descrizione facoltativa.

L’hash di pagamento viene creato dal destinatario del pagamento scegliendo in modo sicuro e non prevedibile un dato pseudorandomico che viene dato in pasto ad una funzione di hash. Questo dato randomico lo chiamiamo Pre-Image.

H = SHA-256(Pre-Image)

Abbiamo un hash di pagamento. Dalle proprietà degli hash sappiamo non è invertibile o forzabile, dunque nessuno può capire qual è la Pre-Image partire dall’hash dal risultato dell’hash. La Pre-Image è un segreto e una volta rivelata, chiunque abbia l’hash può verificare che Pre-Image era effettivamente il segreto.

Il punto è che l’hash di pagamento consente al pagamento di viaggiare su più canali in modo atomico: o percorre tutto il tragitto fino a destinazione o fallisce. Non c’è via di mezzo.

Le invoice vengono generalmente inviate al di fuori di Lightning, usando un qualsiasi meccanismo di comunicazione. Uno molto quotato è codice QR per la sua comodità e compattezza. Nel QR sono presenti tutte le informazioni discusse in alto.

Qualche informazione in più sulle invoice:

  1. Hanno una data di scadenza per evitare al destinatario di conservare tutte le Pre-Image. Quando una invoice è pagata o scade, è possibile eliminarla.
  2. Possono contenere dei routing hints che consentono al mittente di utilizzare canali non annunciati per costruire un percorso verso il destinatario (shadow channel, ne parleremo verso la fine dell’articolo).

Pathfinding, routing #

Questi due termini sono spesso confusi:

  • Il pathfinding consiste nella ricerca del percorso migliore dall’origine alla destinazione.
  • L’utilizzo di questo percorso viene denominato routing.

Lightning utilizza un protocollo source-based per il pathfinding mentre un protocollo onion-routed per il routing.

Il modo standard per cercare un percorso consiste nel testare in modo iterativo vari percorsi finché non ne viene trovato uno con una liquidità sufficiente che permette l’inoltro del pagamenti. Non sarà il metodo che minimizza le fees di routing, ma tutto sommato funzione in modo decente.

Sarebbe fantastico (o forse no, dipende) se avessimo a disposizione i saldi esatti saldi di ogni canale, perché a quel punto il pathfinding sarebbe risolvibile da un qualsiasi studente di un corso universitario di ricerca operativa. Così non è, i saldi non sono e non possono essere noti ai partecipanti dal network.

Passando per un momento al routing, Lightning si è molto ispirato molto alla famosa rete Tor. Il protocollo su non è esattamente come Tor perché ne riutilizza solo il concetto; su Lightning il protocollo di routing si chiama Sphinx e funziona proprio con la stessa analogia della cipolla di Tor: il mittente costruisce l’intera cipolla partendo dal cuore fino allo strato più esterno.

Le informazioni di pagamento per il destinatario vengono crittografate con una chiave che solo il destinatario può decifrare; queste informazioni che sono il nocciolo di tutta l’operazione di routing. Nel capitolo dedicato al routing analizzeremo la costruzione della cipolla step per step, anche da un punto di visto crittografico.

Per dare un’idea generale, il pagamento viene costruito a forma di cipolla partendo dal destinatario e aggiungendo un nuovo strato alla cipolla procedendo a ritroso nel path trovato, quindi dal destinatario al mittente. Il primo strano della cipolla, quello più esterno, sarà proprio il peer del canale del mittente che riceverà il pacchetto onion da inoltrare nella rete.

Quando sarà inviata la cipolla, ogni nodo sarà a conoscenza solo del nodo da cui ha ricevuto la cipolla e il nodo a cui la passerà, senza avere idea di chi siano mittente e destinatario.

Una piccola precisazione: starai pensando che il pacchetto viene realmente sbucciato come una cipolla (una cipolla si sbuccia?).

In realtà ogni nodo quando legge la parte di sua competenza, aggiunge anche un “filler” crittografico per riportare la dimensione del messaggio a quella originale prevista dal mittente (1300 byte). Questo giochetto viene fatto per proteggere la privacy impedendo ai nodi intermedi di dedurre informazioni sulla lunghezza del percorso o sul numero di nodi coinvolti, aumentando la sicurezza complessiva.

Queste piccole cipolle (piccole perché entrano in un singolo pacchetto TCP/IP) sono costruite in modo da avere la stessa lunghezza durante tutto il percorso di routing.

Inoltro della cipolla #

Devo inoltrare la cipolla, sono il mittente del pagamento. Ho finalmente trovato un percorso papabile.

Inoltro il messaggio al mio peer e ci siamo detti che ogni nodo elabora uno strato della cipolla. In pratica ogni nodo riceve un messaggio Lightning chiamato update_add_htlc con un hash di pagamento e con la cipolla. Interviene dunque un algoritmo di inoltro del pagamento che esegue queste operazioni:

  1. Decifra lo strato esterno e controlla l’integrità del messaggio.
  2. Conferma che può soddisfare i suggerimenti di routing in base alle commissioni e alla capacità in uscita.
  3. Aggiorna lo stato sul canale in entrata.
  4. Aggiunge i famosi dati filler per mantenere la lunghezza della cipolla costante.
  5. Esegue a sua volta il routing della cipolla sul suo canale di pagamento in uscita inviando un update_add_htlc che include lo stesso hash di pagamento e la cipolla.
  6. Collabora con il suo peer di canale per aggiornare lo stato del canale.

Ma se c’è un un generico errore in questo processo cosa accade?

Succede che la comunicazione si propaga nel senso opposto tornando verso il mittente con un messaggio di errore update_fail_htlc. Anche ogni nodo impegnato nel routing vede questo messaggio.

Se ti stai facendo domande del tipo: ma cos’è un HTLC? Perché ogni nodo dovrebbe sapere del fallimento e ricevere questo messaggio di errore?

A breve avrai tutte le risposte che cerchi.

(opzionale) se vuoi approfondire la crittografia delle comunicazioni peer-to-peer in Lightning, ti consiglio di visitare sito web del Noise Protocol Framework.

Backup dei canali #

Più o meno tutti conoscono il BIP-39 di Bitcoin, che ci permette di recuperare lo stato di un wallet attraverso una mnemonica. Per chi non ricordasse, la Bitcoin Improvement Proposal 39 consente di generare una sequenza di parole in lingua inglese a partire da una lista pubblica che funge da “seme” di generazione di un wallet deterministico, con una lista pressoché infinita di chiavi pubbliche e private.

Ma in Lightning come si fa un backup?

I wallet Lightning utilizzano anch’essi il backup della mnemonica BIP-39, ma solo per la parte di comunicazione on-chain. Questo è fondamentale capirlo.

È necessario un ulteriore livello di backup, per i canali. Questo backup è chiamato Static Channel Backup (SCB) ed entra in gioco ogni volta che c’è un cambiamento di stato nel canale. Questo dovrebbe farci fare delle domande, perché se per sfortuna viene ripristinata una vecchia transazione di commitment (già revocata), la nostra controparte di canale potrebbe pensare che stiamo tentando di imbrogliarla, punendoci e richiedendo una una transazione di penalità e svuotando l’address.

Un altro aspetto da considerare è che i backup SCB devono essere crittografati per mantenere alto il livello di privacy e sicurezza: se mi sfuggisse un backup non crittografato, chiunque potrebbe utilizzarlo non solo per vedere i miei canali di pagamento, ma anche per chiuderli in modo da consegnare il saldo alla mia controparte.

Sweep #

Come mi comporto se il saldo del mio wallet Lightning diventa troppo grande e voglio ridurre il rischio?

Posso fare uno sweep, di varie tipologie:

on-chainoff-chainsubmarine swap
Sposto i fondi dal wallet LN ad un wallet Bitcoin chiudendo il canale in modo cooperativo, lo abbiamo visto in precedenza.Questa modalità prevede l’esecuzione di un secondo nodo LN non annunciato alla rete. Lo uso come salvadanaio spostando in modo regolare i fondi su questo nodo “nascosto”, che ricordo essere comunque un hot wallet.È uno scambio on-chain versus off-chain. È atomico, vuol dire che se avvio un submarine swap e invio il saldo di un canale LN, l’altra parte in cambio mi invierà bitcoin on-chain.

Submarine swap #

Credo sia il caso di fare un approfondimento sui submarine swap perché genera spesso parecchia confusione:

Premesse:

  • musclesatz ha dei bitcoin on-chain e vuole ricevere fondi su LN (off-chain).
  • Kris ha dei fondi su LN (off-chain) e vuole ricevere bitcoin on-chain.
  • Mark è qualcuno tra musclesatz e Kris che coordina il trade.

  1. Kris genera una Pre-Image (segreto) ed esegue l’hash di questo segreto, incorporandolo in una invoice per me.
  2. Io esegue una transazione on-chain verso un contratto con una clausola che dice che questi bitcoin sono riscattabili fornendo la Pre-Image dell’hash dell’invoice di Kris e rendendo nota una firma valida di Mark, per essere certi che nessuno oltre Mark possa prelevarli.
  3. Mark vede questo contratto e sa che per riscattare questi bitcoin, deve necessariamente pagare Kris, perché solo in quel momento Kris gli rivelerà la Pre-Image.
  4. Kris riceve il pagamento da Mark e quindi gli rivela la Pre-Image.
  5. Mark usa questa Pre-Image e una sua firma valida per muovere i bitcoin in un address che controlla.
  6. Io sono tranquillo che Kris è stato pagato perché Mark ha potuto muovere i fondi nel contratto, quindi conosceva la Pre-Image e quindi gli è stata comunicata da Kris che deve aver ricevuto necessariamente il pagamento.

Se Kris non fosse stato pagato da Mark, Mark non avrebbe potuto fare il claim dei bitcoin nel contratto e grazie ad una clausola particolare di questo contratto (che per inciso si chiamano Hashed Timelock Contract - HTLC, li vedremo nel dettaglio in seguito) dopo un certo intervallo di tempo io avrei potuto riprendere i miei bitcoin presenti in questo contratto, perché c’era anche un timelock in mio favore.

Questa è la sintesi di tutto ciò che ho spiegato in termini di Bitcoin Script.

OP_SIZE 32 OP_EQUAL
OP_IF
OP_HASH160 <ripemd160(swapHash)> OP_EQUALVERIFY
    <receiverHtlcKey>
OP_ELSE
    OP_DROP
    <cltv timeout> OP_CHECKLOCKTIMEVERIFY OP_DROP
    <senderHtlcKey>
OP_ENDIF
OP_CHECKSIG

Un paio di confronti con Bitcoin #

Lightning è costruito su Bitcoin, fin qui tutti d’accordo. Eredità alcune caratteristiche e proprietà, ma ci sono anche alcune differenze importanti:

  • Address e invoice: un address Bitcoin è riutilizzabile (sconsigliato lato privacy) infinite volte. Le invoice LN sono usate one-shot, per un importo specifico. (c’è un eccezione a questo che è meccanismo che sono i keysend che vedremo dopo)

  • Selezione UTXO e pathfinding: per eseguire un pagamento su Bitcoin devo spendere almeno un UTXO, mentre su LN i pagamenti non richiedono il consumo di un “output” perché come abbiamo potuto vedere è un ribilanciamento del saldo presente all’address multisig.

  • Commissioni di mining e commissioni di routing: su Bitcoin paghiamo le fees ai miner per includere la nostra transazione in un blocco, ma su LN gli utenti del network pagano fees ad altri utenti del network per via del routing dei pagamenti attraverso i canali. Questa commissione è composta da una base fee che è una componente fissa pagata per il routing (ogni canale può avere la propria) e da una fee rate è la componente variabile del pagamento, proporzionale valore del pagamento (altra differenza, sulla timechain la commissione non è proporzionale al valore)

  • Transazioni pubbliche Bitcoin e pagamenti privati Lightning: il cuore dell’articolo. La timechain è pubblica, i pagamenti Lightning no.

  • Satoshi e millisatoshi: sulla timechain l’unità più piccola è il satoshi mentre su LN abbiamo anche i millesimi di satoshi. Al settlement del canale Lightning i millisatoshi vengono arrotondati al satoshi intero più vicino.

Software di Lightning network #

Sono partito con l’idea di lasciare questo capitolo vuoto, per non appesantire troppo l’articolo. Se vi interessa sapere nel dettaglio e in maniera tecnica come mettere in piedi un ambiente di sviluppo per eseguire Lightning fatemelo sapere, potete contattarmi su X o su telegram.

Lightning non è un prodotto o un azienda ma un insieme di standard aperti che definiscono una linea comune di interoperabilità. Non c’è una implementazione di riferimento da tenere sott’occhio come per Bitcoin Core ma lo standard è definito attraverso una serie di direttive chiamate Basis of Lightning Technology (BOLT) che potete trovare su GitHub.

Non essendoci consenso come sulla timechain chiunque può costruire al di sopra delle direttive base e se le funzionalità diventano di successo, possono diventare parte integrante di BOLT.

Il cuore di Lightning #

Lightning è composto da un complesso insieme di protocolli eseguiti su internet. Li classificherò in 5 strati differenti in cui ogni strato utilizza (e fa una astrazione) del livello sottostante.

Achtung. Tutte le sezioni di questo capitolo sono state volutamente ammorbidite eliminando dimostrazioni matematiche o dettagli troppo tecnici.

Suite di protocolli di LN

Layer
Network connection: definisce i protocolli che permettono di interagire nella reti
Messaging: definisce i protocolli utili alla formattazione o encoding dei messaggi
Peer-to-Peer: definisce i protocolli di comunicazione tra vari nodi LN
Routing: definisce i protocolli di ricerca del cammino e di routing dei messaggi
Payment: definisce l’interfaccio di pagamento delle invoice

Canali di pagamento #

Riferimento tabella: livello peer-to-peer, channel open & close and channel state machine.

Per capire il concetto dietro al funzionamento dei canali di pagamento è fondamentale porsi una domanda:

Cosa vuol dire possedere dei Bitcoin?

Avere dei Bitcoin significa possedere una chiave privata di un address Bitcoin che ha almeno un UTXO. Questa chiave mi permette di firmare una transazione e legittima il fatto che io sono il proprietario di quel saldo, perché nessun altro la conosce.

Ma la proprietà può anche non essere sempre nelle mani di una singola persona. Bitcoin permette anche gli address multisig in cui c’è bisogno di più chiavi private per firmare una transazione. Uno schema multisig facile da capire è il 2-di-3: significa che sono sufficiente 2 persone su 3 per firmare una transazione e spendere il saldo di quell’indirizzo. Il numerino 2 in questo caso viene denominato quorum.

Ma se in uno schema 2-di-2 una delle parti non collabora?

Non c’è il quorum, per cui i fondi non possono essere spesi. Un sistema di questo tipo non sarebbe considerabile fairness e infatti c’è la possibilità di prevenire questo scenario con la transazione di refund –già analizzata all’inizio dell’articolo–. Bisogna guardare la transazione di refund come fosse un accordo prematrimoniale. Prima di finanziare un indirizzo 2-di-2 mi assicuro di aver un piano di uscita e separare in modo netto i miei fondi da quelli della controparte della controparte.

Niente transazione di refund? Ok.

Torniamo attimo alla costruzione del canale. Quello che succede è che per creare un canale con Kris i nostri due nodi devono stabilire una connessione internet per poter far partire la negoziazione. Ogni nodo è identificato da una chiave pubblica in formato esadecimale, generata a partire da una chiave privata root custodita all’interno del nodo. Ma non basta. Abbiamo anche bisogno di un indirizzo di rete per essere raggiunti e qui abbiamo due scelte: TCP/IP o Tor.

Stiamo quindi definendo un identificatore di nodo che si presenta nella forma ID@Address:Port ma è comunque difficile da leggere. Sarebbe meglio incorporare tutto in un codice QR, no?

Basterebbe scansionarlo e il gioco è fatto, abbiamo i due nodi connessi 😉

Ora che i nodi sono connessi si può iniziare a pensare di costruire il canale di pagamento e questa operazione è frutto dello scambio di sei messaggi (tre per ogni peer) tra i nostri rispettivi nodi:

  • open_channel / accept_channel: invio a Kris le mie capacità e le aspettative che ho con un messaggio di tipo open_channel, se Kris accetterà la mia richiesta risponderà con un accept_channel.
  • funding_created / funding_signed: voglio evitare di subire un imbroglio quindi creo sia la transazione di funding con funding_created, sia quella di refund per proteggermi da possibili imbrogli. Se per Kris va bene, mi risponderà con una funding_signed. Adesso sono tranquillo nel trasmettere la mia transazione di funding (on-chain) per creare e ancorare il canale di pagamento. Non è un’operazione istantanea perché stiamo operando sulla timechain quindi attenderemo le conferma del blocco.
  • funding_locked / funding_locked: non appena la transazione ha le conferme sufficienti (definite nel messaggio iniziale di accept_channel) io e Kris scambieremo un messaggio di funding_locked per iniziare a poter inviare transazioni Lightning.

Durante la costrizione del canale ho fatto una cosa abbastanza insolita: ho costruito due transazioni concatenate. Come ho fatto se la funding transaction non è neanche stata trasmessa sulla timechain?

Grazie ad una caratteristica chiamata Segregated Witness (SegWit) introdotta in Bitcoin nel 2017 sono in grado di riferirmi agli output delle transazioni usando l’hash della transazione, invece dell’ID dell’output. Questo mi consente di concatenare transazione non sono trasmesse.

Si, quello che ho scritto sembra una stronzata ma non è, fammi spiegare.

Quello che intendo dire è che una transazione di refund è valida se è trasmessa sulla timechain e se l’input di questa transazione ha sia la mia firma che quella di Kris. Anche se il mio nodo non ha ancora trasmesso la transazione di funding io nel frattempo posso costruire la transazione di refund calcolando l’hash della transazione di funding e farci riferimento come input nella transazione di refund. Io so in anticipo che il riferimento sarà valido perché ne ho calcolato l’hash.

Adesso il canale è stato impostato, ma tutta la liquidità è dalla mia parte. Significa che io posso inviare satoshi a Kris, ma Kris non non ha fondi da inviarmi. Se inviassi una parte dei miei sats, lo stato del canale cambierebbe ed ecco che compare la transazione di commitment. I saldi del canale vengono aggiornati.

Lo storico di tutte le transazioni di commitment non deve tranne in inganno e far pensare ad una possibile doppia spesa. Solo una di queste transazioni potrà essere confermata sulla timechain. Ci affidiamo alla capacità di Bitcoin di impedire una doppia spesa.

Poniamo il caso che io e Kris iniziamo a transare satoshi e che quindi abbiamo generato tante transazioni di commitment. Siamo arrivati in un punto nel quale abbiamo questo saldo nel canale:

Se volessi chiudere il canale trasmettendo e confermando la transazione di commitment che ho, non posso spendere il saldo per 400 blocchi, mentre Kris può farlo immediatamente. Ovviamente vale anche il viceversa.

Perché c’è questo timelock delay e a cosa serve?

Lo abbiamo accennato in precedenza. Serve per consentire a Kris di esercitare una penalità se nel caso in cui io avessi trasmesso una vecchia transazione di commitment per fregarlo rubandogli dei sats. Questo timelock delay viene negoziato nei messaggi di costruzione del canale.

Ma cosa succede se pubblico un vecchio stato del canale e come posso essere punito?

Ogni volta che lo stato del canale viene aggiornato con un nuovo commitment, ottengo dalla mia controparte un segreto crittografico, chiamavo segreto di revoca, relativo allo stato precedente. Se cerco di fregare Kris, userebbe il segreto di revoca dello stato precedente e avrebbe la prova crittografica che lo “stato” è stato revocato e io sto violando le regole perché sto chiudendo il canale con uno stato non più valido.

Quello che da il potere a Kris di punirmi è il segreto di revoca che gli concede la prova matematica del mio imbroglio.

Ma io non voglio fregarlo e anzi, voglio chiudere il canale in modo cooperativo. Negozio una transazione di commitment finale chiamata shutdown che paga a ciascuna parte il proprio saldo in base allo stato attuale. Specificherò uno script Bitcoin che corrisponde all’indirizzo di chiusura del mio wallet e comunico a Kris di fare una transazione di chiusura che paghi il mio saldo a questo wallet. Kris farà esattamente la stessa cosa e in aggiunta accetterà la chiusura cooperativa. Possiamo finalmente saldare. Invio un ultimo messaggio closing_signed in cui propongo una commissione di transazione per la chiusura on-chain con la mia firma e se Kris sarà d’accordo mi restituirà lo stesso compenso proposto con la propria firma. Se non sarà d’accordo proporrà una fees differente. Questo ciclo di chiusura può andare avanti finché non troviamo una soluzione che ci metta d’accordo entrambi.

Routing sui canali #

Riferimento tabella: livello routing, atomic and trustless multihop contracts.

Per capire il vasto mondo del routing possiamo partire con un esempio calato nella realtà fisica.

Devo spedire 10 monete di un materiale molto raro a Kris, ma non ho un collegamento diretto con lui. Entrambi però conosciamo e abbiamo un collegamento diretto con Mark. Come faccio a convincere Mark a dare 10 monete a Kris senza farmi fregare ed essere certo che non scappi via? E sopratutto, come faccio a capire che le monete siano state consegnate a Kris?

Una possibile soluzione potrebbe essere promettere a Mark 10 monete se può dimostrarmi di aver consegnato 10 monete a Kris.

Ma per quale motivo Mark dovrebbe firmare un contratto del genere, senza trarne un reale vantaggio?

Effettivamente non è molto conveniente, quindi potrei modificarlo ancora promettendo a Mark 11 monete d’oro se è in grado di dimostrarmi che ne ha consegnate 10 a Kris, in questo modo riceverebbe una commissione di una moneta d’oro, non male.

Resta da affrontare però il problema della fiducia, quindi decidiamo di usare un servizio di garanza chiamato escrow.

Kris deve essere pagato, quindi genera un valore segreto R (per semplicità supponiamo che questo valore sia: ciao mondo!) sottoposto ad algoritmo hash SHA-256. L’hash di R la chiameremo payment hash e il segreto che sblocca il pagamento payment Pre-Image.

A questo punto Kris mi invia il payment hash tramite telegram. Io non conosco il segreto, ma intanto posso riscrivere il mio contratto con Mark sfruttando questo payment hash e dicendogli:

Mark, io ti rimborserò con 11 monete se tu puoi mostrarmi un messaggio valido (una Pre-Image) che corrisponde a questo payment hash. Puoi acquisire questo messaggio stabilendo un contratto con Kris. Per assicurarti che sarai rimborsato bloccherò queste monete in un escrow prima che tu stabilisca il contratto con Kris.

Questo contratto protegge le mie monete perché intanto le sto bloccando in un escrow, ma la cosa fondamentale è che io pagherò Mark se mi mostrerà una Pre-Image valida per il payment hash. La Pre-Image è la prova che Kris è stato pagato da Mark.

A questo punto Mark fa un contratto del tutto identico con Kris, dicendo che lo rimborserà di 10 monete se Kris può far vedere un messaggio valido che corrisponde al payment hash. Anche lui avvisa che sarà rimborsato dopo aver rivelato il segreto, mettendo le 10 monete in un escrow.

(Ma ricordiamoci che Kris è il destinatario ed è lui ad aver generato la Pre-Image, quindi può mostrarla a Mark, venendo effettivamente pagato).

Tutte le parti hanno un contratto.

Kris invia la Pre-Image a Mark; lui controlla che il segreto corrisponda al payment hash e avendo la conferma ordina all’escrow di rilasciare 10 monete per Kris. Ora Mark fornisce la Pre-Image a me, lo controllo e ordino al servizio escrow di rilasciare 11 monete per Mark.

Fine, tutti i contratti sono stati risolti. Ho pagato un totale di 11 monete, 1 delle quali è stata ricevuta da Mark come fees e le altre 10 sono arrivate al mio destinatario Kris.

Perché funziona?

Funziona perché con una catena di contratti del genere Mark non poteva scappare avendo le monete bloccate nell’escrow, li ha depositati a garanzia.

Qual è il punto debole?

Se Kris decidesse di non rilasciare la sua Pre-Image, io e Mark avremmo avuto le monete bloccate nell’escrow. Ma questo problema è facilmente risolvibile applicando una scadenza al contratto, un timelock delay.

Le transazioni Lightning sono atomiche, o vanno a buon fine o vanno KO!

Hash Time-Locked Contracts (HTLC) #

Il protocollo di fairness per l’instradamento usato su Lightning è chiamato hash time-locked contract (HTLC) ed è quello che abbiamo appena descritto nell’esempio.

Gli HTLC usano l’hash di una Pre-Image di un pagamento come segreto che sblocca un pagamento. Esiste anche un altro meccanismo per il routing chiamato point time-locked contract (PTLC) ancora più efficiente e con migliore privacy in quando dipende direttamente da un nuovo algoritmo aggiunto nel 2021 in Bitcoin chiamato firme di Schnorr. Qui qualche informazione in più.

Dopo aver letto e capito l’esempio di HTLC, vorrei farti notare un piccolo problema: ognuno di quei contratti può essere sbloccato da chiunque conosca la Pre-Image. Che succede se Kris spende sia l’HTLC di Mark che il mio? Non sarebbe per nulla un sistema trustless se così fosse.

Lo script HTLC deve avere una condizione aggiuntiva che associ ogni HTLC ad un destinatario specifico: lo facciamo richiedendo una firma digitale che corrisponda alla chiave pubblica di ciascun destinatario, impedendo a chiunque altro di spendere quell’HTLC.

Altro piccolo problema da affrontare:

Cosa succede se qualche nodo va offline o non collabora? Come faccio a far fallire gentilmente il pagamento?

  • In modo cooperativo: l’HTLC viene “riavvolto” al contrario rimuovendolo dalla transazione di commitment senza modificare il saldo (sarà più chiaro in seguito)
  • Con un rimborso time-locked: ne abbiamo già parlato, ogni HTLC include una clausola di rimborso collegata ad un timelock delay.

Inoltro dei pagamenti #

Riferimento tabella: livello peer-to-peer, adding, settling, failing HTLCs.

Gli HTLCs sembrano una prerogativa di pagamenti multi-hop, ma in realtà il protocollo Lightning li usa anche per i pagamenti “locali” all’interno di un canale. La ragione dietro questa scelta è mantenere coerenza e lo stesso design del protocollo in ogni punto della rete. Per un destinatario di pagamento non c’è alcuna differenza tra un pagamento fatto dal proprio peer di canale e un pagamento inoltrato dal proprio peer ma per conto di qualcun altro.

Riprendiamo l’esempio di me, Mark e Kris.

  1. Io e Mark abbiamo un canale con un saldo di 70k sats per lato. Ricordo che la transazione di commitment che ci ha fatto arrivare fin qui è ritardata e revocabile.
  2. Io voglio che Mark accetti un HTLC da 50k sats da inoltrare a Kris. Per fare questo però io devo inviare i dettagli di questo HTLC come il payment hash e l’importo di questo pagamento. Lo faccio con il messaggio update_add_htlc.

Tecnicamente lo scambio di messaggi avviene in questo modo:

Le informazioni che Mark riceve sono sufficiente per creare una nuova transazione di commitment che presenta lo stesso saldo per lo stato del canale (il mio e quello di Mark) e un nuovo output che rappresenta l’HTLC offerto da me. Questo nuovo commitment avrà 50k sats nell’output HTLC, importo che proviene direttamente da me, quindi il mio nuovo saldo sarà 20k sats.

Se c’è un cambio di stato del canale ci deve essere un nuovo commitment e dopo che Mark lo crea, io lo firmo con il messaggio di commitment_signed.

Quindi, ora Mark ha un commitment firmato, deve riconoscerlo e revocare il vecchio commitment; lo fa con il messaggio revoke_and_ack e questo permette a me di costruire una chiave di revoca per creare una una transazione di penalità. Quello che sta succedendo è che Mark non potrà più pubblicare il suo vecchio commitment appena revocato, altrimenti ho i mezzi per punirlo (la chiave di revoca). Il vecchio impegno è stato a tutti gli effetti revocato.

Cosa manca?

Beh, io devo ancora revocare il mio vecchio commitment nel mio attuale stato attuale non c’è ancora l’HTLC! Costruisco un nuovo commitment speculare contenente l’HTLC, che deve essere ancora firmato da Mark. Esattamente come ha fatto Mark, procedo ad un revoke_and_ack e alla firma del nuovo commitment con il commitment_signed. Adesso anche io mi impegno a non pubblicare più il vecchio stato, ho concesso a Mark la chiave di revoca del vecchio commitment.

In ogni momento, su Lightning Network, si possono avere anche centinaia di HTLC per singolo canale.

Ora io e Mark abbiamo un nuovo commitment del canale che contiene anche l’output HTLC aggiuntivo, ma il nuovo bilancio ancora non riflette il fatto che io abbia inviato 50k a Mark. Il pagamento sarà possibile solo in cambio della prova del pagamento verso Kris!

Infatti adesso supponiamo che Mark e Kris facciamo esattamente le stesse operazioni che abbiamo eseguito io e Mark. Kris è il destinatario, l’unico a conoscere la Pre-Image dell’hash del pagamento, pertanto Kris può soddisfare subito l’HTLC con Mark.

In effetti è quello che fa, inviando un messaggio update_fulfill_htlc a Mark. Non appena Mark riceve questo messaggio controllerà subito se la l’hash di questa Pre-Image produce il payment hash; se valuta la condizione TRUE questo segreto è utilizzabile per riscattare l’HTLC.

Mark invia un update_fulfill_htlc anche a me, la Pre-Image viene propagata da Kris fino a me. Io eseguirò lo stesso controllo di Mark valutando l’hash della Pre-Image.

Questa è esattamente la prova che Kris è stato pagato!

Io e Mark possiamo rimuovere l’HTLC dalle transazioni di commitment e aggiornare i saldi del nostro canale in modo che rispecchino la nuova situazione dei fondi (ovviamente facendo di nuovo un ciclo di commitment e di revoca 🙂)

Ma se c’è un errore o una scadenza?

In questo scenario il processo si svilupperebbe in maniera simile, a differenza di un messaggio di fallimento update_fail_htlc. Dovrei rimuovere l’HTLC e passare in un ciclo di transazione di commitment e revoche per spostare lo stato del canale al nuovo commitment, con i saldi valorizzati a come erano prima dell’HTLC.

E per i pagamenti in locale, sullo stesso canale?

Sostituisci Mark con Kris nell’esempio. Profit.

Onion routing #

Riferimento tabella: livello routing, source-based onion routing (SPHINX).

Il primo nodo mittente del pagamento viene chiamato nodo di origine. L’ultimo nodo, che è il destinatario, nodo finale. Ogni nodo intermedio tra origine e destinazione viene definito hop e ogni hop deve impostare un HTLC in uscita per il prossimo hop. Le informazioni che io nodo mittente, spedisco, possono essere chiamate hop payload o hop data e tutto il messaggio è denominato onion.

Quindi, se riformulassi l’esempio del paragrafo precedente usando questa nuova terminologia, possiamo dire che io ho costruito un onion con dati hop payload e dicendo ad ogni hop come costruire l’HTLC in uscita per inviare il pagamento a Kris.

Ci sono due possibili formati che posso usare per le informazioni da comunicare a ciascun hop, uno legacy chiamato hop data e uno più flessibile chiamato hop payload.

Da dove devo partire per costruire questo messaggio?

Ovviamente da l’hop payload che sarà consegnato a Kris, il nodo finale. Questo basta ad affermare che il messaggio che riceverà Kris sarà differente per la presente di un campo che avrà tutti i valori impostati a 0. Questo campo si chiama short_channel_id e rappresenta un riferimento di canale che io che sono il costruttore dell’hop payload valorizzo per fare riferimento appunto, ai canali. Kris è il mio destinatario, quindi lo popolo con tutti 0 perché Kris non costruirà un HTLC in uscita essendo il destinatario del pagamento.

Naturalmente nessuno a parte Kris verrà a conoscenza di questa informazione perché è crittografata.

Posso iniziare dunque a serializzare il messaggio onion in un formato chiamato Type-Length-Value valido per gli hop payload. Dopodiché preparerò anche l’hop payload per Mark.

All’interno dell’hop payload sono presenti tre campi: short_channel_id (già affrontato), l’amt_to_forward che è l’importo in millisatoshi e l’outgoing_cltv_value che rappresenta il timelock delay di scadenza dell’HCLT, espresso in altezza futuro in timechain.

Se l’amt_to_forward fosse 50100 satoshi, Mark si aspetta una commissione di 100 sats per fare il routing del pagamento a Kris. Le aspettative su timelock e fees di routing sono stabilite dalla differenza tra i due HTLC, quello in ingresso e quello in uscita.

Supponiamo che tra me e Mark ci sia un altro nodo, per complicare un po il routing del messaggio onion.

Ho 3 payload: li ho costruiti per Kris, per Mark e per Lucas. Questi tre hop payload saranno avvolti a cipolla!

Bene, adesso devo generare diverse chiavi, che mi serviranno per crittografare i vari strati dell’onion:

  • Devo fare in modo che solo il nodo finale possa leggerlo.
  • Ogni intermediario deve conoscere solo nodo precedente e nodo successivo.
  • Nessuno dovrà conoscere la lunghezza del percorso totale.
  • Ogni intermediario potrà verificare che il messaggio non sia oggetto di tampering.

Il processo di costruzione viene chiamato packet construction. Un trucco interessante usante nell’onion routing è quello di rendere il percorso della stessa lunghezza per ogni nodo, come se ogni nodo vedesse il l’onion ancora all’inizio del suo percorso con altri 19 saltelli possibili in avanti (perché il percorso di instradamento massimo dell’onion è di 20 saltelli).

Mano a mano che ogni strato viene “eliminato”, vengono anche aggiunti dei dati spazzatura che servono da riempimento per rendere il payload sempre della stessa lunghezza.

Tutto bellissimo, ma nella pratica come si crea questa cipolla?

Innanzitutto, la cipolla viene costruita partendo dal nodo finale, quindi dal destinatario del messaggio che è Kris.

Inizio a creare un campo “vuoto” da 1300 byte generati in modo pseudo-casuale con una certa chiave.

Inizio ad inserire l’hop payload che ho costruito in precedenza infilandolo (in modo astratto) da sinistra, dovendo quindi spostare il filler più a destra. Questa operazione causa un eccesso di dimensione perché superiamo i 1300 byte, quindi un pezzettino del filler “cadrà” e sarà eliminato.

A questo punto i dati di Kris sono in chiaro nel messaggio onion, quindi devo in qualche modo offuscarli con una chiave chiamato rho che conosce anche (e solo) Kris. Per eseguire l’offuscamento viene generato un flusso dati pseudocasuale a partire da questa chiave e viene eseguita un operazione di XOR tra l’hop payload e questa flusso generato da rho.

Viene usato lo XOR perché quando Kris eseguirà un’altra operazione di XOR potrà vedere il contenuto dell’hop payload. Questo grazie alle caratteristiche dell’operatore XOR: se viene applicata una volta su un dato hai un risultato differente da quello di partenza, ma se lo riapplichi una seconda volta torni al messaggio di origine, come se “tornassi indietro”.

Infine, per garantire l’autenticità e l’integrità del pezzo di Kris, tramite un’altra chiave chiamata mu viene calcolo un HMAC che altro non è che checksum basato sull’hash di tutto l’hop payload.

Adesso tocca al hop payload di Mark. Eseguo gli stessi passaggi, con la differenza adesso non parto più da 1300 byte di filler, ma dai 1300 byte che contengono l’hop payload di Kris, opportunamente offuscato 🙂.

Inserendo l’hop payload di Mark da sinistra, devo stare molto attento a non sovrascrivere una parte dei dati di quello di Kris! Calcolo anche qui l’HMAC con la chiave mu e offusco tutto con la chiave rho. Questa volta però le chiavi mu e rho le conosciamo solo io e Mark perché questo hop è proprio per lui.

Due note:

  • Abbiamo due HMAC, uno esterno appena aggiunto di Mark, uno interno di Kris.
  • Quando Mark riceverà il messaggio onion non saprà quantificare il numero di payload all’interno, per lui sembrerà sempre il primo payload sui 20 possibili, proprio perché stiamo mantenendo la lunghezza fissa con il trucchetto che vedremo a breve.

Eseguo gli stessi step anche per Lucas. L’onion payload è pronto e posso inviarlo proprio a Lucas che è il mio nuovo partner di canale. Alla fine, sarà composto come segue:

  • 1 byte per la versione onion, per poter fare una distinzione in vista di futuri aggiornamenti di formato del protocollo.
  • 33 byte di una chiave di sessione pubblica che sarà usata da ciascun nodo per calcolare le famose chiavi per offuscare e per calcolare il checksum (e anche una terza chiave di cui non vi ho parlato chiamata pad).
  • Il nostro onion payload da 1300 byte.
  • 32 byte dell’HMAC più esterno che sarà verificato dal mio partner di canale, Lucas.

Sono pronto ad inviare il messaggio update_add_htlc a Lucas.

Come prima cosa dovrà aggiornare lo stato del canale con una transazione di commitment, ma questa parte l’abbiamo già vista in modo approfondito, quindi la salterò. Le “nuove” operazioni che Lucas dovrà compiere saranno prendere la chiave di sessione, generare la chiave mu e verificare il checksum HMAC. Una volta verificata l’integrità e l’autenticità, Lucas dovrà recuperare il suo hop payload e poi inoltrare la cipolla agli hop successivi.

Problema: se Lucas rimuove il suo hop payload perderemmo la lunghezza fissa da 1300 byte (molto male)

Qui arriva il trucco di cui parlavo: bisogna che ogni nodo generi un filler prima di spedire l’onion al successivo nodo.

Quello che Lucas fa è raddoppiare l’hop payload da 1300 byte e metterli uno affianco all’altro. Ora ne ha due da 1300 byte. Il secondo hop lo imposta tutto a 0 (zero-filled). Genera la chiave rho dalla chiave di sessione ed esegue uno XOR tra i 2600 byte di hop payload (uno vero, l’altro pieno di zeri) con un flusso da 2600 byte generato a partire dalla chiave rho.

Succede che i primi 1300 byte che contenevano il vero hop payload vengono de-offuscati, perché abbiamo applicato uno XOR per la seconda volta, quindi Lucas può leggere il suo hop payload, invece lo XOR tra i 1300 byte zero-filled e il flusso generato dalla chiave rho produrranno dati pseudocasuali.

In questo modo Lucas leggerà ciò che doveva, lo rimuoverà dall’hop payload, tutti gli altri byte vengono shiftati a sinistra per coprire lo spazio mancante dal payload di Lucas e usare il filler rimanente dall’operazione di XOR per mantenere la lunghezza di 1300 byte. Per spedire il pacchetto onion a Mark però deve aggiungere un HMAC esterno, in modo che Mark possa verificare integrità e correttezza.

L’hop payload di Lucas ha tutte le informazioni necessarie per la costruzione di un HTLC con Mark!

Mark esegue esattamente gli stessi passaggi visti per Lucas e infine Kris riceverà il payload finale. Quando riceve l’update_add_htlc dal payment hash saprà che è un pagamento per lui, che quindi è l’ultimo hop. Non fa nulla di differente (o quasi): toglie uno strato alla cipolla, ma non inoltra nulla perché è giunto il momento di rispondere con un update_fullfil_htlc a Mark per riscattare l’HTLC. Questo nel senso opposto (Kris->Mark->Lucas->musclesatz) e tutti gli HTLC saranno riscattati, aggiornando di conseguenza i saldi dei vari canali.

Nei pagamenti keysend non è più il destinatario a rivelare la Pre-Image al mittente ma è il mittente ad incorporare la Pre-Image nell’hop payload: quando il destinatario riceve la cipolla usa quella Pre-Image (che corrisponde all’hash di pagamento HTLC) per saldare il pagamento! Quindi in questo caso non serve scambiarsi l’invoice o inquadrare un QR 😉

Gossip #

Riferimento tabella: livello routing & layer, routing fees channel metadata & gossip replaying.

Fino ad adesso abbiamo capito come costruire il messaggio onion e come consegnarlo tra i vari nodi, ma la domanda adesso è:

Come fa il faccio a costruire un percorso valido fino al destinatario? Come a faccio a sapere le informazioni sul suo canale?

Ho bisogno di un grafo dei canali, che non è nient’altro che l’insieme (interconnesso) di canali annunciati pubblicamente e di quali nodi collegano.

Il protocollo utilizzato si chiama gossip e consente ad ogni nodo di condividere informazioni con la rete Lightning e viene sfruttato durante la creazione dei canali per diffondere “news” del tipo: ho creato un nuovo canale. Avviso tutti i miei altri canali di questa nuova connessione. Ai miei altri peer: per favore diffondete questo messaggio, fatelo propagare su tutta la rete.

La rete Lightning è (quasi) equivalente al grafo dei canali Lightning.

Stiamo parlando di una rete peer-to-peer quindi è fondamentale avere una fase di bootstrap iniziale affinché i nodi possano “vedersi” a vicenda. Il primo passo scoprire almeno un peer che faccia parte di questa rete e che abbia un grafo completo dei canali.

Usando uno degli innumerevoli protocolli di bootstrap riesco effettivamente a collegarmi ad un peer, quindi adesso devo scaricare e convalidare il grafo dei canali. Fatto ciò, posso iniziare ad aprire anche io canali di pagamento.

La cosa importante è mantenere il grafo aggiornato, scoprendo e convalidando nuovi canali, eliminando i canali chiusi on-chain ed eliminando anche quelli da cui non proviene nessun “battito cardiaco” ogni 2 settimane circa.

Come avviene il bootstrap iniziale?

Un opzione semplice ma poco pratica per la sua fragilità, sarebbe quella di sfruttare un certo numero di peer “fissi” (hardcoded) all’interno del software del nodo Lightning. Questo metodo viene usato come fallback nel caso non riuscissimo a trovare dei peer con l’altro metodo usato, chiamato bootstrap DNS.

Per fare peer-discovery sfruttando il DNS, BOLT #10 usiamo tre record:

  • record SRV per individuare un set di chiavi pubbliche del nodo.
  • record A per mappare la pubkey all’indirizzo IPv4.
  • record AAA per mappare la pubkey all’indirizzo IPv6.

Quando digiti “musclesatz.com” devi pensare ai record DNS “A” e “AAA” come delle mappe che ti aiutano a trovare il mio sito online.

Il record “A” è come una mappa per trovare case in un vecchio quartiere disponendo della via (123 Main Street). Nella pratica è un indirizzo IPv4. Il record “AAA” è come una mappa per trovare case in un quartiere nuovissimo e all’avanguardia, così avanti che non ha più un indirizzo standard come l’IPv4 ma è più una serie di numeri e lettere.

A noi serve sia una chiave pubblica che di un indirizzo IP per poterci connetterci ad un nodo!.

Quindi i passi sono:

  1. Identifico un server DNS che supporta il protocollo Bolt #10. Per farlo posso usare un server di seed comune mantenuto dalle principali implementazioni della rete Lightning.

  2. Emetto una query SRV per ottenere un insieme di peer che potrei usare per il bootstrap. Ognuno di questi peer è identificato da una chiave pubblica codificata in un formato chiamato bech32.

  3. Decodifico le chiavi pubbliche ottenute dalla risposta alla mia query SRV per ottenere gli identificativi dei nodi.

  4. Eseguo una query DNS per ottenere l’indirizzo IP del nodo target in combinazione con il suo identificatore (la chiave pubblica)

  5. Mi connetto al nodo usando un client Lightning.

Ho trovato il mio peer e stabilito la prima connessione.

Adesso devo sincronizzare e convalidare il grafo orientato dei canali. Si dice orientato perché le frecce hanno una direzione associata, il che significa che una relazione dal nodo 1 al nodo 2 può non essere simmetrica, ossia non implica necessariamente una relazione inversa da 2 ad 1.

I canali sono UTXO (indirizzi multisig 2-di-2), quindi possiamo dare una nuova definizione al grafo dei canali: è un sottoinsieme delle UTXO di Bitcoin. Non si può falsificare un grafo, lo vedremo.

Le informazioni del grafo sono propagate nella rete attraverso tre messaggi, descritti in Bolt #7:

  • node_announcement: esegue il “benvenuto” del nodo e contiene informazioni come l’identificatore, la capacità del nodo nel routing dei pagamenti o le sue politiche di routing. Non vengono scritti sulla timechain e sono validi solo se c’è un corrispondente e successivo messaggio di channel_announcement. Convalidare questi messaggi è molto facile: se era già esistente allora il nuovo node_announcement avrà un timestamp temporale maggiore; se non c’è nessun annuncio allora deve esistere necessariamente un channel_announcement nel grafo dei canali locale (lo analizzeremo meglio in seguito). Inoltre deve avere:

    • una signature valida.
    • tutti gli addresses inclusi ordinati in modo crescente in base all’identificatore dell’indirizzo.
    • i byte di alias devono avere una codifica UTF-8 valida.
  • channel_announcement: adesso possiamo preoccuparci di annunciare la presenza di un nuovo canale. Se voglio che il mio nodo permetta il routing di altri messaggi onion, devo comunicarlo pubblicamente. Quando un canale non è annunciato in teoria non potrebbe ricevere pagamenti; nella pratica può farlo grazie ai suggerimenti di routing (routing hints) che includono informazioni per inviare il mittente ad instradare correttamente il messaggio onion. Il grafo dei canali reale è di dimensione sconosciuta proprio per la presenza di canali non annunciati!

Un dettaglio: per evitare lo spam e garantire una robusta autenticazione, usiamo la timechain di Bitcoin. Per fare in modo che un nodo accetti un channel_announcement tale annuncio deve dimostrare l’esistenza dell’apertura del canale sula timechain.

Come è possibile indicare il riferimento di un canale sulla timechain? Usare l’outpoint completo del canale sarebbe folle perché chi verifica dovrebbe avere copia completa delle info dell’UTXO impostato per la verifica, generalmente sono i full node hanno tutte queste informazioni.

Molto meglio usare lo short channel ID. Per creare questo riferimento ci basiamo sulla posizione di questo output nella timechain. Ci serve solo fare riferimento ad un certo blocco, puntare ad una transazione al suo interno e poi ad uno specifico output creato dalla transazione. Questo è proprio lo short channel ID (o scid).

  • channel_update: il terzo e ultimo messaggio del protocollo gossip. È usato per aggiornare le informazioni su un canale di pagamento all’interno della rete Lightning come ad esempio le tabelle di instradamento o per informare gli altri nodi delle modifiche ai canali di pagamento. Questo tipo di messaggio permette ai nodi di fare il routing di pagamenti attraverso i canali più efficienti e aggiornati.

Abbiamo compreso che il protocollo gossip è una attività ricorrente e che quando un nodo esegue il bootstrap sulla rete Lightning inizia a ricevere gossip con i messaggi appena analizzati; grazie a queste informazioni inizierà a costruire il suo grafo dei canali. Più messaggi riceve più la sua mappa diventa precisa ed efficiente nella ricerca del percorso migliore per l’invio di pagamenti.

Ci sono anche altre informazioni che possono essere memorizzate: varie implementazioni Lightning possono collegare altri metadati come punteggi che valutano la qualità di un nodo come peer per instradare i pagamenti.

Pathfinding #

Riferimento tabella: livello payment, payment attempts, pathfind, path selection.

Cos’è e che problema dobbiamo risolvere con il pathfinding?

Il pagamento su Lightning dipende essenzialmente dalla ricerca di un percorso che colleghi un mittente con un destinatario. Questo processo è chiamato pathfinding ma..non fa parte dello standard BOLT! Non ne fa parte perché non richiede coordinamento o interoperabilità, se il routing è specificato in BOLT la ricerca e selezione del percorso viene lasciata al mittente del pagamento secondo l’algoritmo dell’implementazione che sta utilizzando.

Il problema è trovare il percorso migliore tra mittente e destinatario, migliore in termine di successo di consegna, di fees bass e con timelock brevi.

Per capire ancora meglio il problema del trasporto di satoshi tra me e Kris è necessario definire 3 attributi:

  • Capacità: è la quantità complessiva (massima) di sats finanziata nell’address multisig 2-di-2 .
capacità(del mio canale con Kris) = saldo(musclesatz) + saldo(Kris)
  • Saldo: è la quantità detenuta da ciascun peer nel canale.

  • Liquidità: è il saldo disponibile effettivamente inviabile. La liquidità del mio canale è uguale al mio saldo ma sottratta della riserva di canale e di qualsiasi HTLC che ho in sospeso.

capacità(musclesatz) = saldo(musclesatz) - riserva_di_canale(musclesatz) - HTLCs(musclesatz)

L’unico valore pubblico a tutta la rete è la capacità del canale. I saldi non sono annunciati per mantenere un’alta scalabilità e privacy. Se fossero pubblici avremmo risolto il pathfinding con un qualsiasi algoritmo di ricerca del cammino a costo minimo.

Bisogna ingegnarsi, ma intanto possiamo dire che almeno siamo sicuri che la liquidità di un canale è compresa (o uguale) alla channel_reserve (nel caso minimo, è un lower bound) e la capacità del canale meno la channel_reserver (upper bound)

riserva <= liquidità <= (capacità - riserva)

Questo è il nostro intervallo di incertezza, non conosciamo i saldi ma abbiamo una stima di massima della liquidità che deve essere compresa nell’intervallo. Questo intervallo la rete non lo conosce perché lo possiamo conoscere solo io e Kris, però potremmo sfruttare gli HTLC falliti dei nostri tentativi di pagamento per aggiornare la nostra stima sulla liquidità e abbassare l’incertezza.

Se un HTLC fallisce perché eravamo convinti che potesse gestire N satoshi e scopriamo che invece riesce a fornirne solo M (con M < N) allora già siamo in grado di modificare l’estremo superiore del nostro intervallo impostandolo a M - 1.

Semplificando, viene usato l’algoritmo A* (A-star) di Dijkstra nel quale abbiamo i nodi e assegniamo un peso alle connessioni tra i nodi. Questo peso sono le commissioni di routing di un pagamento.

Tuttavia siccome la liquidità del canale è sconosciuta al mittente ma è solo un intervallo teorico, il problema è complesso. Possiamo sospettare quali connessioni abbiano la capacità sufficiente per permettere l’instradamento del pagamento. Posso provare ad ordinare questi percorsi in base al peso e provare ogni percorso in ordine fin quando il pagamento non va a buon fine. Per i pagamenti che non vanno a buon fine sfrutto l’HTLC di errore per aggiornare il mio grafo, sistemando i limiti e riducendo l’incertezza perché ho esplorato un percorso.

Quindi, sintetizzando:

  1. Costruisco il grafo dei canali.
  2. Faccio pathfinding in base ad alcune euristiche.
  3. Eseguo un tentativo di pagamento.
  4. Per i pagamenti che non vanno a buon fine aggiorno il mio intervallo per ridurre l’incertezza al prossimo invio.

Paradossalmente imparo dagli errori perché più pagamenti non andati a buon fine ho, meglio riesco a ridurre l’incertezza del mio intervallo e avere una mappa dei percorsi possibili più precisa. La conoscenza dovuta agli errori mi sarà utilissima per i futuri pagamenti.

Attenzione però: non è una conoscenza “eterna” perché diventa stantia man mano che altri nodi inviano o instradano pagamenti. Io non vedrò mai nessuno di questi pagamenti a meno che non sia io il mittente.

L’onion routing come abbiamo visto mi permette di vedere solo un hop. Bisogna anche considerare per quanto tempo conservare questa conoscenza sull’incertezza dei vari percorsi prima che diventi inutile tenerla in memoria.

I Multipart Payments sono una funzionalità introdotta nel 2020 che equivale ad una strategia di ricerca del percorso migliore ma spezzettando il pagamento in tante piccole parti che saranno inviate come HTLC su diversi percorsi, preservando ovviamente l’atomicità del pagamento. Devono arrivare tutti al destinatario, altrimenti c’è il fallimento dell’operazione.

Rappresentano un miglioramento per un motivo semplicissimo: i piccoli pagamenti non devono adattarsi alla liquidità dei canali, perché ci sarà sicuramente liquidità sufficiente per quel “pezzettino”. È come se il nostro intervallo di incertezza fosse un pagliaio e il nostro piccolo pagamento fosse un ago.

Non c’è ovviamente nessuna certezza statistica che tutti i pagamenti multipart vadano a buon fine.

Sicurezza e attacchi a LN #

Lightning è privato?

Dipende cosa intendiamo per privacy.

Un nodo ha la visibilità sul suo successore e sul suo predecessore. Ma se è molto sviluppato ed è iper connesso, può generare delle euristiche e associazioni come nel caso dei grandissimi wallet non custodial. I nodi di questi provider sono responsabili del routing di una percentuale altissima di pagamenti da e verso quel nodo.

Cosa accadrebbe poi se due o più nodi sono sotto il controllo di un avversario malevolo? Se due nodi collusi si trovano sullo stesso path di pagamento, capiscono immediatamente di star inoltrando HTLC per lo stesso pagamento perché l’hash della Pre-Image è la medesima. Quali sono i rischi?

La sicurezza delle informazioni è molto spesso riassunta in tre aspetti:

  1. Riservatezza: le informazioni segrete arrivano effettivamente al destinatario previsto?
  2. Integrità: le informazioni sono state alterate durante il trasporto?
  3. Disponibilità: il sistema funziona o c’è un qualche tipo di attacco di negazione del servizio in atto?

La realtà, non solo per Lightning ma per qualunque sistema è che non c’è nessuna certezza che un eventuale attaccante non abbia successo. È un gioco a somma zero. Certo, si può mitigare il rischio, ma mai azzerarlo.

C’è differenza tra la privacy di Bitcoin e quella di Lightning?

A primo impatto Lightning offre una privacy migliore. In Bitcoin le transazioni Bitcoin non associano l’identità digitale ad uno specifico address ma è anche vero che le transazioni sono trasmesse in chiaro e vengono analizzate. Sono nate negli ultimi anni tantissime aziende che fanno questo di mestiere.

Su Lightning i pagamenti non vengono trasmessi all’intera rete e sopratutto non sono salvati per l’eternità come sulla timechain ma ci sono altre proprietà del protocollo che potrebbero creare dei mal di pancia.

Uno su tutti? I grandi pagamenti possono avere meno “opzioni” di instradamento.

Ancora? I nodi Lightning mantengono un’identità permanente, mentre i nodi Bitcoin no: posso ricevere ed inviare pagamenti solo dal nodo che ho utilizzato per aprire il canale di pagamento sulla timechain. Non ne posso usarne altri. Inoltre, devo anche annunciare il mio indirizzo IP e il mio ID e questo crea un collegamento permanente tra gli ID dei nodi e gli indirizzi IP. Gli IP sono dei lasciapassare intermedi nell’attacco all’anonimato e sono legati spesso alla posizione fisica di un utente nel mondo reale.

Infine, agli utenti Lightning può essere negato il servizio, bloccando i canali o esaurendoli. Inoltrare i pagamenti richiede che il saldo (risorsa scarsa) venga bloccato negli HTLC lungo il percorso. Un attaccante potrebbe lanciare tantissimi pagamenti senza finalizzarli occupando il capitale per un lungo periodo (ma questo dipende anche dal timelock delay impostato).

Insomma, possiamo decretare che lato privacy non c’è un vincitore tra Bitcoin e Lightning, ognuno ha le sue debolezze e punti di forza. Quello che possiamo affermare è che molti team di ricerca sono attualmente impegnati nel migliorare gli aspetti di sicurezza e privacy su entrambi.

Come ultima parte di questo articolo, farò una carrellata di possibili attacchi che Lightning può subire o ha subito. Siamo giunti quasi al termine.

Collegamento di mittente e destinatario

In Lightning l’anonimato dei partecipanti è essenziale per garantire la libertà di pagamento, questo concetto è uno dei pilastri per cui è nato Bitcoin. Ma un utente malintenzionato potrebbe tentare di violare questa privacy scoprendo il mittente e il destinatario di un pagamento. I motivi possono essere i più disparati. Non solo sarebbe compromessa la sicurezza nei pagamenti, ma anche favorire la censura da o verso determinati destinatari o mittenti.

Possiamo supporre che esistano due tipologie di avversari che possono cercare di violare l’anonimato in Lightning: gli avversari “fuori percorso” e quelli “sul percorso”. Quelli fuori percorso tentano di dedurre il mittente e il destinatario di un pagamento senza partecipare direttamente al processo di instradamento del pagamento mentre gli altri possono sfruttare le informazioni che ottengono durante il processo di pagamento per scoprire mittente e destinatario.

I primi, quelli fuori percorso, utilizzano una tecnica chiamata “sondaggio” o “probing” per dedurre i singoli saldi nei vari canali pagamento. Quando ottengono queste informazioni, l’attaccante può confrontare le istantanee della rete al tempo t1 e t2 per identificare i pagamenti avvenuti e scoprire mittente, destinatario e l’importo del pagamento. Ad esempio esegue una fotografia della rete all’istante t1 = 21:00 ed esegue un’altra fotografia alle 21:01. Se c’è stato un solo pagamento è banale capire chi sono le parti coinvolte e il loro ammontare, anche perché c’è il cambio di stato del canale che deve essere aggiornato ad ogni variazione di saldo! Se ci sono state più transazioni diventa più complesso, motivo per il quale queste aziende hanno sviluppato delle euristiche per disaccoppiare i vari pagamenti.

Quelli sul percorso, invece, partecipano attivamente al routing dei pagamenti e possono osservare le informazioni relative ai pagamenti. La loro bravura sta nell’escludere alcuni nodi dal set di anonimato del mittente o del destinatario usando le informazioni ottenute durante il processo di pagamento. Ad esempio, un nodo intermediario può osservare l’importo di qualsiasi pagamento oggetto di routing e i vari delta del timelock ed escludere qualsiasi nodo dal set di anonimato che è stato impostato con una capacità minore rispetto alla quantità del pagamento instradato.

Denial of Service

Esporre risorse online significa aumentare il rischio di renderle indisponibili a causa di una possibile negazione di servizio. Come farebbe un attaccante a negarmi il servizio? Banale. Viene fatto un bombardamento di richieste inutili, indistinguibili da quelle legittime. Non ci sono perdite in termini di fondi e hanno solo lo scopo di mandare offline il bersaglio vittima del DoS. Calandoci in Lightning, questo network addebita fees per l’uso delle risorse pubbliche, ma in questo caso i canali sono pubblici e le fees sono le commissioni di routing.

Quando un nodo inoltra un pagamento per me, questo nodo utilizza i suoi dati e la sua larghezza di banda per aggiornare lo stato del canale e tra l’altro l’importo viene parcheggiato (HTLC) finché non è regolato o finché non scade (timelock). I pagamenti che non vanno a buon fine non hanno un costo in termini di fees quindi è ottimo per noi, poveri utenti legittimi del network. Meno ottimo per gli attaccanti perché consente loro di bombardare il network di richieste di routing a costo zero.

Una variante concettuale del DoS è l’attacco basato sulla topologia. Lightning non è perfettamente decentralizzato quindi per far “fuori” dei nodi dal routing di pagamenti, per un attaccante non avrebbe senso puntare a quelli che si trovano sul bordo del grafo dei canali. Ha molto più senso attaccare nodi medi/grandi collegati a tantissimi altri nodi.

Disturbo del canale

Ogni nodo può gestire massima 483 HTLC. In un attacco di disturbo del canale l’attaccante esegue il routing di esattamente 483 pagamenti attraverso un canale di destinazione. Cosa potrà mai accadere?

Una versione simile riguarda il blocco della liquidità nel quale l’attaccante utilizza HTLC di grandi dimensioni consumando tutta la larghezza di banda disponibile del canale vittima. Di certo risulta molto più costoso che inviare 483 HTLC.

Deanonimizzazione cross-layer

La creazione e la chiusura di canali Lightning avviene sulla timechain e questo facilita un attaccante nel tentativo di deanonimizzare un utente usando sia i dati off-chain che quelli on-chain. Quali sono gli obiettivi per questi attaccanti?

  1. Creare un cluster di address Bitcoin che siano di proprietà dello stesso utente sul layer 1.

    • Contromisura 1: non riutilizzare gli output delle transazioni funding per aprire nuovi canali, evita molte euristiche.
    • Contromisura 2: non usare mai fonti esterne uniche per il funding, meglio variare.
  2. Creare un cluster di nodi Lightning di proprietà dello stesso utente sul layer 2.

    • Qui voglio farti una domanda: usare gli alias è bellissimo, semplificano la vita e migliorano l’usabilità. Indubbiamente. Ma se utilizzo come alias il nome di un mio dominio, pensi sia una buona o una cattiva idea? Se pensi sia una cattiva idea, qual è la contromisura da adottare?
  3. Creare un collegamento univoco tra i nodi Lightning e e gli address in timechain.

    • Fare l’opposto delle due contromisure in alto permette facilmente il collegamento cross-layer tra nodo Lightning ed entità che gestisce degli address su layer 1.

Ci si può proteggere?

Non è facile ma si può fare.

  • Canali non annunciati se non vuoi fare routing e non esporti.
  • Se vuoi fare routing:
    • Imposta la dimensione minima dell’HTLC che sei disposto ad accettare ad un valore più elevato di quello di default. In questo modo ogni slot non potrà essere occupata da un pagamento troppo piccolo (eviti lo spam).
    • Limita il numero massimo di slot che un peer può consumare.
    • Monitora i tassi di errore e se un peer è sopra un certo tasso, limitane la frequenza.
    • Usa gli shadow channel, sono canali di pagamento paralleli a quelli esistenti. Possono essere usati per il routing ma non essere annunciati.
  • Cerca di trovare un giusto bilanciamento tra accettazione e rifiuto dei canali. Quando un peer apre un canale suo tuo nodo non puoi sapere se verrà utilizzato per attaccare il tuo nodo o meno.
  • Evita di utilizzare lo stesso alias per il tuo nodo e per il tuo dominio (è la risposta alla domanda di sopra 😜).
  • Usa Tor.
  • Continua a leggere e ad informarti, non basarti esclusivamente su questo articolo.

Bye! #

  • Spero che questo excursus sia stato di tuo gradimento. Se trovi errori, imprecisioni o vuoi semplicemente migliorare questo articolo puoi contattarmi su X/Twitter o su telegram.

  • Se pensi di aver imparato qualcosa di nuovo condividi questo articolo o valuta una donazione. Oltre a gratificarmi personalmente mi aiuta a tenere in piedi questo sito e darmi la spinta nel produrre nuovi contenuti!

🙌