Questo articolo è una traduzione di “zkEVM” pubblicato da Scroll ZKP.
Scroll desidera ringraziare Vitalik Buterin, Barry Whitehat, Chih-Cheng Liang, Kobi Gurkan e Georgios Konstantopoulos per i loro preziosi commenti e critiche.
Introduzione
Pensiamo che gli zk-Rollup siano il Santo Graal – una soluzione di scalabilità di livello 2 di prima classe, molto economica e sicura. Tuttavia, gli zk-Rollup esistenti sono specifici per un’applicazione, il che rende difficile la costruzione di DApp decentralizzate componibili generali in un zk-Rollup e la migrazione delle applicazioni esistenti.
Presentiamo zkEVM, che consente di generare prove zk per la verifica della Ethereum Virtual Machine (EVM) generale. Ciò ci consente di costruire uno zk-Rollup completamente compatibile con l’EVM, verso il quale qualsiasi applicazione Ethereum esistente può migrare facilmente.
In questo articolo, identifichiamo le sfide di progettazione di zkEVM e spieghiamo perché ora è possibile. Forniamo anche una specifica intuizione e descriviamo le idee di alto livello su come costruirlo da zero.
Contesto
zk-Rollup è riconosciuto come la migliore soluzione di scalabilità per Ethereum. È altrettanto sicuro della layer 1 di Ethereum e ha il tempo di finalizzazione più breve rispetto a tutte le altre soluzioni di layer 2 (confronti dettagliati qui).
“A medio e lungo termine, i rollup ZK vinceranno in tutti i casi d’uso man mano che la tecnologia ZK-SNARK migliorerà.” – Vitalik Buterin
L’idea di base di zk-Rollup è quella di aggregare un gran numero di transazioni in un blocco Rollup e generare una prova succinta per il blocco fuori dalla catena. Successivamente, il contratto intelligente del layer 1 (Ethereum) deve solo verificare la prova e applicare direttamente lo stato aggiornato senza rieffettuare queste transazioni. Questo può aiutare a risparmiare commissioni di gas di un ordine di grandezza poiché la verifica della prova è molto meno costosa della rieffettuazione dei calcoli. Un altro risparmio deriva dalla compressione dei dati (ovvero tenere solo il minimo di dati sulla blockchain per la verifica).
Sebbene zk-Rollup sia sicuro ed efficiente, le sue applicazioni sono ancora limitate ai pagamenti e agli scambi. È difficile costruire DApp a uso generale per le due seguenti ragioni.
In primo luogo, se si vuole sviluppare DApp in un zk-Rollup, è necessario scrivere tutta la propria logica di contratto intelligente utilizzando un linguaggio speciale (ovvero R1CS). Non solo la sintassi del linguaggio richiesto è complessa, ma richiede anche una competenza estremamente forte in proof-of-knowledge zero.
In secondo luogo, l’attuale zk-Rollup non supporta la composabilità. Ciò significa che diverse applicazioni zk-Rollup non possono interagire tra loro all’interno del layer 2. Questa qualità danneggia notevolmente la composabilità delle applicazioni DeFi.
In breve, uno zk-Rollup non è molto amichevole per gli sviluppatori e le sue funzionalità sono limitate per ora. Questo è il più grande problema che vogliamo risolvere. Vogliamo offrire la migliore esperienza agli sviluppatori e supportare la composabilità all’interno del layer 2 supportando direttamente la verifica EVM nativa, in modo che le applicazioni Ethereum esistenti possano semplicemente migrare verso lo zk-Rollup così com’è.
Costruire DApp generali in zk-Rollups
Ci sono due modi per costruire DApp generali in un zk-Rollup:
- Il primo consiste nel costruire un circuito specifico dell’applicazione (“ASIC”) per diverse applicazioni digitali.
- L’altro consiste nel costruire un circuito universale “EVM” per l’esecuzione dei contratti intelligenti.
Il termine “circuito” si riferisce alla rappresentazione del programma utilizzata nella proof-of-knowledge zero (proof a conoscenza zero).
Ad esempio, se si vuole dimostrare che hash(x) = y, è necessario riscrivere la funzione hash utilizzando la forma del circuito. La forma del circuito supporta solo espressioni molto limitate (ad esempio, R1CS supporta solo l’addizione e la moltiplicazione). È quindi molto difficile scrivere un programma utilizzando il linguaggio del circuito – è necessario costruire tutta la logica del proprio programma (incluse if else, loop, ecc.) utilizzando add e mul.
1- Il primo approccio richiede che lo sviluppatore progetti circuiti “ASIC” specializzati per diverse applicazioni digitali. Questo è il modo più tradizionale di utilizzare la proof-of-knowledge zero. Ogni DApp avrà un carico di lavoro inferiore grazie alla progettazione di circuiti personalizzati. Tuttavia, ciò comporta problemi di composabilità, poiché il circuito è “statico”, e di esperienza degli sviluppatori, poiché richiede una forte competenza nella progettazione dei circuiti.
2- Il secondo approccio non richiede progettazione speciale o competenze per lo sviluppatore. L’idea di alto livello di una tale proof basata sulla macchina è che ogni programma finirà per eseguirsi sul processore (CPU), quindi è sufficiente costruire un circuito CPU universale per verificare la fase CPU a basso livello. Successivamente, possiamo utilizzare questo circuito CPU per verificare l’esecuzione di qualsiasi programma. Nel nostro scenario, il programma è un contratto intelligente e la CPU è un EVM. Tuttavia, questo approccio non è stato comunemente adottato negli ultimi anni a causa dei suoi importanti costi generali. Ad esempio, anche se si vuole dimostrare che il risultato dell’addizione è corretto solo in una singola fase, è comunque necessario pagare i costi generali di un intero circuito EVM. Se si hanno migliaia di fasi nella traccia di esecuzione, il sovraccarico del circuito EVM dal lato del prover sarà 1000 volte superiore.
Recentemente, molte ricerche sono state condotte per ottimizzare le proof-of-knowledge zero seguendo questi due approcci, compresi (i) la proposta di nuove primitive amichevoli zero-knowledge, ad esempio l’hash Poseidon può raggiungere un’efficienza di 100 volte superiore a SHA256 nel circuito, (ii) il lavoro in corso per migliorare l’efficienza delle macchine virtuali (VM) verificabili generali, come in TinyRAM, e (iii) un numero crescente di trucchi di ottimizzazione
Sfide di progettazione di zkEVM
La costruzione di zkEVM è difficile. Anche se l’intuizione è chiara da anni, nessuno è riuscito a costruire un circuito EVM nativo. A differenza di TinyRAM, la costruzione e l’implementazione di zkEVM sono ancora più difficili per le seguenti ragioni:
In primo luogo, l’EVM ha un supporto limitato delle curve ellittiche. Attualmente, l’EVM supporta solo l’accoppiamento BN254. È difficile fare una ricorsione della prova poiché la curva ellittica ciclica non è supportata direttamente. È anche difficile utilizzare altri protocolli specializzati in questo contesto. L’algoritmo di verifica deve essere compatibile con l’EVM.
In secondo luogo, la dimensione delle parole dell’EVM è di 256 bit. L’EVM funziona su interi di 256 bit (come la maggior parte delle VM ordinarie funziona su interi di 32 a 64 bit), mentre le prove zk funzionano più “naturalmente” su campi primi. Fare “aritmetica di campi non corrispondenti” all’interno di un circuito richiede prove di estensione, che aggiungeranno ~100 vincoli per ogni passaggio EVM. Ciò aumenterà la dimensione del circuito EVM di due ordini di grandezza.
In terzo luogo, l’EVM ha molti opcode speciali. L’EVM è diverso da una macchina virtuale tradizionale, ha molti opcode speciali come CALL e ha anche tipi di errore legati al contesto di esecuzione e al gas. Ciò rappresenta nuove sfide per la progettazione dei circuiti.
In quarto luogo, l’EVM è una macchina virtuale basata su stack. L’architettura SyncVM (zksync) e Cairo (starkware) definisce il proprio IR/AIR nel modello basato sui registri. Hanno costruito un compilatore specializzato per compilare il codice dei contratti intelligenti in un nuovo IR compatibile con zk. Il loro approccio è compatibile con il linguaggio invece di essere compatibile con il modello EVM nativo. Ciò è più difficile da dimostrare per il modello basato sullo stack e supporta direttamente la catena degli strumenti nativi.
In quinto luogo, la disposizione di archiviazione di Ethereum comporta un enorme sovraccarico. La disposizione di archiviazione di Ethereum dipende fortemente da Keccak e da un enorme MPT4, entrambi non sono compatibili con zk e hanno un enorme costo di prova. Ad esempio, l’hash Keccak è 1000 volte più grande dell’hash Poseidon nel circuito. Tuttavia, se sostituisci Keccak con un altro hash, ciò comporterà problemi di compatibilità con l’infrastruttura Ethereum esistente.
In sesto luogo, la prova basata sulla macchina ha un’enorme sovraccarico. Anche se si riesce a gestire correttamente tutti i problemi sopra menzionati, è comunque necessario trovare un modo efficiente per comporre insieme tutto per ottenere un circuito EVM completo. Come ho menzionato nella sezione precedente, anche semplici opcode come l’addizione possono causare un overhead per l’intero circuito EVM.
Perché è possibile adesso?
Grazie ai grandi progressi compiuti dai ricercatori in questo campo, sempre più problemi di efficienza sono stati risolti negli ultimi due anni e il costo della dimostrazione della zkEVM è finalmente realizzabile!
La maggiore innovazione tecnologica deriva dai seguenti aspetti:
L’uso dell’impegno polinomiale. Negli ultimi anni, la maggior parte dei protocolli di dimostrazione a conoscenza zero succinti si attiene a R1CS con una richiesta PCP codificata in una configurazione di trust specifica dell’applicazione. La dimensione del circuito di solito esplode e non è possibile fare molte ottimizzazioni personalizzate poiché il grado di ogni vincolo deve essere 2 (il pairing bilineare consente solo una moltiplicazione nell’esponente). Con gli schemi di impegno polinomiale, è possibile elevare i vincoli a qualsiasi grado con una configurazione universale o addirittura trasparente. Ciò consente una grande flessibilità nella scelta del backend.
L’emergere degli argomenti di tabella di ricerca e dei gadget personalizzati. Un’altra importante ottimizzazione deriva dall’uso delle tabelle di ricerca. L’ottimizzazione è stata proposta per la prima volta in Arya e successivamente ottimizzata in Plookup. Ciò può consentire di risparmiare MOLTE primitivi poco favorevoli alla zk (ad esempio le operazioni per bit come AND, XOR, ecc.). I gadget personalizzati consentono di fare vincoli di alto grado con efficienza. TurboPlonk e UltraPlonk definiscono una sintassi di programma elegante per facilitare l’uso delle tabelle di ricerca e la definizione di gadget personalizzati. Ciò può essere estremamente utile per ridurre l’overhead del circuito EVM.
La dimostrazione ricorsiva è sempre più realizzabile. In passato, la dimostrazione ricorsiva presentava un costo enorme in quanto si basava su curve ellittiche cicliche adattate all’appaiamento (cioè una costruzione basata sulle curve MNT). Questo introduce un importante costo computazionale. Tuttavia, sempre più tecniche permettono di farlo senza sacrificare l’efficienza. Ad esempio, Halo può evitare la necessità di una curva adattata alla coppia e ammortizzare il costo della ricorsione utilizzando un argomento speciale di prodotto interno. Aztec dimostra che è possibile aggregare la prova per i protocolli esistenti direttamente (le tabelle di ricerca possono ridurre l’overhead dell’operazione di campo non nativo e quindi rendere il circuito di verifica più piccolo). Ciò può migliorare considerevolmente la scalabilità della dimensione del circuito supportato.
La capacità di accelerazione hardware sta rendendo le dimostrazioni più efficienti. A quanto sappiamo, abbiamo realizzato l’acceleratore GPU e ASIC/FPGA più veloce per il prover. Il nostro articolo che descrive il prover ASIC è già stato accettato dalla più grande conferenza informatica (ISCA) di quest’anno. Il prover GPU è circa 5x-10x più veloce dell’implementazione di Filecoin. Ciò può migliorare notevolmente l’efficienza di calcolo del prover.
Allora, come funziona e come costruirlo?
Oltre alla forte intuizione e all’improvamento tecnologico, dobbiamo ancora avere un’idea più chiara di ciò che dobbiamo dimostrare e definire un’architettura più specifica. Presenteremo maggiori dettagli tecnici e confronti negli articoli successivi. Qui descriviamo il flusso di lavoro globale e alcune idee chiave.
Flusso di lavoro per sviluppatori e utenti
I developer possono implementare smart contract utilizzando qualsiasi linguaggio compatibile con EVM e distribuire il bytecode compilato su Scroll. Successivamente, gli utenti possono inviare transazioni per interagire con questi smart contract distribuiti. L’esperienza degli utenti e dei developer sarà esattamente la stessa della layer 1. Tuttavia, le spese di gas sono notevolmente ridotte e le transazioni sono pre-confermate istantaneamente su Scroll (il prelievo richiede solo pochi minuti per essere finalizzato).
Flusso di lavoro per zkEVM
Anche se il flusso di lavoro esterno rimane lo stesso, la procedura di elaborazione sottostante per il livello 1 e il livello 2 è completamente diversa:
- Il livello 1 si basa sulla riesecuzione dei contratti intelligenti.
- Il livello 2 si basa sulla prova di validità del circuito zkEVM.
Spieghiamo in dettaglio come le cose procedono diversamente per le transazioni di livello 1 e di livello 2.
Nel livello 1, i bytecode dei contratti intelligenti distribuiti sono archiviati nello storage di Ethereum. Le transazioni saranno diffuse in una rete P2P. Per ogni transazione, ogni nodo completo deve caricare il bytecode corrispondente ed eseguirlo su EVM per raggiungere lo stesso stato (la transazione sarà utilizzata come dati di input).
Nel livello 2, il bytecode è anche archiviato nello storage e gli utenti si comporteranno allo stesso modo. Le transazioni saranno inviate fuori dalla catena a un nodo zkEVM centralizzato. Successivamente, invece di eseguire semplicemente il bytecode, zkEVM genererà una prova succinta per dimostrare che gli stati sono stati aggiornati correttamente dopo l’applicazione delle transazioni. Infine, il contratto di livello 1 verificherà la prova e aggiornerà gli stati senza rieseguire le transazioni.
Guardiamo più da vicino il processo di esecuzione e vediamo cosa zkEVM deve provare alla fine della giornata. Nell’esecuzione nativa, EVM caricherà il bytecode ed eseguirà gli opcode nel bytecode uno per uno dall’inizio. Ogni opcode può essere considerato come eseguente i tre sotto-step seguenti: (i) leggere elementi dalla pila, dalla memoria o dallo storage (ii) eseguire un calcolo su questi elementi (iii) riscrivere i risultati nella pila, nella memoria o nello storage. Ad esempio, l’opcode add deve leggere due elementi dalla pila, aggiungerli e riscrivere il risultato nella pila.
Pertanto, è chiaro che la prova di zkEVM deve contenere gli aspetti seguenti che corrispondono al processo di esecuzione:
- Il bytecode viene correttamente caricato dallo storage persistente (si esegue l’opcode corretto caricato da un determinato indirizzo)
- Gli opcode nel bytecode vengono eseguiti uno per uno in modo coerente (il bytecode viene eseguito nell’ordine senza saltare o saltare alcun opcode)
- Ogni opcode viene eseguito correttamente (i tre sotto-step di ogni opcode sono eseguiti correttamente, R/W + calcolo)
Punto forte del design di zkEVM
Nella progettazione dell’architettura di zkEVM, dobbiamo affrontare i tre aspetti sopra menzionati uno per volta.
Dobbiamo progettare un circuito per un accumulatore crittografico.
Questa parte funge da “archiviazione verificabile”, abbiamo bisogno di una tecnica per dimostrare che stiamo leggendo correttamente. Un accumulatore crittografico può essere utilizzato per raggiungere questo obiettivo in modo efficiente. Prendiamo ad esempio l’albero di Merkle. Il bytecode deployato verrà memorizzato come una foglia nell’albero di Merkle. Successivamente, il verificatore può verificare che il bytecode sia stato caricato correttamente da un indirizzo specifico utilizzando una prova succinta (cioè verificando il percorso di Merkle nel circuito). Per lo storage di Ethereum, è necessario che il circuito sia compatibile con la funzione di hash Merkle Patricia Trie e Keccak.
Dobbiamo progettare un circuito per collegare il bytecode con la vera traccia di esecuzione.
Un problema per spostare il bytecode in un circuito statico riguarda gli opcode condizionali come jump (corrispondente al ciclo, all’istruzione if else nei contratti intelligenti). Può saltare ovunque. La destinazione non è determinata prima che il bytecode sia stato eseguito con un input specifico. Questo è il motivo per cui dobbiamo verificare la vera traccia di esecuzione. La traccia di esecuzione può essere considerata come un “bytecode srotolato”, che includerà la sequenza di opcode nell’ordine di esecuzione reale (cioè se si salta in un’altra posizione, la traccia conterrà l’opcode e la posizione destinata).
Il prover fornirà direttamente la traccia di esecuzione come testimonianza del circuito. Dobbiamo dimostrare che la traccia di esecuzione fornita è quella “srotolata” dal bytecode con un input specifico. L’idea è di forzare il valore del contatore del programma ad essere coerente. Per gestire la destinazione indeterminata, l’idea è di lasciare che il prover fornisca tutto. Successivamente, è possibile verificare l’efficacia della coerenza utilizzando un argomento di ricerca (cioè dimostrare che gli opcode con il giusto contatore globale sono inclusi nel “bus”).
Dobbiamo progettare circuiti per ogni opcode (dimostrare che la lettura, la scrittura e i calcoli di ogni opcode sono corretti).
Questa è la parte più importante – dimostrare che ogni opcode nella traccia di esecuzione è corretto e coerente. Ciò comporterà un enorme sovraccarico se si mettono insieme direttamente tutte le cose. L’idea di ottimizzazione importante qui è che:
- Possiamo separare R/W e calcolo in due prove. Una andrà a cercare gli elementi necessari per tutti gli opcode in un “bus”. L’altra dimostrerà che il calcolo effettuato sugli elementi del “bus” viene eseguito correttamente. Ciò può ridurre notevolmente il sovraccarico di ogni parte (cioè non è necessario considerare l’intero storage di EVM nella prova di calcolo). In una specifica più dettagliata, la prima viene chiamata “prova di stato” e la seconda “prova EVM”. Un’altra osservazione è che il “mapping del bus” può essere gestito efficacemente dall’argomento di ricerca.
- Possiamo progettare una restrizione personalizzata di grado superiore per ogni opcode (ad esempio, la parola EVM può essere risolta efficacemente suddividendola in più parti). Possiamo scegliere di “aprire” una restrizione con un polinomio selettore in base alle nostre esigenze. Ciò consente di evitare il sovraccarico di tutto il circuito EVM ad ogni passo.
Questa architettura è stata specificata per la prima volta dalla Fondazione Ethereum. È ancora in una fase precoce e in sviluppo attivo. Collaboriamo strettamente con loro per trovare il miglior modo di implementare il circuito EVM. Finora, le caratteristiche più importanti sono state definite e alcuni opcode sono già stati implementati qui (utilizzando la sintassi UltraPlonk nel repo Halo2). Ulteriori dettagli verranno presentati negli articoli successivi. Invitiamo i lettori interessati a leggere questo documento. Il processo di sviluppo sarà trasparente. Sarà un’impresa comunitaria e una progettazione completamente aperta. Speriamo che altre persone possano unirsi a noi e contribuire a questo progetto.
Cosa può offrire in più?
zkEVM è molto più di una semplice scalabilità della layer 2. Può essere considerato come un modo diretto per scalare il layer 1 di Ethereum attraverso la prova di validità del layer 1. Ciò significa che è possibile scalare il layer 1 esistente senza alcuna layer 2 speciale.
Ad esempio, è possibile utilizzare zkEVM come un nodo completo. La prova può essere utilizzata per dimostrare le transizioni tra gli stati esistenti direttamente, senza bisogno di portare nulla al layer 2, si può dimostrare per tutte le transazioni del layer 1 direttamente! In modo più ampio, è possibile utilizzare zkEVM per generare una prova succinta per l’intera Ethereum come Mina. L’unica cosa che è necessario aggiungere è la ricorsione della prova (cioè integrare il circuito di verifica di un blocco in zkEVM).
Conclusione
zkEVM può offrire la stessa esperienza agli sviluppatori e agli utenti. È molto più conveniente senza compromettere la sicurezza. È stata proposta un’architettura modulare per la sua costruzione che sfrutta le recenti innovazioni nel campo della prova a conoscenza zero per ridurre i costi generali (compresi i vincoli personalizzati, gli argomenti di ricerca, la ricorsione della prova e l’accelerazione hardware). Siamo ansiosi di vedere altre persone unirsi all’effort della comunità zkEVM e riflettere con noi!