Ruka kwenda kwenye maudhui makuu

Kutumia zero-knowledge kwa hali ya siri

server
offchain
centralized
zero-knowledge
zokrates
mud
Advanced
Ori Pomerantz
15 Machi 2025
25 minute read

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:

ZanaMadhumuniImethibitishwa kwenye toleo
Zokrates (opens in a new tab)Uthibitisho wa zero-knowledge na uthibitishaji wake1.1.9
Typescript (opens in a new tab)Lugha ya programu kwa seva na wateja5.4.2
Nodi (opens in a new tab)Kuendesha seva20.18.2
Viem (opens in a new tab)Mawasiliano na Mnyororo wa bloku2.9.20
MUD (opens in a new tab)Usimamizi wa data ya Onchain2.0.12
React (opens in a new tab)Kiolesura cha mtumiaji wa Wateja18.2.0
Vite (opens in a new tab)Kutumikia msimbo wa wateja4.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:

  1. 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), na mprocs (opens in a new tab).

  2. Kloni hifadhi.

    1git clone https://github.com/qbzzt/20240901-secret-state.git
  3. Sakinisha vifurushi.

    1cd 20240901-secret-state/
    2pnpm install
    3npm install -g mprocs

    Ikiwa Foundry ilisakinishwa kama sehemu ya pnpm install, unahitaji kuanzisha upya ganda la mstari wa amri.

  4. Kusanya mikataba

    1cd packages/contracts
    2forge build
    3cd ../..
  5. Anzisha programu (pamoja na mnyororo wa bloku wa anvil (opens in a new tab)) na subiri.

    1mprocs

    Kumbuka 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.

    Skrini ya mprocs

    Ikiwa kuna tatizo na mprocs, unaweza kuendesha michakato minne kwa mikono, kila mmoja katika dirisha lake la mstari wa amri:

    • Anvil

      1cd packages/contracts
      2anvil --base-fee 0 --block-time 2
    • Mikataba

      1cd packages/contracts
      2pnpm mud dev-contracts --rpc http://127.0.0.1:8545
    • Seva

      1cd packages/server
      2pnpm start
    • Wateja

      1cd packages/client
      2pnpm run dev
  6. 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 migodi
    • width: Upana wa uwanja wa migodi
    • numberOfBombs: 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 la Configuration, 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, kutoka gameId hadi 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:

  1. mprocs (opens in a new tab) inaendesha vipengele vinne:

  2. Kifurushi cha contracts kinasambaza mikataba ya MUD na kisha kinaendesha hati ya PostDeploy.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).

  3. 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.

  4. Seva inasajili kazi ya kutekelezwa wakati jedwali la Configuration linabadilika (opens in a new tab). Kazi hii (opens in a new tab) inaitwa baada ya PostDeploy.s.sol kutekeleza na kurekebisha jedwali.

  5. 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.

  6. 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.

  7. 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.

  1. 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).

  2. newGame (opens in a new tab) ni wito wa System. Katika MUD simu zote zinaelekezwa kupitia mkataba wa World, na katika hali nyingi unaita <namespace>__<function name>. Katika kesi hii, wito ni kwa app__newGame, ambayo MUD kisha inaelekeza kwa newGame katika GameSystem (opens in a new tab).

  3. Kazi ya onchain inakagua kwamba mchezaji hana mchezo unaoendelea, na ikiwa hakuna inaongeza ombi kwenye jedwali la PendingGame (opens in a new tab).

  4. Seva inagundua mabadiliko katika PendingGame na inaendesha kazi iliyosajiliwa (opens in a new tab). Kazi hii inaita newGame (opens in a new tab), ambayo kwa upande wake inaita createGame (opens in a new tab).

  5. Jambo la kwanza createGame hufanya ni kuunda ramani isiyo ya kawaida na idadi inayofaa ya migodi (opens in a new tab). Kisha, inaita makeMapBorders (opens in a new tab) ili kuunda ramani yenye mipaka tupu, ambayo ni muhimu kwa Zokrates. Mwishowe, createGame inaita calculateMapHash, ili kupata hashi ya ramani, ambayo hutumiwa kama kitambulisho cha mchezo.

  6. Kazi ya newGame inaongeza mchezo mpya kwenye gamesInProgress.

  7. Jambo la mwisho ambalo seva hufanya ni kuita app__newGameResponse (opens in a new tab), ambayo iko onchain. Kazi hii iko katika System tofauti, 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.

  8. Sehemu ya onchain inasasisha majedwali husika:

    • Unda mchezo katika PlayerGame.
    • Weka ramani ya kinyume katika GamePlayer.
    • Ondoa ombi kutoka PendingGame.
  9. Seva inatambua mabadiliko katika PendingGame, lakini haifanyi chochote kwa sababu wantsGame (opens in a new tab) ni ya uongo.

  10. Kwa upande wa wateja gameRecord (opens in a new tab) imewekwa kwenye ingizo la PlayerGame kwa anwani ya mchezaji. Wakati PlayerGame inapobadilika, gameRecord pia hubadilika.

  11. Ikiwa kuna thamani katika gameRecord, na mchezo haujashindwa wala kushindwa, wateja wanaonyesha ramani (opens in a new tab).

Chimba

  1. Mchezaji anabonyeza kitufe cha seli ya ramani (opens in a new tab), ambayo inaita kazi ya dig (opens in a new tab). Kazi hii inaita dig onchain (opens in a new tab).

  2. 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).

  3. 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.

  4. Seva (opens in a new tab) inaita digResponse (opens in a new tab) onchain.

  5. digResponse hufanya mambo mawili. Kwanza, inakagua uthibitisho wa zero knowledge (opens in a new tab). Kisha, ikiwa uthibitisho umekaguliwa, inaita processDigResult (opens in a new tab) ili kushughulikia matokeo.

  6. processDigResult inakagua ikiwa mchezo umeshindwa (opens in a new tab) au umeshinda (opens in a new tab), na inasasisha Ramani, ramani ya onchain (opens in a new tab).

  7. 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}
2
3def 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}
2
3// 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 `
9
10const hashProgram = `
11 ${hashFragment}
12 .
13 .
14 .
15 `
16
17const digProgram = `
18 ${hashFragment}
19 .
20 .
21 .
22 `
Onyesha yote

Kisha 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.pk

Kwenye 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 matokeo
2// (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)
5
6 return proof
7 }

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).

  1. Kwenye kompyuta inayoendesha anvil (mnyororo wa bloku), weka vigezo hivi vya mazingira.

    1WORLD_ADDRESS=0x8d8b6b8414e1e3dcfd4168561b9be6bd3bf6ec4b
    2UNAUTHORIZED_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
    3AUTHORIZED_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
  2. Tumia cast kujaribu kuweka anwani ya mthibitishaji kama anwani isiyoidhinishwa.

    1cast send $WORLD_ADDRESS 'app__setVerifier(address)' `cast address-zero` --private-key $UNAUTHORIZED_KEY

    Sio tu kwamba cast inaripoti kutofaulu, lakini unaweza kufungua Zana za Maendeleo za MUD kwenye mchezo kwenye kivinjari, bonyeza Majedwali, na uchague app__VerifierAddress. Angalia kwamba anwani sio sifuri.

  3. Weka anwani ya mthibitishaji kama anwani ya seva.

    1cast send $WORLD_ADDRESS 'app__setVerifier(address)' `cast address-zero` --private-key $AUTHORIZED_KEY

    Anwani 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: '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000
5000000000000000000000000000000000000000000000000205a65726f206b6e6f776c6564676520766572696669636174696f6
6e206661696c'
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:

  1. Sakinisha Zokrates (opens in a new tab).

  2. 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;
    3
    4 def hashMap(bool[12][7] map) -> field {
    5 bool[512] mut map1d = [false; 512];
    6 u32 mut counter = 0;
    7
    8 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 }
    14
    15 field[4] hashMe = [
    16 pack128(map1d[0..128]),
    17 pack128(map1d[128..256]),
    18 pack128(map1d[256..384]),
    19 pack128(map1d[384..512])
    20 ];
    21
    22 return poseidon(hashMe);
    23 }
    24
    25
    26 // 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 }
    30
    31 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
  3. 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.zok
    2zokrates setup -e ""
  4. 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-verifier
    2diff 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) +y kupata 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

Umesaidika na mafunzo haya?