Napište plazmu specifickou pro aplikaci, která zachovává soukromí
Úvod
Na rozdíl od rollupů používají plazmy hlavní síť Ethereum k zajištění integrity, nikoli však dostupnosti. V tomto článku napíšeme aplikaci, která se chová jako plazma, přičemž Ethereum zaručuje integritu (žádné neoprávněné změny), ale nikoli dostupnost (centralizovaná součást může selhat a vyřadit celý systém).
Aplikace, kterou zde píšeme, je banka zachovávající soukromí. Různé adresy mají účty se zůstatky a mohou posílat peníze (ETH) na jiné účty. Banka zveřejňuje hashe stavu (účty a jejich zůstatky) a transakce, ale skutečné zůstatky drží mimo blockchain, kde mohou zůstat soukromé.
Návrh
Nejedná se o systém připravený pro produkční nasazení, ale o výukový nástroj. Jako takový je napsán s několika zjednodušujícími předpoklady.
-
Pevně daný fond účtů. Existuje určitý počet účtů a každý účet patří na předem určenou adresu. Díky tomu je systém mnohem jednodušší, protože v důkazech s nulovou znalostí je obtížné pracovat s datovými strukturami s proměnlivou velikostí. Pro systém připravený pro produkční nasazení můžeme použít Merkle kořen jako hash stavu a poskytnout Merkle důkazy pro požadované zůstatky.
-
Ukládání do paměti. V produkčním systému je třeba zapisovat všechny zůstatky na účtech na disk, aby se zachovaly pro případ restartu. Zde je v pořádku, pokud se informace jednoduše ztratí.
-
Pouze převody. Produkční systém by vyžadoval způsob, jak vkládat prostředky do banky a jak je vybírat. Cílem je zde však pouze ilustrovat koncept, takže tato banka je omezena na převody.
Důkazy s nulovou znalostí
Na základní úrovni důkaz s nulovou znalostí ukazuje, že dokazující zná nějaká data, Datasoukromá, taková, že existuje vztah Vztah mezi nějakými veřejnými daty, Dataveřejná, a Datasoukromá. Ověřovatel zná Vztah a Dataveřejná.
Abychom zachovali soukromí, je třeba, aby stavy a transakce byly soukromé. Abychom však zajistili integritu, potřebujeme, aby kryptografický hash (opens in a new tab) stavů byl veřejný. Abychom lidem, kteří odesílají transakce, dokázali, že se tyto transakce skutečně uskutečnily, musíme také zveřejňovat hashe transakcí.
Ve většině případů je Datasoukromá vstupem do programu důkazu s nulovou znalostí a Dataveřejná je výstupem.
Tato pole v Datasoukromá:
- Stavn, starý stav
- Stavn+1, nový stav
- Transakce, transakce, která mění starý stav na nový. Tato transakce musí obsahovat tato pole:
- Cílová adresa, která přijímá převod
- Částka, která se převádí
- Nonce, aby se zajistilo, že každá transakce může být zpracována pouze jednou. Zdrojová adresa nemusí být v transakci, protože ji lze obnovit z podpisu.
- Podpis, podpis, který je oprávněn provést transakci. V našem případě je jedinou adresou oprávněnou k provedení transakce zdrojová adresa. Protože náš systém s nulovou znalostí funguje tak, jak funguje, potřebujeme kromě podpisu Ethereum také veřejný klíč účtu.
Toto jsou pole v Dataveřejná:
- Hash(Stavn) hash starého stavu
- Hash(Stavn+1) hash nového stavu
- Hash(Transakce) hash transakce, která mění stav ze Stavun na Stavn+1.
Vztah kontroluje několik podmínek:
- Veřejné hashe jsou skutečně správnými hashi pro soukromá pole.
- Transakce, když se aplikuje na starý stav, má za následek nový stav.
- Podpis pochází ze zdrojové adresy transakce.
Vzhledem k vlastnostem kryptografických hashovacích funkcí stačí prokázat tyto podmínky k zajištění integrity.
Datové struktury
Primární datovou strukturou je stav, který uchovává server. Pro každý účet server sleduje zůstatek na účtu a nonce (opens in a new tab), které se používá k zabránění opakovacím útokům (opens in a new tab).
Komponenty
Tento systém vyžaduje dvě součásti:
- Server, který přijímá transakce, zpracovává je a zveřejňuje hashe na řetězci spolu s důkazy s nulovou znalostí.
- Chytrý kontrakt, který ukládá hashe a ověřuje důkazy s nulovou znalostí, aby se zajistilo, že přechody stavů jsou legitimní.
Datový a řídicí tok
Toto jsou způsoby, jakými jednotlivé součásti komunikují při převodu z jednoho účtu na druhý.
-
Webový prohlížeč odešle podepsanou transakci s žádostí o převod z účtu podepisujícího na jiný účet.
-
Server ověří, že transakce je platná:
- Podepisující má v bance účet s dostatečným zůstatkem.
- Příjemce má v bance účet.
-
Server vypočítá nový stav odečtením převedené částky od zůstatku podepisujícího a jejím přičtením k zůstatku příjemce.
-
Server vypočítá důkaz s nulovou znalostí, že změna stavu je platná.
-
Server odešle na Ethereum transakci, která obsahuje:
- Nový hash stavu
- Hash transakce (aby odesílatel transakce věděl, že byla zpracována)
- Důkaz s nulovou znalostí, který dokazuje, že přechod do nového stavu je platný
-
Chytrý kontrakt ověří důkaz s nulovou znalostí.
-
Pokud se důkaz s nulovou znalostí ověří, chytrý kontrakt provede tyto akce:
- Aktualizace současného hashe stavu na nový hash stavu
- Vydá záznam do protokolu s novým hashem stavu a hashem transakce
Nástroje
Pro kód na straně klienta použijeme Vite (opens in a new tab), React (opens in a new tab), Viem (opens in a new tab) a Wagmi (opens in a new tab). Jedná se o standardní nástroje v oboru; pokud je neznáte, můžete použít tento tutoriál.
Většina serveru je napsána v JavaScriptu pomocí Node (opens in a new tab). Část s nulovou znalostí je napsána v jazyce Noir (opens in a new tab). Potřebujeme verzi 1.0.0-beta.10, takže po instalaci Noir podle pokynů (opens in a new tab) spusťte:
1noirup -v 1.0.0-beta.10Blockchain, který používáme, je anvil, lokální testovací blockchain, který je součástí Foundry (opens in a new tab).
Implementace
Protože se jedná o složitý systém, budeme ho implementovat postupně.
Fáze 1 – Ruční nulová znalost
V první fázi podepíšeme transakci v prohlížeči a poté ručně poskytneme informace do důkazu s nulovou znalostí. Kód nulové znalosti očekává, že tyto informace získá v souboru server/noir/Prover.toml (zdokumentováno zde (opens in a new tab)).
Chcete-li to vidět v akci:
-
Ujistěte se, že máte nainstalovaný Node (opens in a new tab) a Noir (opens in a new tab). Nejlépe je nainstalujte na systém UNIX, jako je macOS, Linux nebo WSL (opens in a new tab).
-
Stáhněte si kód 1. fáze a spusťte webový server, který bude obsluhovat kód klienta.
1git clone https://github.com/qbzzt/250911-zk-bank.git -b 01-manual-zk2cd 250911-zk-bank3cd client4npm install5npm run devDůvod, proč zde potřebujete webový server, je ten, že aby se předešlo určitým typům podvodů, mnoho peněženek (například MetaMask) nepřijímá soubory obsluhované přímo z disku.
-
Otevřete prohlížeč s peněženkou.
-
V peněžence zadejte novou heslovou frázi. Upozorňujeme, že tímto smažete stávající heslovou frázi, takže se ujistěte, že máte zálohu.
Heslová fráze je
test test test test test test test test test test test junk, výchozí testovací heslová fráze pro anvil. -
Přejděte na kód na straně klienta (opens in a new tab).
-
Připojte se k peněžence a vyberte cílový účet a částku.
-
Klikněte na Podepsat a podepište transakci.
-
Pod nadpisem Prover.toml najdete text. Nahraďte soubor
server/noir/Prover.tomltímto textem. -
Spusťte důkaz s nulovou znalostí.
1cd ../server/noir2nargo executeVýstup by měl být podobný tomuto
1ori@CryptoDocGuy:~/noir/250911-zk-bank/server/noir$ nargo execute23[zkBank] Circuit witness successfully solved4[zkBank] Witness saved to target/zkBank.gz5[zkBank] Circuit output: (0x199aa62af8c1d562a6ec96e66347bf3240ab2afb5d022c895e6bf6a5e617167b, 0x0cfc0a67cb7308e4e9b254026b54204e34f6c8b041be207e64c5db77d95dd82d, 0x450cf9da6e180d6159290554ae3d8787, 0x6d8bc5a15b9037e52fb59b6b98722a85) -
Porovnejte poslední dvě hodnoty s hashem, který vidíte ve webovém prohlížeči, abyste zjistili, zda je zpráva správně zahashována.
server/noir/Prover.toml
Tento soubor (opens in a new tab) ukazuje formát informací, který očekává Noir.
1message="send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 500 finney (milliEth) 0 "Zpráva je v textovém formátu, což usnadňuje její pochopení uživatelem (což je nutné při podepisování) a její zpracování kódem Noir. Částka je uvedena ve finney, aby bylo možné na jedné straně provádět zlomkové převody a na druhé straně byla snadno čitelná. Poslední číslo je nonce (opens in a new tab).
Řetězec je dlouhý 100 znaků. Důkazy s nulovou znalostí si dobře neporadí s daty s proměnlivou velikostí, proto je často nutné data doplňovat.
1pubKeyX=["0x83",...,"0x75"]2pubKeyY=["0x35",...,"0xa5"]3signature=["0xb1",...,"0x0d"]Tyto tři parametry jsou bajtová pole s pevnou velikostí.
1[[accounts]]2address="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"3balance=100_0004nonce=056[[accounts]]7address="0x70997970C51812dc3A010C7d01b50e0d17dc79C8"8balance=100_0009nonce=0Zobrazit všeTímto způsobem se specifikuje pole struktur. Pro každou položku zadáme adresu, zůstatek (v milliETH, známé také jako finney (opens in a new tab)) a další hodnotu nonce.
client/src/Transfer.tsx
Tento soubor (opens in a new tab) implementuje zpracování na straně klienta a generuje soubor server/noir/Prover.toml (ten, který obsahuje parametry nulové znalosti).
Zde je vysvětlení zajímavějších částí.
1export default attrs => {Tato funkce vytváří komponentu Transfer Reactu, kterou mohou importovat další soubory.
1 const accounts = [2 "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",3 "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",4 "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",5 "0x90F79bf6EB2c4f870365E785982E1f101E93b906",6 "0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65",7 ]Toto jsou adresy účtů, adresy vytvořené pomocí test ... heslové fráze test junk. Pokud chcete použít vlastní adresy, stačí upravit tuto definici.
1 const account = useAccount()2 const wallet = createWalletClient({3 transport: custom(window.ethereum!)4 })Tyto Wagmi hooky (opens in a new tab) nám umožňují přístup ke knihovně viem (opens in a new tab) a k peněžence.
1 const message = `send ${toAccount} ${ethAmount*1000} finney (milliEth) ${nonce}`.padEnd(100, " ")Toto je zpráva, doplněná mezerami. Pokaždé, když se změní jedna z proměnných useState (opens in a new tab), komponenta se překreslí a zpráva se aktualizuje.
1 const sign = async () => {Tato funkce je volána, když uživatel klikne na tlačítko Podepsat. Zpráva se automaticky aktualizuje, ale podpis vyžaduje schválení uživatelem v peněžence a my o něj nechceme žádat, pokud to není nutné.
1 const signature = await wallet.signMessage({2 account: fromAccount,3 message,4 })Požádejte peněženku o podepsání zprávy (opens in a new tab).
1 const hash = hashMessage(message)Získejte hash zprávy. Je užitečné poskytnout ho uživateli pro ladění (kódu Noir).
1 const pubKey = await recoverPublicKey({2 hash,3 signature4 })Získejte veřejný klíč (opens in a new tab). To je nutné pro funkci Noir ecrecover (opens in a new tab).
1 setSignature(signature)2 setHash(hash)3 setPubKey(pubKey)Nastavte stavové proměnné. Tímto se komponenta překreslí (po ukončení funkce sign) a uživateli se zobrazí aktualizované hodnoty.
1 let proverToml = `Text pro Prover.toml.
1message="${message}"23pubKeyX=${hexToArray(pubKey.slice(4,4+2*32))}4pubKeyY=${hexToArray(pubKey.slice(4+2*32))}Viem nám poskytuje veřejný klíč jako 65bajtový hexadecimální řetězec. První bajt je 0x04, označení verze. Následuje 32 bajtů pro x veřejného klíče a poté 32 bajtů pro y veřejného klíče.
Noir však očekává, že tyto informace získá jako dvě bajtová pole, jedno pro x a jedno pro y. Je snazší ho analyzovat zde na straně klienta než v rámci důkazu s nulovou znalostí.
Všimněte si, že se obecně jedná o dobrou praxi v oblasti nulové znalosti. Kód uvnitř důkazu s nulovou znalostí je nákladný, takže jakékoli zpracování, které lze provést mimo důkaz s nulovou znalostí, by se mělo provádět mimo důkaz s nulovou znalostí.
1signature=${hexToArray(signature.slice(2,-2))}Podpis je také poskytován jako 65bajtový hexadecimální řetězec. Poslední bajt je však nutný pouze k obnovení veřejného klíče. Protože veřejný klíč bude již poskytnut kódu Noir, nepotřebujeme ho k ověření podpisu a kód Noir ho nevyžaduje.
1${accounts.map(accountInProverToml).reduce((a,b) => a+b, "")}2`Poskytněte účty.
1 setProverToml(proverToml)2 }34 return (5 <>6 <h2>Převod</h2>Toto je formát HTML (přesněji JSX (opens in a new tab)) komponenty.
server/noir/src/main.nr
Tento soubor (opens in a new tab) je skutečný kód nulové znalosti.
1use std::hash::pedersen_hash;Pedersen hash (opens in a new tab) je poskytován se standardní knihovnou Noir (opens in a new tab). Důkazy s nulovou znalostí běžně používají tuto hashovací funkci. V aritmetických obvodech (opens in a new tab) se vypočítává mnohem snadněji než standardní hashovací funkce.
1use keccak256::keccak256;2use dep::ecrecover;Tyto dvě funkce jsou externí knihovny definované v souboru Nargo.toml (opens in a new tab). Jsou přesně tím, po čem jsou pojmenovány: funkcí, která vypočítá hash keccak256 (opens in a new tab), a funkcí, která ověřuje podpisy Ethereum a obnovuje adresu Ethereum podepisujícího.
1global ACCOUNT_NUMBER : u32 = 5;Noir je inspirován jazykem Rust (opens in a new tab). Proměnné jsou ve výchozím nastavení konstanty. Takto definujeme globální konfigurační konstanty. Konkrétně ACCOUNT_NUMBER je počet účtů, které ukládáme.
Datové typy s názvem u<číslo> mají daný počet bitů a jsou bez znaménka. Jediné podporované typy jsou u8, u16, u32, u64 a u128.
1global FLAT_ACCOUNT_FIELDS : u32 = 2;Tato proměnná se používá pro Pedersen hash účtů, jak je vysvětleno níže.
1global MESSAGE_LENGTH : u32 = 100;Jak bylo vysvětleno výše, délka zprávy je pevná. Je zde specifikována.
1global ASCII_MESSAGE_LENGTH : [u8; 3] = [0x31, 0x30, 0x30];2global HASH_BUFFER_SIZE : u32 = 26+3+MESSAGE_LENGTH;Podpisy EIP-191 (opens in a new tab) vyžadují vyrovnávací paměť s 26bajtovou předponou, za níž následuje délka zprávy v ASCII a nakonec samotná zpráva.
1struct Account {2 balance: u128,3 address: Field,4 nonce: u32,5}Informace, které ukládáme o účtu. Field (opens in a new tab) je číslo, typicky až 253 bitů, které lze použít přímo v aritmetickém obvodu (opens in a new tab), který implementuje důkaz s nulovou znalostí. Zde používáme Field k uložení 160bitové adresy Ethereum.
1struct TransferTxn {2 from: Field,3 to: Field,4 amount: u128,5 nonce: u326}Informace, které ukládáme pro převodní transakci.
1fn flatten_account(account: Account) -> [Field; FLAT_ACCOUNT_FIELDS] {Definice funkce. Parametrem jsou informace o účtu. Výsledkem je pole proměnných Field, jejichž délka je FLAT_ACCOUNT_FIELDS.
1 let flat = [2 account.address,3 ((account.balance << 32) + account.nonce.into()).into(),4 ];První hodnota v poli je adresa účtu. Druhá zahrnuje jak zůstatek, tak nonce. Volání .into() změní číslo na datový typ, kterým má být. account.nonce je hodnota u32, ale aby ji bylo možné přičíst k hodnotě account.balance << 32, která je u128, musí být u128. To je první .into(). Druhý převádí výsledek u128 na Field, aby se vešel do pole.
1 flat2}V jazyce Noir mohou funkce vracet hodnotu pouze na konci (neexistuje předčasné vrácení). Chcete-li zadat návratovou hodnotu, vyhodnotíte ji těsně před uzavírací závorkou funkce.
1fn flatten_accounts(accounts: [Account; ACCOUNT_NUMBER]) -> [Field; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER] {Tato funkce převede pole účtů na pole Field, které lze použít jako vstup do Petersen Hash.
1 let mut flat: [Field; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER] = [0; FLAT_ACCOUNT_FIELDS*ACCOUNT_NUMBER];Takto se určuje měnitelná proměnná, tj. ne konstanta. Proměnné v Noir musí mít vždy hodnotu, proto tuto proměnnou inicializujeme na samé nuly.
1 for i in 0..ACCOUNT_NUMBER {Toto je smyčka for. Všimněte si, že hranice jsou konstanty. Smyčky Noir musí mít své hranice známé v době kompilace. Důvodem je, že aritmetické obvody nepodporují řízení toku. Při zpracování smyčky for kompilátor jednoduše vloží kód dovnitř několikrát, jednou pro každou iteraci.
1 let fields = flatten_account(accounts[i]);2 for j in 0..FLAT_ACCOUNT_FIELDS {3 flat[i*FLAT_ACCOUNT_FIELDS + j] = fields[j];4 }5 }67 flat8}910fn hash_accounts(accounts: [Account; ACCOUNT_NUMBER]) -> Field {11 pedersen_hash(flatten_accounts(accounts))12}Zobrazit všeNakonec jsme se dostali k funkci, která hashuje pole účtů.
1fn find_account(accounts: [Account; ACCOUNT_NUMBER], address: Field) -> u32 {2 let mut account : u32 = ACCOUNT_NUMBER;34 for i in 0..ACCOUNT_NUMBER {5 if accounts[i].address == address {6 account = i;7 }8 }Tato funkce najde účet se specifickou adresou. Tato funkce by byla ve standardním kódu strašně neefektivní, protože iteruje přes všechny účty, i když už našla adresu.
V důkazech s nulovou znalostí však neexistuje žádné řízení toku. Pokud někdy potřebujeme zkontrolovat podmínku, musíme ji zkontrolovat pokaždé.
Podobná věc se děje s příkazy if. Příkaz if ve smyčce výše je přeložen do těchto matematických příkazů.
výsledekpodmínky = účty[i].adresa == adresa // jedna, pokud se rovnají, jinak nula
účetnový = výsledekpodmínky*i + (1-výsledekpodmínky)*účetstarý
1 assert (account < ACCOUNT_NUMBER, f"{address} nemá účet");23 account4}Funkce assert (opens in a new tab) způsobí pád důkazu s nulovou znalostí, pokud je tvrzení nepravdivé. V tomto případě, pokud nemůžeme najít účet s příslušnou adresou. K nahlášení adresy použijeme formátovací řetězec (opens in a new tab).
1fn apply_transfer_txn(accounts: [Account; ACCOUNT_NUMBER], txn: TransferTxn) -> [Account; ACCOUNT_NUMBER] {Tato funkce aplikuje převodní transakci a vrací nové pole účtů.
1 let from = find_account(accounts, txn.from);2 let to = find_account(accounts, txn.to);34 let (txnFrom, txnAmount, txnNonce, accountNonce) =5 (txn.from, txn.amount, txn.nonce, accounts[from].nonce);V Noir nemůžeme přistupovat k prvkům struktury uvnitř formátovacího řetězce, proto si vytvoříme použitelnou kopii.
1 assert (accounts[from].balance >= txn.amount,2 f"{txnFrom} nemá {txnAmount} finney");34 assert (accounts[from].nonce == txn.nonce,5 f"Transakce má nonce {txnNonce}, ale očekává se, že účet použije {accountNonce}");Toto jsou dvě podmínky, které by mohly způsobit neplatnost transakce.
1 let mut newAccounts = accounts;23 newAccounts[from].balance -= txn.amount;4 newAccounts[from].nonce += 1;5 newAccounts[to].balance += txn.amount;67 newAccounts8}Vytvořte nové pole účtů a poté ho vraťte.
1fn readAddress(messageBytes: [u8; MESSAGE_LENGTH]) -> FieldTato funkce čte adresu ze zprávy.
1{2 let mut result : Field = 0;34 for i in 7..47 {Adresa má vždy 20 bajtů (tj. 40 hexadecimálních číslic) a začíná na znaku #7.
1 result *= 0x10;2 if messageBytes[i] >= 48 & messageBytes[i] <= 57 { // 0-93 result += (messageBytes[i]-48).into();4 }5 if messageBytes[i] >= 65 & messageBytes[i] <= 70 { // A-F6 result += (messageBytes[i]-65+10).into()7 }8 if messageBytes[i] >= 97 & messageBytes[i] <= 102 { // a-f9 result += (messageBytes[i]-97+10).into()10 } 11 } 1213 result14}1516fn readAmountAndNonce(messageBytes: [u8; MESSAGE_LENGTH]) -> (u128, u32)Zobrazit všePřečtěte částku a nonce ze zprávy.
1{2 let mut amount : u128 = 0;3 let mut nonce: u32 = 0;4 let mut stillReadingAmount: bool = true;5 let mut lookingForNonce: bool = false;6 let mut stillReadingNonce: bool = false;Ve zprávě je první číslo za adresou částka finney (tj. tisícina ETH) k převodu. Druhé číslo je nonce. Jakýkoli text mezi nimi je ignorován.
1 for i in 48..MESSAGE_LENGTH {2 if messageBytes[i] >= 48 & messageBytes[i] <= 57 { // 0-93 let digit = (messageBytes[i]-48);45 if stillReadingAmount {6 amount = amount*10 + digit.into();7 }89 if lookingForNonce { // Právě jsme to našli10 stillReadingNonce = true;11 lookingForNonce = false;12 }1314 if stillReadingNonce {15 nonce = nonce*10 + digit.into();16 }17 } else {18 if stillReadingAmount {19 stillReadingAmount = false;20 lookingForNonce = true;21 }22 if stillReadingNonce {23 stillReadingNonce = false;24 }25 }26 }2728 (amount, nonce)29}Zobrazit všeVrácení n-tice (opens in a new tab) je v Noir způsob, jak vrátit více hodnot z funkce.
1fn readTransferTxn(message: str<MESSAGE_LENGTH>) -> TransferTxn 2{3 let mut txn: TransferTxn = TransferTxn { from: 0, to: 0, amount:0, nonce:0 };4 let messageBytes = message.as_bytes();56 txn.to = readAddress(messageBytes);7 let (amount, nonce) = readAmountAndNonce(messageBytes);8 txn.amount = amount;9 txn.nonce = nonce;1011 txn12}Zobrazit všeTato funkce převede zprávu na bajty a poté převede částky na TransferTxn.
1// Ekvivalent hashMessage od Viem2// https://viem.sh/docs/utilities/hashMessage#hashmessage3fn hashMessage(message: str<MESSAGE_LENGTH>) -> [u8;32] {Pedersen Hash jsme mohli použít pro účty, protože se hashují pouze v rámci důkazu s nulovou znalostí. V tomto kódu však musíme zkontrolovat podpis zprávy, který je generován prohlížečem. K tomu je třeba dodržet formát podepisování Ethereum v EIP 191 (opens in a new tab). To znamená, že musíme vytvořit kombinovanou vyrovnávací paměť se standardní předponou, délkou zprávy v ASCII a samotnou zprávou a k jejímu hashování použít standardní keccak256 z Etherea.
1 // Předpona ASCII2 let prefix_bytes = [3 0x19, // \x194 0x45, // 'E'5 0x74, // 't'6 0x68, // 'h'7 0x65, // 'e'8 0x72, // 'r'9 0x65, // 'e'10 0x75, // 'u'11 0x6D, // 'm'12 0x20, // ' '13 0x53, // 'S'14 0x69, // 'i'15 0x67, // 'g'16 0x6E, // 'n'17 0x65, // 'e'18 0x64, // 'd'19 0x20, // ' '20 0x4D, // 'M'21 0x65, // 'e'22 0x73, // 's'23 0x73, // 's'24 0x61, // 'a'25 0x67, // 'g'26 0x65, // 'e'27 0x3A, // ':'28 0x0A // '\n'29 ];Zobrazit všeAby se předešlo případům, kdy aplikace požádá uživatele o podepsání zprávy, kterou lze použít jako transakci nebo pro jiný účel, EIP 191 stanoví, že všechny podepsané zprávy začínají znakem 0x19 (není to platný znak ASCII), za nímž následuje Ethereum Signed Message: a nový řádek.
1 let mut buffer: [u8; HASH_BUFFER_SIZE] = [0u8; HASH_BUFFER_SIZE];2 for i in 0..26 {3 buffer[i] = prefix_bytes[i];4 }56 let messageBytes : [u8; MESSAGE_LENGTH] = message.as_bytes();78 if MESSAGE_LENGTH <= 9 {9 for i in 0..1 {10 buffer[i+26] = ASCII_MESSAGE_LENGTH[i];11 }1213 for i in 0..MESSAGE_LENGTH {14 buffer[i+26+1] = messageBytes[i];15 }16 }1718 if MESSAGE_LENGTH >= 10 & MESSAGE_LENGTH <= 99 {19 for i in 0..2 {20 buffer[i+26] = ASCII_MESSAGE_LENGTH[i];21 }2223 for i in 0..MESSAGE_LENGTH {24 buffer[i+26+2] = messageBytes[i];25 }26 }2728 if MESSAGE_LENGTH >= 100 {29 for i in 0..3 {30 buffer[i+26] = ASCII_MESSAGE_LENGTH[i];31 }3233 for i in 0..MESSAGE_LENGTH {34 buffer[i+26+3] = messageBytes[i];35 }36 }3738 assert(MESSAGE_LENGTH < 1000, "Zprávy s délkou přes tři číslice nejsou podporovány");Zobrazit všeZpracujte délky zpráv až do 999 a selžete, pokud je větší. Tento kód jsem přidal, i když délka zprávy je konstanta, protože to usnadňuje její změnu. V produkčním systému byste pravděpodobně předpokládali, že se MESSAGE_LENGTH nemění kvůli lepšímu výkonu.
1 keccak256::keccak256(buffer, HASH_BUFFER_SIZE)2}Použijte standardní funkci Ethereum keccak256.
1fn signatureToAddressAndHash(2 message: str<MESSAGE_LENGTH>, 3 pubKeyX: [u8; 32],4 pubKeyY: [u8; 32],5 signature: [u8; 64]6 ) -> (Field, Field, Field) // adresa, prvních 16 bajtů hashe, posledních 16 bajtů hashe 7{Tato funkce ověřuje podpis, což vyžaduje hash zprávy. Poté nám poskytne adresu, která jej podepsala, a hash zprávy. Hash zprávy je dodáván ve dvou hodnotách Field, protože se s nimi ve zbytku programu snadněji pracuje než s bajtovým polem.
Musíme použít dvě hodnoty Field, protože výpočty pole se provádějí modulo (opens in a new tab) velkého čísla, ale toto číslo je obvykle menší než 256 bitů (jinak by bylo obtížné provádět tyto výpočty v EVM).
1 let hash = hashMessage(message);23 let mut (hash1, hash2) = (0,0);45 for i in 0..16 {6 hash1 = hash1*256 + hash[31-i].into();7 hash2 = hash2*256 + hash[15-i].into();8 }Určete hash1 a hash2 jako měnitelné proměnné a zapište do nich hash bajt po bajtu.
1 (2 ecrecover::ecrecover(pubKeyX, pubKeyY, signature, hash), Je to podobné jako u ecrecover v Solidity (opens in a new tab), se dvěma důležitými rozdíly:
- Pokud podpis není platný, volání selže s
asserta program se přeruší. - Zatímco veřejný klíč lze obnovit z podpisu a hashe, jedná se o zpracování, které lze provést externě, a proto se nevyplatí ho provádět v rámci důkazu s nulovou znalostí. Pokud se nás zde někdo pokusí podvést, ověření podpisu se nezdaří.
1 hash1,2 hash23 )4}56fn main(7 accounts: [Account; ACCOUNT_NUMBER],8 message: str<MESSAGE_LENGTH>,9 pubKeyX: [u8; 32],10 pubKeyY: [u8; 32],11 signature: [u8; 64],12 ) -> pub (13 Field, // Hash pole starých účtů14 Field, // Hash pole nových účtů15 Field, // Prvních 16 bajtů hashe zprávy16 Field, // Posledních 16 bajtů hashe zprávy17 )Zobrazit všeNakonec se dostáváme k funkci main. Musíme dokázat, že máme transakci, která platně mění hash účtů ze staré hodnoty na novou. Také musíme dokázat, že má tento specifický hash transakce, aby osoba, která ji odeslala, věděla, že její transakce byla zpracována.
1{2 let mut txn = readTransferTxn(message);Potřebujeme, aby txn byla měnitelná, protože adresu odesílatele nečteme ze zprávy, ale z podpisu.
1 let (fromAddress, txnHash1, txnHash2) = signatureToAddressAndHash(2 message,3 pubKeyX,4 pubKeyY,5 signature);67 txn.from = fromAddress;89 let newAccounts = apply_transfer_txn(accounts, txn);1011 (12 hash_accounts(accounts),13 hash_accounts(newAccounts),14 txnHash1,15 txnHash216 )17}Zobrazit všeFáze 2 – Přidání serveru
Ve druhé fázi přidáme server, který přijímá a implementuje převodní transakce z prohlížeče.
Chcete-li to vidět v akci:
-
Zastavte Vite, pokud běží.
-
Stáhněte si větev, která obsahuje server, a ujistěte se, že máte všechny potřebné moduly.
1git checkout 02-add-server2cd client3npm install4cd ../server5npm installNení třeba kompilovat kód Noir, je to stejný kód, který jste použili pro 1. fázi.
-
Spusťte server.
1npm run start -
V samostatném okně příkazového řádku spusťte Vite, abyste mohli obsluhovat kód prohlížeče.
1cd client2npm run dev -
Přejděte na klientský kód na adrese http://localhost:5173 (opens in a new tab)
-
Než budete moci vydat transakci, musíte znát nonce a také částku, kterou můžete odeslat. Chcete-li získat tyto informace, klikněte na Aktualizovat údaje o účtu a podepište zprávu.
Máme zde dilema. Na jedné straně nechceme podepisovat zprávu, kterou lze znovu použít (opakovací útok (opens in a new tab)), což je důvod, proč chceme mít nonce. Nicméně ještě nemáme nonce. Řešením je zvolit nonce, které lze použít pouze jednou a které již máme na obou stranách, například aktuální čas.
Problém s tímto řešením je, že čas nemusí být dokonale synchronizován. Takže místo toho podepíšeme hodnotu, která se mění každou minutu. To znamená, že naše okno zranitelnosti vůči opakovacím útokům je maximálně jedna minuta. Vzhledem k tomu, že v produkci bude podepsaný požadavek chráněn protokolem TLS a že druhá strana tunelu – server – již může sdělit zůstatek a nonce (musí je znát, aby mohl fungovat), jedná se o přijatelné riziko.
-
Jakmile prohlížeč získá zpět zůstatek a nonce, zobrazí formulář pro převod. Vyberte cílovou adresu a částku a klikněte na Převod. Podepište tento požadavek.
-
Chcete-li zobrazit převod, buď Aktualizujte údaje o účtu, nebo se podívejte do okna, kde spouštíte server. Server protokoluje stav při každé změně.
1ori@CryptoDocGuy:~/x/250911-zk-bank/server$ npm run start23> server@1.0.0 start4> node --experimental-json-modules index.mjs56Listening on port 30007Txn send 0x90F79bf6EB2c4f870365E785982E1f101E93b906 36000 finney (milliEth) 0 processed8New state:90xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 64000 (1)100x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 100000 (0)110x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)120x90F79bf6EB2c4f870365E785982E1f101E93b906 has 136000 (0)130x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)14Txn send 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 7200 finney (milliEth) 1 processed15New state:160xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 56800 (2)170x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 107200 (0)180x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)190x90F79bf6EB2c4f870365E785982E1f101E93b906 has 136000 (0)200x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)21Txn send 0x90F79bf6EB2c4f870365E785982E1f101E93b906 3000 finney (milliEth) 2 processed22New state:230xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 has 53800 (3)240x70997970C51812dc3A010C7d01b50e0d17dc79C8 has 107200 (0)250x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC has 100000 (0)260x90F79bf6EB2c4f870365E785982E1f101E93b906 has 139000 (0)270x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 has 100000 (0)Zobrazit vše
server/index.mjs
Tento soubor (opens in a new tab) obsahuje proces serveru a interaguje s kódem Noir na main.nr (opens in a new tab). Zde je vysvětlení zajímavých částí.
1import { Noir } from '@noir-lang/noir_js'Knihovna noir.js (opens in a new tab) propojuje kód JavaScriptu a kód Noir.
1const circuit = JSON.parse(await fs.readFile("./noir/target/zkBank.json"))2const noir = new Noir(circuit)Načtěte aritmetický obvod – kompilovaný program Noir, který jsme vytvořili v předchozí fázi – a připravte se na jeho spuštění.
1// Informace o účtu poskytujeme pouze v odpovědi na podepsaný požadavek2const accountInformation = async signature => {3 const fromAddress = await recoverAddress({4 hash: hashMessage("Získat data účtu " + Math.floor((new Date().getTime())/60000)),5 signature6 })Pro poskytnutí informací o účtu potřebujeme pouze podpis. Důvodem je, že již víme, jaká bude zpráva, a tedy i hash zprávy.
1const processMessage = async (message, signature) => {Zpracujte zprávu a proveďte transakci, kterou kóduje.
1 // Získat veřejný klíč2 const pubKey = await recoverPublicKey({3 hash,4 signature5 })Nyní, když spouštíme JavaScript na serveru, můžeme získat veřejný klíč tam, spíše než na klientovi.
1 let noirResult2 try {3 noirResult = await noir.execute({4 message,5 signature: signature.slice(2,-2).match(/.{2}/g).map(x => `0x${x}`),6 pubKeyX,7 pubKeyY,8 accounts: Accounts9 })Zobrazit všenoir.execute spouští program Noir. Parametry jsou ekvivalentní těm, které jsou uvedeny v souboru Prover.toml (opens in a new tab). Všimněte si, že dlouhé hodnoty jsou poskytovány jako pole hexadecimálních řetězců (["0x60", "0xA7"]), nikoli jako jediná hexadecimální hodnota (0x60A7), jak to dělá Viem.
1 } catch (err) {2 console.log(`Chyba Noir: ${err}`)3 throw Error("Neplatná transakce, nebyla zpracována")4 }Pokud dojde k chybě, zachyťte ji a poté předejte zjednodušenou verzi klientovi.
1 Accounts[fromAccountNumber].nonce++2 Accounts[fromAccountNumber].balance -= amount3 Accounts[toAccountNumber].balance += amountProveďte transakci. Už jsme to udělali v kódu Noir, ale je snazší to udělat znovu zde, než extrahovat výsledek odtamtud.
1let Accounts = [2 {3 address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",4 balance: 5000,5 nonce: 0,6 },Počáteční struktura účtů.
Fáze 3 – Chytré kontrakty Ethereum
-
Zastavte procesy serveru a klienta.
-
Stáhněte si větev s chytrými kontrakty a ujistěte se, že máte všechny potřebné moduly.
1git checkout 03-smart-contracts2cd client3npm install4cd ../server5npm install -
Spusťte
anvilv samostatném okně příkazového řádku. -
Vygenerujte ověřovací klíč a ověřovač Solidity, poté zkopírujte kód ověřovače do projektu Solidity.
1cd noir2bb write_vk -b ./target/zkBank.json -o ./target --oracle_hash keccak3bb write_solidity_verifier -k ./target/vk -o ./target/Verifier.sol4cp target/Verifier.sol ../../smart-contracts/src -
Přejděte na chytré kontrakty a nastavte proměnné prostředí pro použití blockchainu
anvil.1cd ../../smart-contracts2export ETH_RPC_URL=http://localhost:85453ETH_PRIVATE_KEY=ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 -
Nasaďte
Verifier.sola uložte adresu do proměnné prostředí.1VERIFIER_ADDRESS=`forge create src/Verifier.sol:HonkVerifier --private-key $ETH_PRIVATE_KEY --optimize --broadcast | awk '/Deployed to:/ {print $3}'`2echo $VERIFIER_ADDRESS -
Nasaďte kontrakt
ZkBank.1ZKBANK_ADDRESS=`forge create ZkBank --private-key $ETH_PRIVATE_KEY --broadcast --constructor-args $VERIFIER_ADDRESS 0x199aa62af8c1d562a6ec96e66347bf3240ab2afb5d022c895e6bf6a5e617167b | awk '/Deployed to:/ {print $3}'`2echo $ZKBANK_ADDRESSHodnota
0x199..67bje Pederson hash počátečního stavuÚčtů. Pokud tento počáteční stav vserver/index.mjsupravíte, můžete spustit transakci a zobrazit počáteční hash hlášený důkazem s nulovou znalostí. -
Spusťte server.
1cd ../server2npm run start -
Spusťte klienta v jiném okně příkazového řádku.
1cd client2npm run dev -
Spusťte nějaké transakce.
-
Chcete-li ověřit, že se stav změnil na blockchainu, restartujte proces serveru. Podívejte se, že
ZkBankjiž nepřijímá transakce, protože původní hodnota hashe v transakcích se liší od hodnoty hashe uložené na blockchainu.Toto je typ očekávané chyby.
1ori@CryptoDocGuy:~/x/250911-zk-bank/server$ npm run start23> server@1.0.0 start4> node --experimental-json-modules index.mjs56Listening on port 30007Verification error: ContractFunctionExecutionError: The contract function "processTransaction" reverted with the following reason:8Wrong old state hash910Contract Call:11 address: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F051212 function: processTransaction(bytes _proof, bytes32[] _publicInputs)13 args: (0x0000000000000000000000000000000000000000000000042ab5d6d1986846cf00000000000000000000000000000000000000000000000b75c020998797da7800000000000000000000000000000000000000000000000)Zobrazit vše
server/index.mjs
Změny v tomto souboru se týkají především vytvoření skutečného důkazu a jeho odeslání na blockchain.
1import { exec } from 'child_process'2import util from 'util'34const execPromise = util.promisify(exec)Musíme použít balíček Barretenberg (opens in a new tab) k vytvoření skutečného důkazu k odeslání na blockchain. Tento balíček můžeme použít buď spuštěním rozhraní příkazového řádku (bb), nebo použitím knihovny JavaScript, bb.js (opens in a new tab). Knihovna JavaScript je mnohem pomalejší než nativní spouštění kódu, takže zde používáme exec (opens in a new tab) pro použití příkazového řádku.
Všimněte si, že pokud se rozhodnete použít bb.js, musíte použít verzi, která je kompatibilní s verzí Noir, kterou používáte. V době psaní tohoto článku aktuální verze Noir (1.0.0-beta.11) používá bb.js verze 0.87.
1const zkBankAddress = process.env.ZKBANK_ADDRESS || "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"Zde uvedená adresa je ta, kterou získáte, když začnete s čistým anvilem a budete postupovat podle výše uvedených pokynů.
1const walletClient = createWalletClient({ 2 chain: anvil, 3 transport: http(), 4 account: privateKeyToAccount("0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6")5})Tento privátní klíč je jedním z výchozích předem financovaných účtů v anvil.
1const generateProof = async (witness, fileID) => {Vygenerujte důkaz pomocí spustitelného souboru bb.
1 const fname = `witness-${fileID}.gz` 2 await fs.writeFile(fname, witness)Zapište svědka do souboru.
1 await execPromise(`bb prove -b ./noir/target/zkBank.json -w ${fname} -o ${fileID} --oracle_hash keccak --output_format fields`)Vytvořte důkaz. Tento krok také vytvoří soubor s veřejnými proměnnými, ale ten nepotřebujeme. Tyto proměnné jsme již získali z noir.execute.
1 const proof = "0x" + JSON.parse(await fs.readFile(`./${fileID}/proof_fields.json`)).reduce((a,b) => a+b, "").replace(/0x/g, "")Důkaz je pole JSON hodnot Field, z nichž každá je reprezentována jako hexadecimální hodnota. Musíme ho však odeslat v transakci jako jedinou hodnotu bytes, kterou Viem reprezentuje velkým hexadecimálním řetězcem. Zde měníme formát zřetězením všech hodnot, odstraněním všech 0x a následným přidáním jednoho na konec.
1 await execPromise(`rm -r ${fname} ${fileID}`)23 return proof4}Vyčistěte a vraťte důkaz.
1const processMessage = async (message, signature) => {2 .3 .4 .56 const publicFields = noirResult.returnValue.map(x=>'0x' + x.slice(2).padStart(64, "0"))Veřejná pole musí být pole 32bajtových hodnot. Jelikož jsme však potřebovali rozdělit hash transakce mezi dvě hodnoty Field, zobrazuje se jako 16bajtová hodnota. Zde přidáváme nuly, aby Viem pochopil, že se jedná o 32 bajtů.
1 const proof = await generateProof(noirResult.witness, `${fromAddress}-${nonce}`)Každá adresa používá každou nonce pouze jednou, takže můžeme použít kombinaci fromAddress a nonce jako jedinečný identifikátor pro soubor svědka a výstupní adresář.
1 try {2 await zkBank.write.processTransaction([3 proof, publicFields])4 } catch (err) {5 console.log(`Chyba ověření: ${err}`)6 throw Error("Transakci nelze ověřit na blockchainu")7 }8 .9 .10 .11}Zobrazit všeOdešlete transakci do řetězce.
smart-contracts/src/ZkBank.sol
Toto je onchain kód, který přijímá transakci.
1// SPDX-License-Identifier: MIT23pragma solidity >=0.8.21;45import {HonkVerifier} from "./Verifier.sol";67contract ZkBank {8 HonkVerifier immutable myVerifier;9 bytes32 currentStateHash;1011 constructor(address _verifierAddress, bytes32 _initialStateHash) {12 currentStateHash = _initialStateHash;13 myVerifier = HonkVerifier(_verifierAddress);14 }Zobrazit všeKód na blockchainu musí sledovat dvě proměnné: ověřovač (samostatný kontrakt vytvořený nargem) a aktuální hash stavu.
1 event TransactionProcessed(2 bytes32 indexed transactionHash,3 bytes32 oldStateHash,4 bytes32 newStateHash5 );Pokaždé, když se stav změní, vydáme událost TransactionProcessed.
1 function processTransaction(2 bytes calldata _proof,3 bytes32[] calldata _publicFields4 ) public {Tato funkce zpracovává transakce. Získá důkaz (jako bajty) a veřejné vstupy (jako pole bytes32) ve formátu, který ověřovatel vyžaduje (aby se minimalizovalo zpracování na blockchainu a tím i náklady na gas).
1 require(_publicInputs[0] == currentStateHash,2 "Špatný starý hash stavu");Důkaz s nulovou znalostí musí být o tom, že transakce se mění z našeho současného hashe na nový.
1 myVerifier.verify(_proof, _publicFields);Zavolejte kontrakt ověřovače, abyste ověřili důkaz s nulovou znalostí. Tento krok vrátí transakci, pokud je důkaz s nulovou znalostí nesprávný.
1 currentStateHash = _publicFields[1];23 emit TransactionProcessed(4 _publicFields[2]<<128 | _publicFields[3],5 _publicFields[0],6 _publicFields[1]7 );8 }9}Zobrazit všePokud je vše v pořádku, aktualizujte hash stavu na novou hodnotu a vydejte událost TransactionProcessed.
Zneužití centralizovanou součástí
Informační bezpečnost se skládá ze tří atributů:
- Důvěrnost, uživatelé nemohou číst informace, ke kterým nejsou oprávněni.
- Integrita, informace nemohou být měněny jinak než oprávněnými uživateli oprávněným způsobem.
- Dostupnost, oprávnění uživatelé mohou systém používat.
V tomto systému je integrita zajištěna prostřednictvím důkazů s nulovou znalostí. Dostupnost je mnohem obtížnější zaručit a důvěrnost je nemožná, protože banka musí znát zůstatek každého účtu a všechny transakce. Neexistuje způsob, jak zabránit entitě, která má informace, v jejich sdílení.
Možná by bylo možné vytvořit skutečně důvěrnou banku pomocí neviditelných adres (opens in a new tab), ale to je nad rámec tohoto článku.
Nepravdivé informace
Jedním ze způsobů, jak může server porušit integritu, je poskytnutí nepravdivých informací, když jsou požadována data (opens in a new tab).
K vyřešení tohoto problému můžeme napsat druhý program Noir, který přijímá účty jako soukromý vstup a adresu, pro kterou jsou informace požadovány, jako veřejný vstup. Výstupem je zůstatek a nonce této adresy a hash účtů.
Tento důkaz samozřejmě nelze ověřit na blockchainu, protože nechceme zveřejňovat nonce a zůstatky na blockchainu. Může však být ověřen klientským kódem spuštěným v prohlížeči.
Vynucené transakce
Obvyklým mechanismem pro zajištění dostupnosti a prevenci cenzury na L2 jsou vynucené transakce (opens in a new tab). Ale vynucené transakce se nekombinují s důkazy s nulovou znalostí. Server je jedinou entitou, která může ověřovat transakce.
Můžeme upravit smart-contracts/src/ZkBank.sol tak, aby přijímal vynucené transakce a zabránil serveru měnit stav, dokud nebudou zpracovány. To nás však vystavuje jednoduchému útoku typu denial-of-service. Co když je vynucená transakce neplatná, a proto ji nelze zpracovat?
Řešením je mít důkaz s nulovou znalostí, že vynucená transakce je neplatná. To dává serveru tři možnosti:
- Zpracovat vynucenou transakci a poskytnout důkaz s nulovou znalostí, že byla zpracována, a nový hash stavu.
- Odmítnout vynucenou transakci a poskytnout kontraktu důkaz s nulovou znalostí, že transakce je neplatná (neznámá adresa, špatné nonce nebo nedostatečný zůstatek).
- Ignorovat vynucenou transakci. Neexistuje způsob, jak donutit server, aby transakci skutečně zpracoval, ale znamená to, že celý systém je nedostupný.
Dluhopisy dostupnosti
V reálné implementaci by pravděpodobně existoval nějaký druh motivace k zisku pro udržení serveru v provozu. Tuto pobídku můžeme posílit tím, že server zveřejní dluhopis dostupnosti, který může kdokoli spálit, pokud vynucená transakce není zpracována v určitém období.
Špatný kód Noir
Normálně, aby lidé důvěřovali chytrému kontraktu, nahrajeme zdrojový kód do prohlížeče bloků (opens in a new tab). V případě důkazů s nulovou znalostí to však nestačí.
Verifier.sol obsahuje ověřovací klíč, který je funkcí programu Noir. Tento klíč nám však neříká, jaký byl program Noir. Chcete-li mít skutečně důvěryhodné řešení, musíte nahrát program Noir (a verzi, která ho vytvořila). V opačném případě by důkazy s nulovou znalostí mohly odrážet jiný program, program se zadními vrátky.
Dokud nám prohlížeče bloků neumožní nahrávat a ověřovat programy Noir, měli byste to dělat sami (nejlépe na IPFS). Poté budou moci zkušení uživatelé stáhnout zdrojový kód, sami ho zkompilovat, vytvořit Verifier.sol a ověřit, že je identický s tím na blockchainu.
Závěr
Aplikace typu Plasma vyžadují centralizovanou komponentu jako úložiště informací. To otevírá potenciální zranitelnosti, ale na oplátku nám to umožňuje zachovat soukromí způsoby, které na samotném blockchainu nejsou dostupné. S důkazy s nulovou znalostí můžeme zajistit integritu a případně učinit ekonomicky výhodným, aby kdokoli, kdo provozuje centralizovanou komponentu, udržoval dostupnost.
Více z mé práce najdete zde (opens in a new tab).
Poděkování
- Josh Crites si přečetl návrh tohoto článku a pomohl mi s ošemetným problémem Noir.
Za zbývající chyby jsem zodpovědný já.
Stránka naposledy aktualizována: 28. října 2025