Kutumia zero-knowledge kwa hali ya siri
Hakuna siri kwenye mnyororo wa bloku. Kila kitu kinachowekwa kwenye mnyororo wa bloku kiko wazi kwa kila mtu kusoma. Hii ni muhimu, kwa sababu mnyororo wa bloku unategemea mtu yeyote kuweza kuuthibitisha. Hata hivyo, mara nyingi michezo hutegemea hali ya siri. Kwa mfano, mchezo wa minesweeper (opens in a new tab) hauna maana kabisa ikiwa unaweza tu kwenda kwenye kichunguzi cha mnyororo wa bloku na kuona ramani.
Suluhisho rahisi zaidi ni kutumia sehemu ya seva kushikilia hali ya siri. Hata hivyo, sababu tunayotumia mnyororo wa bloku ni kuzuia udanganyifu na msanidi programu wa mchezo. Tunahitaji kuhakikisha uaminifu wa sehemu ya seva. Seva inaweza kutoa hashi ya hali, na kutumia uthibitisho wa zero-knowledge kuthibitisha kuwa hali iliyotumiwa kuhesabu matokeo ya hatua ndiyo sahihi.
Baada ya kusoma makala hii utajua jinsi ya kuunda aina hii ya seva inayoshikilia hali ya siri, wateja wa kuonyesha hali, na sehemu ya onchain kwa mawasiliano kati ya hizi mbili. Zana kuu tutakazotumia zitakuwa:
| Zana | Madhumuni | Imethibitishwa kwenye toleo |
|---|---|---|
| Zokrates (opens in a new tab) | Uthibitisho wa zero-knowledge na uthibitishaji wake | 1.1.9 |
| Typescript (opens in a new tab) | Lugha ya programu kwa seva na wateja | 5.4.2 |
| Nodi (opens in a new tab) | Kuendesha seva | 20.18.2 |
| Viem (opens in a new tab) | Mawasiliano na Mnyororo wa bloku | 2.9.20 |
| MUD (opens in a new tab) | Usimamizi wa data ya Onchain | 2.0.12 |
| React (opens in a new tab) | Kiolesura cha mtumiaji wa Wateja | 18.2.0 |
| Vite (opens in a new tab) | Kutumikia msimbo wa wateja | 4.2.1 |
Mfano wa Minesweeper
Minesweeper (opens in a new tab) ni mchezo unaojumuisha ramani ya siri yenye uwanja wa migodi. Mchezaji anachagua kuchimba katika eneo maalum. Ikiwa eneo hilo lina mgodi, mchezo umeisha. Vinginevyo, mchezaji anapata idadi ya migodi katika viwanja nane vinavyozunguka eneo hilo.
Programu hii imeandikwa kwa kutumia MUD (opens in a new tab), mfumo unaoturuhusu kuhifadhi data onchain kwa kutumia hifadhidata ya ufunguo-thamani (opens in a new tab) na kusawazisha data hiyo kiotomatiki na vipengele vya offchain. Mbali na usawazishaji, MUD hurahisisha kutoa udhibiti wa ufikiaji, na kwa watumiaji wengine kupanua (opens in a new tab) programu yetu bila ruhusa.
Kuendesha mfano wa minesweeper
Ili kuendesha mfano wa minesweeper:
-
Hakikisha una mahitaji ya awali yaliyowekwa (opens in a new tab): Nodi (opens in a new tab), Foundry (opens in a new tab),
git(opens in a new tab),pnpm(opens in a new tab), namprocs(opens in a new tab). -
Kloni hifadhi.
1git clone https://github.com/qbzzt/20240901-secret-state.git -
Sakinisha vifurushi.
1cd 20240901-secret-state/2pnpm install3npm install -g mprocsIkiwa Foundry ilisakinishwa kama sehemu ya
pnpm install, unahitaji kuanzisha upya ganda la mstari wa amri. -
Kusanya mikataba
1cd packages/contracts2forge build3cd ../.. -
Anzisha programu (pamoja na mnyororo wa bloku wa anvil (opens in a new tab)) na subiri.
1mprocsKumbuka kuwa uanzishaji unachukua muda mrefu. Ili kuona maendeleo, kwanza tumia mshale wa chini kusogeza hadi kwenye kichupo cha contracts ili kuona mikataba ya MUD ikisambazwa. Unapopata ujumbe Waiting for file changes…, mikataba imesambazwa na maendeleo zaidi yatatokea kwenye kichupo cha server. Hapo, unasubiri hadi upate ujumbe Verifier address: 0x.....
Ikiwa hatua hii itafanikiwa, utaona skrini ya
mprocs, na michakato tofauti upande wa kushoto na matokeo ya console kwa mchakato uliochaguliwa kwa sasa upande wa kulia.Ikiwa kuna tatizo na
mprocs, unaweza kuendesha michakato minne kwa mikono, kila mmoja katika dirisha lake la mstari wa amri:-
Anvil
1cd packages/contracts2anvil --base-fee 0 --block-time 2 -
Mikataba
1cd packages/contracts2pnpm mud dev-contracts --rpc http://127.0.0.1:8545 -
Seva
1cd packages/server2pnpm start -
Wateja
1cd packages/client2pnpm run dev
-
-
Sasa unaweza kuvinjari kwa wateja (opens in a new tab), bonyeza New Game, na uanze kucheza.
Majedwali
Tunahitaji majedwali kadhaa (opens in a new tab) onchain.
-
Configuration: Jedwali hili ni la pekee, halina ufunguo na rekodi moja tu. Hutumika kushikilia taarifa za usanidi wa mchezo:height: Urefu wa uwanja wa migodiwidth: Upana wa uwanja wa migodinumberOfBombs: Idadi ya mabomu katika kila uwanja wa migodi
-
VerifierAddress: Jedwali hili pia ni la pekee. Hutumika kushikilia sehemu moja ya usanidi, anwani ya mkataba wa mthibitishaji (verifier). Tungeweza kuweka taarifa hizi kwenye jedwali laConfiguration, lakini imewekwa na sehemu tofauti, seva, kwa hivyo ni rahisi kuiweka kwenye jedwali tofauti. -
PlayerGame: Ufunguo ni anwani ya mchezaji. Data ni:gameId: Thamani ya baiti 32 ambayo ni hashi ya ramani ambayo mchezaji anacheza (kitambulisho cha mchezo).win: boolean inayoonyesha kama mchezaji alishinda mchezo.lose: boolean inayoonyesha kama mchezaji alishindwa mchezo.digNumber: idadi ya uchimbaji uliofanikiwa katika mchezo.
-
GamePlayer: Jedwali hili linashikilia ramani ya kinyume, kutokagameIdhadi anwani ya mchezaji. -
Map: Ufunguo ni tupo ya thamani tatu:gameId: Thamani ya baiti 32 ambayo ni hashi ya ramani ambayo mchezaji anacheza (kitambulisho cha mchezo).- kuratibu ya
x - kuratibu ya
y
Thamani ni nambari moja. Ni 255 ikiwa bomu liligunduliwa. Vinginevyo, ni idadi ya mabomu karibu na eneo hilo pamoja na moja. Hatuwezi tu kutumia idadi ya mabomu, kwa sababu kwa chaguo-msingi hifadhi zote katika EVM na thamani zote za safu katika MUD ni sifuri. Tunahitaji kutofautisha kati ya "mchezaji bado hajachimba hapa" na "mchezaji alichimba hapa, na akakuta hakuna mabomu karibu".
Kwa kuongeza, mawasiliano kati ya wateja na seva hufanyika kupitia sehemu ya onchain. Hii pia inatekelezwa kwa kutumia majedwali.
PendingGame: Maombi ambayo hayajashughulikiwa ya kuanza mchezo mpya.PendingDig: Maombi ambayo hayajashughulikiwa ya kuchimba katika eneo maalum katika mchezo maalum. Hili ni jedwali la offchain (opens in a new tab), ikimaanisha kuwa haliandikwi kwenye ghala la EVM, linasomeka tu offchain kwa kutumia matukio.
Utekelezaji na mtiririko wa data
Mtiririko huu unaratibu utekelezaji kati ya wateja, sehemu ya onchain, na seva.
Uanzishaji
Unapoendesha mprocs, hatua hizi hutokea:
-
mprocs(opens in a new tab) inaendesha vipengele vinne:- Anvil (opens in a new tab), ambayo inaendesha mnyororo wa bloku wa ndani
- Mikataba (opens in a new tab), ambayo inakusanya (ikihitajika) na kusambaza mikataba ya MUD
- Wateja (opens in a new tab), ambayo inaendesha Vite (opens in a new tab) ili kuhudumia UI na msimbo wa wateja kwa vivinjari vya wavuti.
- Seva (opens in a new tab), ambayo hufanya vitendo vya seva
-
Kifurushi cha
contractskinasambaza mikataba ya MUD na kisha kinaendesha hati yaPostDeploy.s.sol(opens in a new tab). Hati hii inaweka usanidi. Msimbo kutoka github unabainisha uwanja wa migodi wa 10x5 wenye migodi nane ndani yake (opens in a new tab). -
Seva (opens in a new tab) inaanza kwa kuweka MUD (opens in a new tab). Miongoni mwa mambo mengine, hii inawasha usawazishaji wa data, ili nakala ya majedwali husika iwepo kwenye kumbukumbu ya seva.
-
Seva inasajili kazi ya kutekelezwa wakati jedwali la
Configurationlinabadilika (opens in a new tab). Kazi hii (opens in a new tab) inaitwa baada yaPostDeploy.s.solkutekeleza na kurekebisha jedwali. -
Wakati kazi ya uanzishaji wa seva inapokuwa na usanidi, inaita
zkFunctions(opens in a new tab) ili kuanzisha sehemu ya zero-knowledge ya seva. Hii haiwezi kutokea hadi tupate usanidi kwa sababu kazi za zero-knowledge lazima ziwe na upana na urefu wa uwanja wa migodi kama viwango vya kudumu. -
Baada ya sehemu ya zero-knowledge ya seva kuanzishwa, hatua inayofuata ni kusambaza mkataba wa uthibitishaji wa zero-knowledge kwenye mnyororo wa bloku (opens in a new tab) na kuweka anwani ya mthibitishwa katika MUD.
-
Mwishowe, tunasajili masasisho ili tuone mchezaji anapoomba ama kuanza mchezo mpya (opens in a new tab) au kuchimba kwenye mchezo uliopo (opens in a new tab).
Mchezo mpya
Hivi ndivyo hutokea mchezaji anapoomba mchezo mpya.
-
Ikiwa hakuna mchezo unaoendelea kwa mchezaji huyu, au kuna mmoja lakini una gameId ya sifuri, wateja wanaonyesha kitufe cha mchezo mpya (opens in a new tab). Mtumiaji anapobonyeza kitufe hiki, React inaendesha kazi ya
newGame(opens in a new tab). -
newGame(opens in a new tab) ni wito waSystem. Katika MUD simu zote zinaelekezwa kupitia mkataba waWorld, na katika hali nyingi unaita<namespace>__<function name>. Katika kesi hii, wito ni kwaapp__newGame, ambayo MUD kisha inaelekeza kwanewGamekatikaGameSystem(opens in a new tab). -
Kazi ya onchain inakagua kwamba mchezaji hana mchezo unaoendelea, na ikiwa hakuna inaongeza ombi kwenye jedwali la
PendingGame(opens in a new tab). -
Seva inagundua mabadiliko katika
PendingGamena inaendesha kazi iliyosajiliwa (opens in a new tab). Kazi hii inaitanewGame(opens in a new tab), ambayo kwa upande wake inaitacreateGame(opens in a new tab). -
Jambo la kwanza
createGamehufanya ni kuunda ramani isiyo ya kawaida na idadi inayofaa ya migodi (opens in a new tab). Kisha, inaitamakeMapBorders(opens in a new tab) ili kuunda ramani yenye mipaka tupu, ambayo ni muhimu kwa Zokrates. Mwishowe,createGameinaitacalculateMapHash, ili kupata hashi ya ramani, ambayo hutumiwa kama kitambulisho cha mchezo. -
Kazi ya
newGameinaongeza mchezo mpya kwenyegamesInProgress. -
Jambo la mwisho ambalo seva hufanya ni kuita
app__newGameResponse(opens in a new tab), ambayo iko onchain. Kazi hii iko katikaSystemtofauti,ServerSystem(opens in a new tab), ili kuwezesha udhibiti wa ufikiaji. Udhibiti wa ufikiaji umefafanuliwa katika faili ya usanidi ya MUD (opens in a new tab),mud.config.ts(opens in a new tab).Orodha ya ufikiaji inaruhusu anwani moja tu kuita
System. Hii inazuia ufikiaji wa kazi za seva kwa anwani moja, kwa hivyo hakuna anayeweza kujifanya kuwa seva. -
Sehemu ya onchain inasasisha majedwali husika:
- Unda mchezo katika
PlayerGame. - Weka ramani ya kinyume katika
GamePlayer. - Ondoa ombi kutoka
PendingGame.
- Unda mchezo katika
-
Seva inatambua mabadiliko katika
PendingGame, lakini haifanyi chochote kwa sababuwantsGame(opens in a new tab) ni ya uongo. -
Kwa upande wa wateja
gameRecord(opens in a new tab) imewekwa kwenye ingizo laPlayerGamekwa anwani ya mchezaji. WakatiPlayerGameinapobadilika,gameRecordpia hubadilika. -
Ikiwa kuna thamani katika
gameRecord, na mchezo haujashindwa wala kushindwa, wateja wanaonyesha ramani (opens in a new tab).
Chimba
-
Mchezaji anabonyeza kitufe cha seli ya ramani (opens in a new tab), ambayo inaita kazi ya
dig(opens in a new tab). Kazi hii inaitadigonchain (opens in a new tab). -
Sehemu ya onchain inafanya ukaguzi kadhaa wa kiakili (opens in a new tab), na ikiwa imefanikiwa inaongeza ombi la kuchimba kwenye
PendingDig(opens in a new tab). -
Seva inagundua mabadiliko katika
PendingDig(opens in a new tab). Ikiwa ni halali (opens in a new tab), inaita msimbo wa zero-knowledge (opens in a new tab) (imeelezewa hapa chini) ili kutoa matokeo na uthibitisho kwamba ni halali. -
Seva (opens in a new tab) inaita
digResponse(opens in a new tab) onchain. -
digResponsehufanya mambo mawili. Kwanza, inakagua uthibitisho wa zero knowledge (opens in a new tab). Kisha, ikiwa uthibitisho umekaguliwa, inaitaprocessDigResult(opens in a new tab) ili kushughulikia matokeo. -
processDigResultinakagua ikiwa mchezo umeshindwa (opens in a new tab) au umeshinda (opens in a new tab), na inasasishaRamani, ramani ya onchain (opens in a new tab). -
Mteja huchukua masasisho kiotomatiki na husasisha ramani inayoonyeshwa kwa mchezaji (opens in a new tab), na inapohitajika humwambia mchezaji ikiwa ameshinda au ameshindwa.
Kutumia Zokrates
Katika mtiririko ulioelezwa hapo juu tuliruka sehemu za maarifa-sifuri, tukizichukulia kama sanduku jeusi. Sasa tufungue tuone jinsi msimbo huo umeandikwa.
Kupiga hashi ramani
Tunaweza kutumia msimbo huu wa JavaScript (opens in a new tab) kutekeleza Poseidon (opens in a new tab), kazi ya hashi ya Zokrates tunayotumia. Hata hivyo, ingawa hii ingekuwa haraka zaidi, pia ingekuwa ngumu zaidi kuliko kutumia tu kazi ya hashi ya Zokrates kuifanya. Hii ni mafunzo, na kwa hivyo msimbo umeboreshwa kwa urahisi, si kwa utendaji. Kwa hivyo, tunahitaji programu mbili tofauti za Zokrates, moja ya kuhesabu tu hashi ya ramani (hashi) na nyingine ya kuunda uthibitisho wa maarifa-sifuri wa matokeo ya kuchimba katika eneo kwenye ramani (chimba).
Kazi ya hashi
Hii ni kazi inayokokotoa hashi ya ramani. Tutapitia msimbo huu mstari kwa mstari.
1import "hashes/poseidon/poseidon.zok" as poseidon;2import "utils/pack/bool/pack128.zok" as pack128;Mistari hii miwili inaingiza kazi mbili kutoka kwa maktaba ya kawaida ya Zokrates (opens in a new tab). Kazi ya kwanza (opens in a new tab) ni hashi ya Poseidon (opens in a new tab). Inachukua safu ya vipengele vya uwanja (opens in a new tab) na kurudisha uwanja.
Kipengele cha uwanja katika Zokrates kwa kawaida huwa na urefu usiozidi biti 256, lakini si kwa kiasi kikubwa. Ili kurahisisha msimbo, tunazuia ramani iwe na biti zisizozidi 512, na kupiga hashi safu ya nyanja nne, na katika kila uwanja tunatumia biti 128 tu. Kazi ya pack128 (opens in a new tab) hubadilisha safu ya biti 128 kuwa uwanja kwa kusudi hili.
1 def hashMap(bool[${width+2}][${height+2}] map) -> field {Mstari huu unaanzisha ufafanuzi wa kazi. hashMap inapokea kigezo kimoja kiitwacho map, safu ya pande mbili ya bool(ean). Ukubwa wa ramani ni width+2 kwa height+2 kwa sababu ambazo zimeelezwa hapa chini.
Tunaweza kutumia ${width+2} na ${height+2} kwa sababu programu za Zokrates zimehifadhiwa katika programu hii kama kamba za kiolezo (opens in a new tab). Msimbo kati ya ${ na } unathminiwa na JavaScript, na kwa njia hii programu inaweza kutumika kwa saizi tofauti za ramani. Kigezo cha ramani kina mpaka wa upana wa eneo moja kuzunguka bila mabomu yoyote, ambayo ndiyo sababu tunahitaji kuongeza mbili kwenye upana na urefu.
Thamani ya kurudi ni uwanja ambao una hashi.
1 bool[512] mut map1d = [false; 512];Ramani ni ya pande mbili. Hata hivyo, kazi ya pack128 haifanyi kazi na safu za pande mbili. Kwa hivyo, kwanza tunalaza ramani kuwa safu ya baiti 512, kwa kutumia map1d. Kwa chaguo-msingi vigezo vya Zokrates ni viwango vya kudumu, lakini tunahitaji kugawa thamani kwa safu hii katika kitanzi, kwa hivyo tunakifafanua kama mut (opens in a new tab).
Tunahitaji kuanzisha safu kwa sababu Zokrates haina undefined. Usemi wa [false; 512] unamaanisha safu ya thamani 512 za uongo (opens in a new tab).
1 u32 mut counter = 0;Pia tunahitaji kaunta kutofautisha kati ya biti ambazo tayari tumejaza katika map1d na zile ambazo hatujajaza.
1 for u32 x in 0..${width+2} {Hivi ndivyo unavyotangaza kitanzi cha kwa (opens in a new tab) katika Zokrates. Kitanzi cha Zokrates kwa lazima kiwe na mipaka maalum, kwa sababu ingawa kinaonekana kama kitanzi, mkalimani kwa kweli "huifungua". Usemi ${width+2} ni kiwango cha kudumu cha wakati wa kukusanya kwa sababu width imewekwa na msimbo wa TypeScript kabla ya kuita mkusanyaji.
1 for u32 y in 0..${height+2} {2 map1d[counter] = map[x][y];3 counter = counter+1;4 }5 }Kwa kila eneo kwenye ramani, weka thamani hiyo kwenye safu ya map1d na uongeze kaunta.
1 field[4] hashMe = [2 pack128(map1d[0..128]),3 pack128(map1d[128..256]),4 pack128(map1d[256..384]),5 pack128(map1d[384..512])6 ];pack128 kuunda safu ya thamani nne za uwanja kutoka map1d. Katika Zokrates array[a..b] inamaanisha kipande cha safu kinachoanzia a na kuishia b-1.
1 return poseidon(hashMe);2}Tumia poseidon kubadilisha safu hii kuwa hashi.
Programu ya hashi
Seva inahitaji kuita hashMap moja kwa moja ili kuunda vitambulisho vya mchezo. Hata hivyo, Zokrates inaweza tu kuita kazi ya main kwenye programu kuanza, kwa hivyo tunaunda programu yenye main inayoita kazi ya hashi.
1${hashFragment}23def main(bool[${width+2}][${height+2}] map) -> field {4 return hashMap(map);5}Programu ya kuchimba
Hii ndiyo moyo wa sehemu ya maarifa-sifuri ya programu, ambapo tunatoa uthibitisho unaotumika kuthibitisha matokeo ya kuchimba.
1${hashFragment}23// Idadi ya migodi katika eneo (x,y)4def map2mineCount(bool[${width+2}][${height+2}] map, u32 x, u32 y) -> u8 {5 return if map[x+1][y+1] { 1 } else { 0 };6}Kwa nini mpaka wa ramani
Uthibitisho wa maarifa-sifuri hutumia mizunguko ya kihesabu (opens in a new tab), ambayo haina mbadala rahisi kwa taarifa ya if. Badala yake, wanatumia mbadala wa kiendeshaji chenye masharti (opens in a new tab). Ikiwa a inaweza kuwa sifuri au moja, unaweza kuhesabu if a { b } else { c } kama ab+(1-a)c.
Kwa sababu hii, taarifa ya if ya Zokrates daima inathmini matawi yote mawili. Kwa mfano, ikiwa una msimbo huu:
1bool[5] arr = [false; 5];2u32 index=10;3return if index>4 { 0 } else { arr[index] }Itatoa hitilafu, kwa sababu inahitaji kuhesabu arr[10], ingawa thamani hiyo itazidishwa na sifuri baadaye.
Hii ndiyo sababu tunahitaji mpaka wa upana wa eneo moja kuzunguka ramani. Tunahitaji kuhesabu jumla ya idadi ya migodi karibu na eneo, na hiyo inamaanisha tunahitaji kuona eneo la safu moja juu na chini, kushoto na kulia, kwa eneo tunalochimba. Hiyo inamaanisha maeneo hayo lazima yawepo kwenye safu ya ramani ambayo Zokrates inapewa.
1def main(private bool[${width+2}][${height+2}] map, u32 x, u32 y) -> (field, u8) {Kwa chaguo-msingi, uthibitisho wa Zokrates unajumuisha pembejeo zao. Haina faida kujua kuna migodi mitano karibu na eneo isipokuwa unajua ni eneo gani (na huwezi tu kuilinganisha na ombi lako, kwa sababu basi mthibitishaji anaweza kutumia thamani tofauti na asikuambie). Hata hivyo, tunahitaji kuweka ramani kuwa siri, huku tukiitoa kwa Zokrates. Suluhisho ni kutumia kigezo cha private, ambacho hakifunuliwi na uthibitisho.
Hii inafungua njia nyingine ya matumizi mabaya. Mthibitishaji anaweza kutumia kuratibu sahihi, lakini kuunda ramani yenye idadi yoyote ya migodi karibu na eneo, na labda katika eneo lenyewe. Ili kuzuia matumizi haya mabaya, tunafanya uthibitisho wa maarifa-sifuri ujumuishe hashi ya ramani, ambayo ni kitambulisho cha mchezo.
1 return (hashMap(map),Thamani ya kurudi hapa ni tuple inayojumuisha safu ya hashi ya ramani pamoja na matokeo ya kuchimba.
1 if map2mineCount(map, x, y) > 0 { 0xFF } else {Tunatumia 255 kama thamani maalum endapo eneo lenyewe lina bomu.
1 map2mineCount(map, x-1, y-1) + map2mineCount(map, x, y-1) + map2mineCount(map, x+1, y-1) +2 map2mineCount(map, x-1, y) + map2mineCount(map, x+1, y) +3 map2mineCount(map, x-1, y+1) + map2mineCount(map, x, y+1) + map2mineCount(map, x+1, y+1)4 }5 );6}Ikiwa mchezaji hajagonga mgodi, ongeza hesabu za migodi kwa eneo linalozunguka eneo hilo na urudishe hiyo.
Kutumia Zokrates kutoka TypeScript
Zokrates ina kiolesura cha mstari wa amri, lakini katika programu hii tunaitumia katika msimbo wa TypeScript (opens in a new tab).
Maktaba yenye ufafanuzi wa Zokrates inaitwa zero-knowledge.ts (opens in a new tab).
1import { initialize as zokratesInitialize } from "zokrates-js"Leta mafungo ya JavaScript ya Zokrates (opens in a new tab). Tunahitaji tu kazi ya initialize (opens in a new tab) kwa sababu inarudisha ahadi inayotatua ufafanuzi wote wa Zokrates.
1export const zkFunctions = async (width: number, height: number) : Promise<any> => {Sawa na Zokrates yenyewe, pia tunasafirisha kazi moja tu, ambayo pia ni isiyosawazisha (opens in a new tab). Inaporudi hatimaye, inatoa kazi kadhaa kama tutakavyoona hapa chini.
1const zokrates = await zokratesInitialize()Anzisha Zokrates, pata kila kitu tunachohitaji kutoka kwa maktaba.
1const hashFragment = `2 import "utils/pack/bool/pack128.zok" as pack128;3 import "hashes/poseidon/poseidon.zok" as poseidon;4 .5 .6 .7 }8 `910const hashProgram = `11 ${hashFragment}12 .13 .14 .15 `1617const digProgram = `18 ${hashFragment}19 .20 .21 .22 `Onyesha yoteKisha tuna kazi ya hashi na programu mbili za Zokrates tulizoona hapo juu.
1const digCompiled = zokrates.compile(digProgram)2const hashCompiled = zokrates.compile(hashProgram)Hapa tunakusanya programu hizo.
1// Tengeneza funguo za uthibitishaji wa zero-knowledge.\n// Kwenye mfumo wa uzalishaji ungetaka kutumia sherehe ya usanidi.\n// (https://zokrates.github.io/toolbox/trusted_setup.html#initializing-a-phase-2-ceremony).\nconst keySetupResults = zokrates.setup(digCompiled.program, \"\")\nconst verifierKey = keySetupResults.vk\nconst proverKey = keySetupResults.pkKwenye mfumo wa uzalishaji tunaweza kutumia hafla ya kuanzisha (opens in a new tab) ngumu zaidi, lakini hii ni nzuri ya kutosha kwa maonyesho. Sio tatizo watumiaji kujua ufunguo wa mthibitishaji - bado hawawezi kuitumia kuthibitisha mambo isipokuwa ni kweli. Kwa sababu tunabainisha entropy (kigezo cha pili, ""), matokeo yatakuwa sawa kila wakati.
Kumbuka: Uundaji wa programu za Zokrates na uundaji wa ufunguo ni michakato ya polepole. Hakuna haja ya kuzirudia kila wakati, ila tu wakati saizi ya ramani inabadilika. Kwenye mfumo wa uzalishaji ungewafanya mara moja, kisha uhifadhi matokeo. Sababu pekee kwa nini siifanyi hapa ni kwa ajili ya urahisi.
calculateMapHash
1const calculateMapHash = function (hashMe: boolean[][]): string {2 return (3 "0x" +4 BigInt(zokrates.computeWitness(hashCompiled, [hashMe]).output.slice(1, -1))5 .toString(16)6 .padStart(64, "0")7 )8}Kazi ya computeWitness (opens in a new tab) inatekeleza programu ya Zokrates. Inarudisha muundo wenye nyanja mbili: output, ambayo ni matokeo ya programu kama kamba ya JSON, na witness, ambayo ni taarifa inayohitajika kuunda uthibitisho wa maarifa-sifuri wa matokeo. Hapa tunahitaji tu matokeo.
Matokeo ni kamba ya fomu "31337", nambari ya desimali iliyofungwa kwenye alama za nukuu. Lakini matokeo tunayohitaji kwa viem ni nambari ya heksadesimali ya fomu 0x60A7. Kwa hivyo tunatumia .slice(1,-1) kuondoa alama za nukuu na kisha BigInt kuendesha kamba iliyobaki, ambayo ni nambari ya desimali, kwa BigInt (opens in a new tab). .toString(16) inabadilisha BigInt hii kuwa kamba ya heksadesimali, na "0x"+ inaongeza alama ya nambari za heksadesimali.
1// Chimba na urudishe uthibitisho wa maarifa-sifuri wa matokeo2// (msimbo wa upande wa seva)Uthibitisho wa maarifa-sifuri unajumuisha pembejeo za umma (x na y) na matokeo (hashi ya ramani na idadi ya mabomu).
1 const zkDig = function(map: boolean[][], x: number, y: number) : any {2 if (x<0 || x>=width || y<0 || y>=height)3 throw new Error("Trying to dig outside the map")Ni tatizo kuangalia ikiwa faharasa iko nje ya mipaka katika Zokrates, kwa hivyo tunafanya hapa.
1const runResults = zokrates.computeWitness(digCompiled, [map, `${x}`, `${y}`])Tekeleza programu ya kuchimba.
1 const proof = zokrates.generateProof(2 digCompiled.program,3 runResults.witness,4 proverKey)56 return proof7 }Tumia generateProof (opens in a new tab) na urudishe uthibitisho.
1const solidityVerifier = `2 // Ukubwa wa ramani: ${width} x ${height}3 \n${zokrates.exportSolidityVerifier(verifierKey)}4 `Mthibitishaji wa Solidity, mkataba-erevu tunaoweza kusambaza kwenye mnyororo wa bloku na kutumia kuthibitisha uthibitisho unaotokana na digCompiled.program.
1 return {2 zkDig,3 calculateMapHash,4 solidityVerifier,5 }6}Mwishowe, rudisha kila kitu ambacho msimbo mwingine unaweza kuhitaji.
Majaribio ya usalama
Majaribio ya usalama ni muhimu kwa sababu hitilafu ya utendakazi hatimaye itajidhihirisha. Lakini ikiwa programu haina usalama, kuna uwezekano itabaki imefichwa kwa muda mrefu kabla ya kufunuliwa na mtu anayedanganya na kuondoka na rasilimali ambazo ni za wengine.
Ruhusa
Kuna chombo kimoja cha upendeleo katika mchezo huu, seva. Ni mtumiaji pekee anayeruhusiwa kuita kazi katika ServerSystem (opens in a new tab). Tunaweza kutumia cast (opens in a new tab) kuthibitisha wito kwa kazi zilizoidhinishwa zinaruhusiwa tu kama akaunti ya seva.
Ufunguo binafsi wa seva uko katika setupNetwork.ts (opens in a new tab).
-
Kwenye kompyuta inayoendesha
anvil(mnyororo wa bloku), weka vigezo hivi vya mazingira.1WORLD_ADDRESS=0x8d8b6b8414e1e3dcfd4168561b9be6bd3bf6ec4b2UNAUTHORIZED_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a3AUTHORIZED_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d -
Tumia
castkujaribu kuweka anwani ya mthibitishaji kama anwani isiyoidhinishwa.1cast send $WORLD_ADDRESS 'app__setVerifier(address)' `cast address-zero` --private-key $UNAUTHORIZED_KEYSio tu kwamba
castinaripoti kutofaulu, lakini unaweza kufungua Zana za Maendeleo za MUD kwenye mchezo kwenye kivinjari, bonyeza Majedwali, na uchague app__VerifierAddress. Angalia kwamba anwani sio sifuri. -
Weka anwani ya mthibitishaji kama anwani ya seva.
1cast send $WORLD_ADDRESS 'app__setVerifier(address)' `cast address-zero` --private-key $AUTHORIZED_KEYAnwani katika app__VerifiedAddress sasa inapaswa kuwa sifuri.
Kazi zote za MUD katika Mfumo huo huo hupitia udhibiti sawa wa ufikiaji, kwa hivyo nachukulia jaribio hili linatosha. Ikiwa hutafanya hivyo, unaweza kuangalia kazi zingine katika ServerSystem (opens in a new tab).
Matumizi mabaya ya maarifa-sifuri
Hisabati ya kuthibitisha Zokrates iko nje ya wigo wa mafunzo haya (na uwezo wangu). Hata hivyo, tunaweza kufanya ukaguzi mbalimbali kwenye msimbo wa maarifa-sifuri ili kuthibitisha kwamba ikiwa haijafanywa kwa usahihi inashindwa. Majaribio haya yote yatatuhitaji kubadilisha zero-knowledge.ts (opens in a new tab) na kuanzisha upya programu nzima. Haitoshi kuanzisha upya mchakato wa seva, kwa sababu inaweka programu katika hali isiyowezekana (mchezaji ana mchezo unaoendelea, lakini mchezo haupatikani tena kwa seva).
Jibu lisilo sahihi
Uwezekano rahisi zaidi ni kutoa jibu lisilo sahihi katika uthibitisho wa maarifa-sifuri. Ili kufanya hivyo, tunaingia ndani ya zkDig na kurekebisha mstari wa 91 (opens in a new tab):
1proof.inputs[3] = "0x" + "1".padStart(64, "0")Hii inamaanisha tutadai kila wakati kuna bomu moja, bila kujali jibu sahihi. Jaribu kucheza na toleo hili, na utaona kwenye kichupo cha seva cha skrini ya pnpm dev hitilafu hii:
1 cause: {2 code: 3,3 message: 'execution reverted: revert: Zero knowledge verification fail',4 data: '0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000005000000000000000000000000000000000000000000000000205a65726f206b6e6f776c6564676520766572696669636174696f66e206661696c'7 },Kwa hivyo aina hii ya udanganyifu inashindwa.
Uthibitisho usio sahihi
Nini kinatokea ikiwa tunatoa taarifa sahihi, lakini tuna data isiyo sahihi ya uthibitisho? Sasa, badilisha mstari wa 91 na:
1proof.proof = {2 a: ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],3 b: [4 ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],5 ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],6 ],7 c: ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],8}Bado inashindwa, lakini sasa inashindwa bila sababu kwa sababu inatokea wakati wa wito wa mthibitishaji.
Mtumiaji anawezaje kuthibitisha msimbo wa imani-sifuri?
Mikataba-erevu ni rahisi kiasi kuthibitisha. Kwa kawaida, msanidi programu anachapisha msimbo chanzo kwa kichunguzi cha bloku, na kichunguzi cha bloku kinathibitisha kwamba msimbo chanzo unakusanywa kwa msimbo katika shughuli ya usambazaji wa mkataba. Katika kesi ya MUD Mfumo huu ni ngumu kidogo (opens in a new tab), lakini si kwa kiasi kikubwa.
Hii ni ngumu zaidi na maarifa-sifuri. Mthibitishaji anajumuisha viwango vya kudumu na anafanya mahesabu juu yao. Hii haikuambii nini kinachothibitishwa.
1 function verifyingKey() pure internal returns (VerifyingKey memory vk) {2 vk.alpha = Pairing.G1Point(uint256(0x0f43f4fe7b5c2326fed4ac6ed2f4003ab9ab4ea6f667c2bdd77afb068617ee16), uint256(0x25a77832283f9726935219b5f4678842cda465631e72dbb24708a97ba5d0ce6f));3 vk.beta = Pairing.G2Point([uint256(0x2cebd0fbd21aca01910581537b21ae4fed46bc0e524c055059aa164ba0a6b62b), uint256(0x18fd4a7bc386cf03a95af7163d5359165acc4e7961cb46519e6d9ee4a1e2b7e9)], [uint256(0x11449dee0199ef6d8eebfe43b548e875c69e7ce37705ee9a00c81fe52f11a009), uint256(0x066d0c83b32800d3f335bb9e8ed5e2924cf00e77e6ec28178592eac9898e1a00)]);Suluhisho, angalau hadi wachunguzi wa bloku wataongeza uthibitishaji wa Zokrates kwenye violesura vyao vya watumiaji, ni kwa wasanidi programu kufanya programu za Zokrates zipatikane, na angalau baadhi ya watumiaji kuzikusanya wenyewe na ufunguo sahihi wa uthibitishaji.
Kufanya hivyo:
-
Unda faili,
dig.zok, na programu ya Zokrates. Msimbo hapa chini unadhania umehifadhi saizi ya ramani ya asili, 10x5.1 import "utils/pack/bool/pack128.zok" as pack128;2 import "hashes/poseidon/poseidon.zok" as poseidon;34 def hashMap(bool[12][7] map) -> field {5 bool[512] mut map1d = [false; 512];6 u32 mut counter = 0;78 for u32 x in 0..12 {9 for u32 y in 0..7 {10 map1d[counter] = map[x][y];11 counter = counter+1;12 }13 }1415 field[4] hashMe = [16 pack128(map1d[0..128]),17 pack128(map1d[128..256]),18 pack128(map1d[256..384]),19 pack128(map1d[384..512])20 ];2122 return poseidon(hashMe);23 }242526 // Idadi ya migodi katika eneo (x,y)27 def map2mineCount(bool[12][7] map, u32 x, u32 y) -> u8 {28 return if map[x+1][y+1] { 1 } else { 0 };29 }3031 def main(private bool[12][7] map, u32 x, u32 y) -> (field, u8) {32 return (hashMap(map) ,33 if map2mineCount(map, x, y) > 0 { 0xFF } else {34 map2mineCount(map, x-1, y-1) + map2mineCount(map, x, y-1) + map2mineCount(map, x+1, y-1) +35 map2mineCount(map, x-1, y) + map2mineCount(map, x+1, y) +36 map2mineCount(map, x-1, y+1) + map2mineCount(map, x, y+1) + map2mineCount(map, x+1, y+1)37 }38 );39 }Onyesha yote -
Kusanya msimbo wa Zokrates na uunde ufunguo wa uthibitishaji. Ufunguo wa uthibitishaji unapaswa kuundwa na entropy sawa iliyotumika katika seva ya awali, katika kesi hii kamba tupu (opens in a new tab).
1zokrates compile --input dig.zok2zokrates setup -e "" -
Unda mthibitishaji wa Solidity peke yako, na uthibitishe kuwa unafanya kazi sawa na ule ulioko kwenye mnyororo wa bloku (seva inaongeza maoni, lakini hiyo sio muhimu).
1zokrates export-verifier2diff verifier.sol ~/20240901-secret-state/packages/contracts/src/verifier.sol
Maamuzi ya kubuni
Katika programu yoyote ngumu vya kutosha kuna malengo ya kubuni yanayoshindana ambayo yanahitaji maelewano. Wacha tuangalie baadhi ya maelewano na kwa nini suluhisho la sasa ni bora kuliko chaguzi zingine.
Kwa nini maarifa-sifuri
Kwa minesweeper hauitaji maarifa-sifuri kweli. Seva inaweza kushikilia ramani kila wakati, na kisha kufunua yote wakati mchezo umekwisha. Kisha, mwishoni mwa mchezo, mkataba-erevu unaweza kuhesabu hashi ya ramani, kuthibitisha inalingana, na ikiwa haifanyi hivyo adhibu seva au kupuuzia mchezo kabisa.
Sikutumia suluhisho hili rahisi kwa sababu inafanya kazi tu kwa michezo mifupi yenye hali ya mwisho iliyoelezwa vizuri. Wakati mchezo unaweza kuwa hauna mwisho (kama ilivyo kwa ulimwengu huru (opens in a new tab)), unahitaji suluhisho linalothibitisha hali bila kuifunua.
Kama mafunzo, makala hii ilihitaji mchezo mfupi ambao ni rahisi kuelewa, lakini mbinu hii ni muhimu zaidi kwa michezo mirefu.
Kwa nini Zokrates?
Zokrates (opens in a new tab) sio maktaba pekee ya maarifa-sifuri inayopatikana, lakini inafanana na lugha ya kawaida ya programu inayoelekeza (opens in a new tab) na inasaidia vigezo vya boolean.
Kwa programu yako, na mahitaji tofauti, unaweza kupendelea kutumia Circum (opens in a new tab) au Cairo (opens in a new tab).
Wakati wa kukusanya Zokrates
Katika programu hii tunakusanya programu za Zokrates kila wakati seva inapoanza (opens in a new tab). Hii ni wazi ni upotevu wa rasilimali, lakini hii ni mafunzo, yaliyoboreshwa kwa urahisi.
Ikiwa ningekuwa ninaandika programu ya kiwango cha uzalishaji, ningeangalia kama nina faili na programu za Zokrates zilizokusanywa kwa saizi hii ya uwanja wa migodi, na ikiwa ndivyo nitumie hiyo. Vivyo hivyo kwa kusambaza mkataba wa mthibitishaji onchain.
Kuunda funguo za mthibitishaji na mthibitishaji
Uundaji wa ufunguo (opens in a new tab) ni hesabu nyingine safi ambayo haihitaji kufanywa zaidi ya mara moja kwa saizi fulani ya uwanja wa migodi. Tena, inafanywa mara moja tu kwa ajili ya urahisi.
Kwa kuongeza, tunaweza kutumia hafla ya kuanzisha (opens in a new tab). Faida ya hafla ya kuanzisha ni kwamba unahitaji ama entropy au matokeo ya kati kutoka kwa kila mshiriki ili kudanganya kwenye uthibitisho wa maarifa-sifuri. Ikiwa angalau mshiriki mmoja wa hafla ni mkweli na anafuta habari hiyo, uthibitisho wa maarifa-sifuri uko salama kutokana na mashambulizi fulani. Hata hivyo, hakuna utaratibu wa kuthibitisha kwamba habari imefutwa kutoka kila mahali. Ikiwa uthibitisho wa maarifa-sifuri ni muhimu sana, unataka kushiriki katika hafla ya kuanzisha.
Hapa tunategemea nguvu za milele za tau (opens in a new tab), ambayo ilikuwa na washiriki wengi. Pengine ni salama vya kutosha, na rahisi zaidi. Pia hatuongezi entropy wakati wa uundaji wa ufunguo, ambayo inafanya iwe rahisi kwa watumiaji kuthibitisha usanidi wa maarifa-sifuri.
Wapi pa kuthibitisha
Tunaweza kuthibitisha uthibitisho wa maarifa-sifuri ama onchain (ambayo inagharimu gesi) au katika mteja (kwa kutumia verify (opens in a new tab)). Nilichagua ya kwanza, kwa sababu hii inakuwezesha kuthibitisha mthibitishaji mara moja na kisha kuamini kwamba haibadiliki maadamu anwani ya mkataba wake inabaki sawa. Ikiwa uthibitishaji ungefanywa kwa mteja, ungehitaji kuthibitisha msimbo unaopokea kila unapopakua mteja.
Pia, ingawa mchezo huu ni wa mchezaji mmoja, michezo mingi ya mnyororo wa bloku ni ya wachezaji wengi. uthibitishaji wa onchain unamaanisha unathibitisha tu uthibitisho wa maarifa-sifuri mara moja. Kuifanya katika mteja kungehitaji kila mteja kuthibitisha kivyake.
Kufanya ramani iwe bapa katika TypeScript au Zokrates?
Kwa ujumla, wakati usindikaji unaweza kufanywa ama katika TypeScript au Zokrates, ni bora kuifanya katika TypeScript, ambayo ni haraka sana, na haihitaji uthibitisho wa maarifa-sifuri. Hii ndiyo sababu, kwa mfano, kwamba hatutoi Zokrates na hashi na kuifanya ithibitishe kuwa ni sahihi. Hashing inapaswa kufanywa ndani ya Zokrates, lakini mechi kati ya hashi iliyorudishwa na hashi onchain inaweza kutokea nje yake.
Hata hivyo, bado tunalainisha ramani katika Zokrates (opens in a new tab), ambapo tungeweza kuifanya katika TypeScript. Sababu ni kwamba chaguzi zingine, kwa maoni yangu, ni mbaya zaidi.
-
Toa safu ya boolean ya mwelekeo mmoja kwa msimbo wa Zokrates, na utumie usemi kama
x*(height+2) +ykupata ramani ya mwelekeo miwili. Msimbo (opens in a new tab) ungekuwa mgumu kidogo, kwa hivyo niliamua kuwa faida ya utendaji haifai kwa mafunzo. -
Tuma Zokrates safu ya mwelekeo mmoja na safu ya mwelekeo miwili. Hata hivyo, suluhisho hili halitupati faida yoyote. Msimbo wa Zokrates ungehitaji kuthibitisha kwamba safu ya mwelekeo mmoja inayoletwa ni kweli uwakilishi sahihi wa safu ya mwelekeo miwili. Kwa hivyo hakungekuwa na faida yoyote ya utendaji.
-
Laza safu ya mwelekeo miwili katika Zokrates. Hii ndiyo chaguo rahisi zaidi, kwa hivyo niliichagua.
Wapi pa kuhifadhi ramani
Katika programu hii gamesInProgress (opens in a new tab) ni kigezo tu kwenye kumbukumbu. Hii inamaanisha kwamba ikiwa seva yako itakufa na kuhitaji kuanzishwa upya, habari zote ilizohifadhi zinapotea. Sio tu kwamba wachezaji hawawezi kuendelea na mchezo wao, hawawezi hata kuanza mchezo mpya kwa sababu sehemu ya onchain inafikiri bado wana mchezo unaoendelea.
Huu ni muundo mbaya kwa mfumo wa uzalishaji, ambapo utahifadhi habari hii kwenye hifadhidata. Sababu pekee niliyotumia kigezo hapa ni kwa sababu hii ni mafunzo na urahisi ndio jambo kuu.
Hitimisho: Chini ya hali gani hii ni mbinu inayofaa?
Kwa hivyo, sasa unajua jinsi ya kuandika mchezo na seva inayohifadhi hali ya siri ambayo sio ya onchain. Lakini katika kesi gani unapaswa kuifanya? Kuna mambo makuu mawili ya kuzingatia.
-
Mchezo unaoendelea kwa muda mrefu: Kama ilivyoelezwa hapo juu, katika mchezo mfupi unaweza tu kuchapisha hali mara mchezo unapokwisha na kila kitu kuthibitishwa basi. Lakini hiyo sio chaguo wakati mchezo unachukua muda mrefu au usiojulikana, na hali inahitaji kubaki siri.
-
Ugawanyaji fulani wa kati unakubalika: Uthibitisho wa maarifa-sifuri unaweza kuthibitisha uadilifu, kwamba chombo hakighushi matokeo. Wanachoweza kufanya ni kuhakikisha kuwa chombo bado kitapatikana na kujibu ujumbe. Katika hali ambapo upatikanaji pia unahitaji kugawanywa, uthibitisho wa maarifa-sifuri sio suluhisho la kutosha, na unahitaji hesabu za vyama vingi (opens in a new tab).
Tazama hapa kwa kazi zangu zaidi (opens in a new tab).
Shukrani
- Alvaro Alonso alisoma rasimu ya makala hii na akanifafanulia baadhi ya kutoelewa kwangu kuhusu Zokrates.
Makosa yoyote yaliyobaki ni jukumu langu.
Ukurasa ulihaririwa mwisho: 25 Februari 2026
