मुख्य आशयावर जा

गुप्त स्थितीसाठी झिरो-नॉलेज वापरणे

सर्व्हर
साखळीबाह्य
केंद्रीकृत
झिरो-नॉलेज
Zokrates
MUD
गोपनीयता
प्रगत
ओरी पोमेरँट्झ
15 मार्च, 2025
25 मिनिटांचे वाचन

ब्लॉकचेनवर कोणतीही गुपिते नसतात. ब्लॉकचेनवर पोस्ट केलेली प्रत्येक गोष्ट सर्वांना वाचण्यासाठी खुली असते. हे आवश्यक आहे, कारण ब्लॉकचेनची रचना कोणीही त्याची पडताळणी करू शकेल यावर आधारित आहे. तथापि, गेम्स अनेकदा गुप्त स्थितीवर अवलंबून असतात. उदाहरणार्थ, जर तुम्ही फक्त ब्लॉक एक्सप्लोररवर जाऊन नकाशा पाहू शकत असाल तर माइनस्वीपर (नवीन टॅबमध्ये उघडते) गेमला काहीच अर्थ उरत नाही.

सर्वात सोपा उपाय म्हणजे गुप्त स्थिती ठेवण्यासाठी सर्व्हर घटक वापरणे. तथापि, आपण ब्लॉकचेन वापरण्याचे कारण म्हणजे गेम डेव्हलपरकडून होणारी फसवणूक टाळणे. आपल्याला सर्व्हर घटकाच्या प्रामाणिकपणाची खात्री करणे आवश्यक आहे. सर्व्हर स्थितीचा हॅश प्रदान करू शकतो आणि चालचा (move) निकाल मोजण्यासाठी वापरलेली स्थिती योग्य आहे हे सिद्ध करण्यासाठी शून्य-ज्ञान पुरावे वापरू शकतो.

हा लेख वाचल्यानंतर तुम्हाला अशा प्रकारचा गुप्त स्थिती ठेवणारा सर्व्हर, स्थिती दर्शवणारा क्लायंट आणि या दोन्हींमधील संवादासाठी ऑनचेन घटक कसा तयार करायचा हे समजेल. आपण वापरत असलेली मुख्य साधने खालीलप्रमाणे असतील:

साधनउद्देशया आवृत्तीवर पडताळणी केली
Zokrates (नवीन टॅबमध्ये उघडते)शून्य-ज्ञान पुरावे आणि त्यांची पडताळणी1.1.9
TypeScript (नवीन टॅबमध्ये उघडते)सर्व्हर आणि क्लायंट दोन्हीसाठी प्रोग्रामिंग भाषा5.4.2
Node (नवीन टॅबमध्ये उघडते)सर्व्हर चालवणे20.18.2
Viem (नवीन टॅबमध्ये उघडते)ब्लॉकचेनसोबत संवाद2.9.20
MUD (नवीन टॅबमध्ये उघडते)ऑनचेन डेटा व्यवस्थापन2.0.12
React (नवीन टॅबमध्ये उघडते)क्लायंट युजर इंटरफेस18.2.0
Vite (नवीन टॅबमध्ये उघडते)क्लायंट कोड सर्व्ह करणे4.2.1

माइनस्वीपर उदाहरण

माइनस्वीपर (नवीन टॅबमध्ये उघडते) हा एक गेम आहे ज्यामध्ये माइनफील्डसह (सुरुंग क्षेत्र) एक गुप्त नकाशा असतो. खेळाडू एका विशिष्ट ठिकाणी खोदण्याची निवड करतो. जर त्या ठिकाणी माइन (सुरुंग) असेल, तर गेम संपतो. अन्यथा, खेळाडूला त्या ठिकाणाच्या सभोवतालच्या आठ चौकोनांमधील माइन्सची संख्या मिळते.

हे ॲप्लिकेशन MUD (नवीन टॅबमध्ये उघडते) वापरून लिहिले आहे, जे एक फ्रेमवर्क आहे आणि आपल्याला की-व्हॅल्यू डेटाबेस (नवीन टॅबमध्ये उघडते) वापरून ऑनचेन डेटा संचयित करण्यास आणि तो डेटा साखळीबाह्य घटकांसह स्वयंचलितपणे सिंक्रोनाइझ करण्यास अनुमती देते. सिंक्रोनायझेशन व्यतिरिक्त, MUD ॲक्सेस कंट्रोल प्रदान करणे सोपे करते आणि इतर वापरकर्त्यांना आपले ॲप्लिकेशन परवानगीविना विस्तारित (नवीन टॅबमध्ये उघडते) करणे सोपे करते.

माइनस्वीपर उदाहरण चालवणे

माइनस्वीपर उदाहरण चालवण्यासाठी:

  1. तुमच्याकडे आवश्यक पूर्वअटी इन्स्टॉल केलेल्या असल्याची (नवीन टॅबमध्ये उघडते) खात्री करा: नोड (नवीन टॅबमध्ये उघडते), Foundry (नवीन टॅबमध्ये उघडते), git (नवीन टॅबमध्ये उघडते), pnpm (नवीन टॅबमध्ये उघडते), आणि mprocs (नवीन टॅबमध्ये उघडते).

  2. रिपॉझिटरी क्लोन करा.

    git clone https://github.com/qbzzt/20240901-secret-state.git
    
  3. पॅकेजेस इन्स्टॉल करा.

    cd 20240901-secret-state/
    pnpm install
    npm install -g mprocs
    

    जर Foundry pnpm install चा भाग म्हणून इन्स्टॉल केले असेल, तर तुम्हाला कमांड-लाइन शेल रीस्टार्ट करणे आवश्यक आहे.

  4. कॉन्ट्रॅक्ट्स कंपाईल करा

    cd packages/contracts
    forge build
    cd ../..
    
  5. प्रोग्राम सुरू करा (ज्यामध्ये anvil (नवीन टॅबमध्ये उघडते) ब्लॉकचेन समाविष्ट आहे) आणि प्रतीक्षा करा.

    mprocs
    

    लक्षात घ्या की स्टार्टअपला बराच वेळ लागतो. प्रगती पाहण्यासाठी, प्रस्थापित होत असलेले MUD कॉन्ट्रॅक्ट्स पाहण्यासाठी प्रथम contracts टॅबवर स्क्रोल करण्यासाठी डाउन ॲरो (खालचा बाण) वापरा. जेव्हा तुम्हाला Waiting for file changes… हा संदेश मिळेल, तेव्हा कॉन्ट्रॅक्ट्स प्रस्थापित झालेले असतात आणि पुढील प्रगती server टॅबमध्ये होईल. तेथे, तुम्हाला Verifier address: 0x.... हा संदेश मिळेपर्यंत प्रतीक्षा करा.

    जर ही पायरी यशस्वी झाली, तर तुम्हाला mprocs स्क्रीन दिसेल, ज्यामध्ये डावीकडे विविध प्रक्रिया आणि उजवीकडे सध्या निवडलेल्या प्रक्रियेसाठी कन्सोल आउटपुट असेल.

    The mprocs screen

    जर mprocs मध्ये काही समस्या असेल, तर तुम्ही चारही प्रक्रिया मॅन्युअली चालवू शकता, प्रत्येक त्याच्या स्वतःच्या कमांड लाइन विंडोमध्ये:

    • Anvil

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

      cd packages/contracts
      pnpm mud dev-contracts --rpc http://127.0.0.1:8545
      
    • Server

      cd packages/server
      pnpm start
      
    • Client

      cd packages/client
      pnpm run dev
      
  6. आता तुम्ही क्लायंटवर (नवीन टॅबमध्ये उघडते) ब्राउझ करू शकता, New Game वर क्लिक करू शकता आणि खेळायला सुरुवात करू शकता.

टेबल्स

आपल्याला ऑनचेन अनेक टेबल्सची (नवीन टॅबमध्ये उघडते) आवश्यकता आहे.

  • Configuration: हे टेबल एक सिंगलटन आहे, यात कोणतीही की नाही आणि एकच रेकॉर्ड आहे. याचा वापर गेम कॉन्फिगरेशन माहिती ठेवण्यासाठी केला जातो:

    • height: माइनफील्डची उंची
    • width: माइनफील्डची रुंदी
    • numberOfBombs: प्रत्येक माइनफील्डमधील बॉम्बची संख्या
  • VerifierAddress: हे टेबल देखील एक सिंगलटन आहे. याचा वापर कॉन्फिगरेशनचा एक भाग, पडताळणीकर्ता कॉन्ट्रॅक्टचा पत्ता (verifier) ठेवण्यासाठी केला जातो. आपण ही माहिती Configuration टेबलमध्ये ठेवू शकलो असतो, परंतु ती सर्व्हर या वेगळ्या घटकाद्वारे सेट केली जाते, त्यामुळे ती वेगळ्या टेबलमध्ये ठेवणे सोपे आहे.

  • PlayerGame: खेळाडूचा पत्ता ही की आहे. डेटा खालीलप्रमाणे आहे:

    • gameId: 32-बाइट मूल्य जे खेळाडू खेळत असलेल्या नकाशाचा हॅश आहे (गेम आयडेंटिफायर).
    • win: एक बुलियन जे खेळाडूने गेम जिंकला आहे की नाही हे दर्शवते.
    • lose: एक बुलियन जे खेळाडू गेम हरला आहे की नाही हे दर्शवते.
    • digNumber: गेममधील यशस्वी खोदाईची संख्या.
  • GamePlayer: हे टेबल gameId पासून खेळाडूच्या पत्त्यापर्यंत रिव्हर्स मॅपिंग ठेवते.

  • Map: की हे तीन मूल्यांचे ट्यूपल (tuple) आहे:

    • gameId: 32-बाइट मूल्य जे खेळाडू खेळत असलेल्या नकाशाचा हॅश आहे (गेम आयडेंटिफायर).
    • x कोऑर्डिनेट
    • y कोऑर्डिनेट

    मूल्य ही एकच संख्या आहे. जर बॉम्ब सापडला तर ते 255 असते. अन्यथा, ते त्या ठिकाणाच्या सभोवतालच्या बॉम्बची संख्या अधिक एक असते. आपण फक्त बॉम्बची संख्या वापरू शकत नाही, कारण डीफॉल्टनुसार EVM मधील सर्व स्टोरेज आणि MUD मधील सर्व रो (row) मूल्ये शून्य असतात. आपल्याला "खेळाडूने अद्याप येथे खोदलेले नाही" आणि "खेळाडूने येथे खोदले आहे, आणि सभोवताली शून्य बॉम्ब असल्याचे आढळले आहे" यातील फरक ओळखणे आवश्यक आहे.

याव्यतिरिक्त, क्लायंट आणि सर्व्हरमधील संवाद ऑनचेन घटकाद्वारे होतो. हे देखील टेबल्स वापरून लागू केले जाते.

  • PendingGame: नवीन गेम सुरू करण्यासाठी अनसर्व्हिस्ड (सेवा न दिलेल्या) विनंत्या.
  • PendingDig: विशिष्ट गेममध्ये विशिष्ट ठिकाणी खोदण्यासाठी अनसर्व्हिस्ड विनंत्या. हे एक साखळीबाह्य टेबल (नवीन टॅबमध्ये उघडते) आहे, याचा अर्थ ते EVM स्टोरेजमध्ये लिहिले जात नाही, ते केवळ घटना वापरून साखळीबाह्य वाचनीय आहे.

एक्झिक्यूशन आणि डेटा फ्लो

हे फ्लो क्लायंट, ऑनचेन घटक आणि सर्व्हर यांच्यातील एक्झिक्यूशनचे समन्वय साधतात.

इनिशिएलायझेशन (प्रारंभ)

जेव्हा तुम्ही mprocs चालवता, तेव्हा या पायऱ्या घडतात:

  1. mprocs (नवीन टॅबमध्ये उघडते) चार घटक चालवते:

  2. contracts पॅकेज MUD कॉन्ट्रॅक्ट्स प्रस्थापित करते आणि नंतर PostDeploy.s.sol स्क्रिप्ट (नवीन टॅबमध्ये उघडते) चालवते. ही स्क्रिप्ट कॉन्फिगरेशन सेट करते. GitHub मधील कोड आठ माइन्ससह 10x5 माइनफील्ड (नवीन टॅबमध्ये उघडते) निर्दिष्ट करतो.

  3. सर्व्हर (नवीन टॅबमध्ये उघडते) MUD सेट करून (नवीन टॅबमध्ये उघडते) सुरू होतो. इतर गोष्टींबरोबरच, हे डेटा सिंक्रोनायझेशन सक्रिय करते, जेणेकरून संबंधित टेबल्सची एक प्रत सर्व्हरच्या मेमरीमध्ये अस्तित्वात असते.

  4. जेव्हा Configuration टेबल बदलते (नवीन टॅबमध्ये उघडते) तेव्हा कार्यान्वित करण्यासाठी सर्व्हर एका फंक्शनला सबस्क्राईब करतो. PostDeploy.s.sol कार्यान्वित झाल्यानंतर आणि टेबलमध्ये बदल केल्यानंतर हे फंक्शन (नवीन टॅबमध्ये उघडते) कॉल केले जाते.

  5. जेव्हा सर्व्हर इनिशिएलायझेशन फंक्शनकडे कॉन्फिगरेशन असते, तेव्हा ते सर्व्हरचा झिरो-नॉलेज भाग इनिशिएलाईज करण्यासाठी zkFunctions ला कॉल करते (नवीन टॅबमध्ये उघडते). जोपर्यंत आपल्याला कॉन्फिगरेशन मिळत नाही तोपर्यंत हे घडू शकत नाही कारण झिरो-नॉलेज फंक्शन्समध्ये माइनफील्डची रुंदी आणि उंची स्थिरांक (constants) म्हणून असणे आवश्यक आहे.

  6. सर्व्हरचा झिरो-नॉलेज भाग इनिशिएलाईज झाल्यानंतर, पुढची पायरी म्हणजे ब्लॉकचेनवर झिरो-नॉलेज पडताळणी कॉन्ट्रॅक्ट प्रस्थापित करणे (नवीन टॅबमध्ये उघडते) आणि MUD मध्ये पडताळणीकर्ता पत्ता सेट करणे.

  7. शेवटी, आपण अपडेट्ससाठी सबस्क्राईब करतो जेणेकरून जेव्हा एखादा खेळाडू नवीन गेम सुरू करण्याची (नवीन टॅबमध्ये उघडते) किंवा विद्यमान गेममध्ये खोदण्याची (नवीन टॅबमध्ये उघडते) विनंती करेल तेव्हा आपल्याला दिसेल.

नवीन गेम

जेव्हा खेळाडू नवीन गेमची विनंती करतो तेव्हा हे घडते.

  1. जर या खेळाडूसाठी कोणताही गेम प्रगतीपथावर नसेल, किंवा एखादा असेल परंतु त्याचा gameId शून्य असेल, तर क्लायंट एक नवीन गेम बटण (नवीन टॅबमध्ये उघडते) प्रदर्शित करतो. जेव्हा वापरकर्ता हे बटण दाबतो, तेव्हा React newGame फंक्शन चालवते (नवीन टॅबमध्ये उघडते).

  2. newGame (नवीन टॅबमध्ये उघडते) हा एक System कॉल आहे. MUD मध्ये सर्व कॉल्स World कॉन्ट्रॅक्टद्वारे राउट केले जातात, आणि बहुतांश प्रकरणांमध्ये तुम्ही <namespace>__<function name> ला कॉल करता. या प्रकरणात, कॉल app__newGame ला आहे, ज्याला MUD नंतर GameSystem मधील newGame कडे राउट करते (नवीन टॅबमध्ये उघडते).

  3. ऑनचेन फंक्शन तपासते की खेळाडूचा कोणताही गेम प्रगतीपथावर नाही, आणि जर नसेल तर PendingGame टेबलमध्ये विनंती जोडते (नवीन टॅबमध्ये उघडते).

  4. सर्व्हर PendingGame मधील बदल शोधतो आणि सबस्क्राईब केलेले फंक्शन चालवतो (नवीन टॅबमध्ये उघडते). हे फंक्शन newGame (नवीन टॅबमध्ये उघडते) ला कॉल करते, जे पुढे createGame (नवीन टॅबमध्ये उघडते) ला कॉल करते.

  5. createGame सर्वात आधी योग्य संख्येच्या माइन्ससह एक यादृच्छिक (random) नकाशा तयार करते (नवीन टॅबमध्ये उघडते). त्यानंतर, ते रिक्त सीमांसह नकाशा तयार करण्यासाठी makeMapBorders (नवीन टॅबमध्ये उघडते) ला कॉल करते, जे Zokrates साठी आवश्यक आहे. शेवटी, createGame नकाशाचा हॅश मिळवण्यासाठी calculateMapHash ला कॉल करते, ज्याचा वापर गेम आयडी म्हणून केला जातो.

  6. newGame फंक्शन gamesInProgress मध्ये नवीन गेम जोडते.

  7. सर्व्हरची शेवटची कृती म्हणजे app__newGameResponse (नवीन टॅबमध्ये उघडते) ला कॉल करणे, जे ऑनचेन आहे. ॲक्सेस कंट्रोल सक्षम करण्यासाठी हे फंक्शन एका वेगळ्या System, ServerSystem (नवीन टॅबमध्ये उघडते) मध्ये आहे. ॲक्सेस कंट्रोल MUD कॉन्फिगरेशन फाईल (नवीन टॅबमध्ये उघडते), mud.config.ts (नवीन टॅबमध्ये उघडते) मध्ये परिभाषित केले आहे.

    ॲक्सेस लिस्ट केवळ एकाच पत्त्याला System कॉल करण्याची अनुमती देते. हे सर्व्हर फंक्शन्सचा ॲक्सेस एकाच पत्त्यापुरता मर्यादित करते, त्यामुळे कोणीही सर्व्हरची तोतयागिरी करू शकत नाही.

  8. ऑनचेन घटक संबंधित टेबल्स अपडेट करतो:

    • PlayerGame मध्ये गेम तयार करा.
    • GamePlayer मध्ये रिव्हर्स मॅपिंग सेट करा.
    • PendingGame मधून विनंती काढून टाका.
  9. सर्व्हर PendingGame मधील बदल ओळखतो, परंतु काहीही करत नाही कारण wantsGame (नवीन टॅबमध्ये उघडते) फॉल्स (false) आहे.

  10. क्लायंटवर gameRecord (नवीन टॅबमध्ये उघडते) खेळाडूच्या पत्त्यासाठी PlayerGame एंट्रीवर सेट केले जाते. जेव्हा PlayerGame बदलते, तेव्हा gameRecord देखील बदलते.

  11. जर gameRecord मध्ये एखादे मूल्य असेल, आणि गेम जिंकला किंवा हरला नसेल, तर क्लायंट नकाशा प्रदर्शित करतो (नवीन टॅबमध्ये उघडते).

खोदणे

  1. खेळाडू नकाशा सेलच्या बटणावर क्लिक करतो (नवीन टॅबमध्ये उघडते), जे dig फंक्शनला (नवीन टॅबमध्ये उघडते) कॉल करते. हे फंक्शन ऑनचेन dig ला (नवीन टॅबमध्ये उघडते) कॉल करते.

  2. ऑनचेन घटक अनेक सॅनिटी चेक्स (sanity checks) करतो (नवीन टॅबमध्ये उघडते), आणि यशस्वी झाल्यास खोदण्याची विनंती PendingDig (नवीन टॅबमध्ये उघडते) मध्ये जोडतो.

  3. सर्व्हर PendingDig मधील बदल शोधतो (नवीन टॅबमध्ये उघडते). जर ते वैध असेल (नवीन टॅबमध्ये उघडते), तर ते निकाल आणि तो वैध असल्याचा पुरावा दोन्ही तयार करण्यासाठी झिरो-नॉलेज कोडला कॉल करते (नवीन टॅबमध्ये उघडते) (खाली स्पष्ट केले आहे).

  4. सर्व्हर (नवीन टॅबमध्ये उघडते) ऑनचेन digResponse (नवीन टॅबमध्ये उघडते) ला कॉल करतो.

  5. digResponse दोन गोष्टी करते. प्रथम, ते शून्य-ज्ञान पुरावा (नवीन टॅबमध्ये उघडते) तपासते. त्यानंतर, जर पुरावा योग्य असेल, तर ते प्रत्यक्षात निकालावर प्रक्रिया करण्यासाठी processDigResult (नवीन टॅबमध्ये उघडते) ला कॉल करते.

  6. processDigResult गेम हरला (नवीन टॅबमध्ये उघडते) आहे की जिंकला (नवीन टॅबमध्ये उघडते) आहे हे तपासते, आणि ऑनचेन नकाशा, Map अपडेट करते (नवीन टॅबमध्ये उघडते).

  7. क्लायंट स्वयंचलितपणे अपडेट्स घेतो आणि खेळाडूला प्रदर्शित केलेला नकाशा अपडेट करतो (नवीन टॅबमध्ये उघडते), आणि लागू असल्यास खेळाडूला सांगतो की तो विजय आहे की पराभव.

Zokrates वापरणे

वर स्पष्ट केलेल्या प्रक्रियांमध्ये आपण झिरो-नॉलेज भागांना ब्लॅक बॉक्स मानून वगळले होते. आता आपण ते उघडून पाहूया आणि तो कोड कसा लिहिला आहे ते समजून घेऊया.

नकाशाचे हॅशिंग

आपण वापरत असलेले Zokrates हॅश फंक्शन, Poseidon (नवीन टॅबमध्ये उघडते) लागू करण्यासाठी आपण हा JavaScript कोड (नवीन टॅबमध्ये उघडते) वापरू शकतो. तथापि, हे अधिक वेगवान असले तरी, ते करण्यासाठी फक्त Zokrates हॅश फंक्शन वापरण्यापेक्षा अधिक गुंतागुंतीचे असेल. हे एक ट्युटोरियल आहे, आणि त्यामुळे कोड साधेपणासाठी ऑप्टिमाइझ केला आहे, कार्यप्रदर्शनासाठी नाही. म्हणून, आपल्याला दोन भिन्न Zokrates प्रोग्राम्सची आवश्यकता आहे, एक फक्त नकाशाचा हॅश मोजण्यासाठी (hash) आणि दुसरा नकाशावरील एखाद्या ठिकाणी खोदण्याच्या परिणामाचा शून्य-ज्ञान पुरावा तयार करण्यासाठी (dig).

हॅश फंक्शन

हे फंक्शन नकाशाचा हॅश मोजते. आपण हा कोड ओळीनुसार पाहू.

import "hashes/poseidon/poseidon.zok" as poseidon;
import "utils/pack/bool/pack128.zok" as pack128;

या दोन ओळी Zokrates स्टँडर्ड लायब्ररी (नवीन टॅबमध्ये उघडते) मधून दोन फंक्शन्स इम्पोर्ट करतात. पहिले फंक्शन (नवीन टॅबमध्ये उघडते) हे Poseidon हॅश (नवीन टॅबमध्ये उघडते) आहे. ते field घटकांचा (नवीन टॅबमध्ये उघडते) अ‍ॅरे घेते आणि field परत करते.

Zokrates मधील फील्ड घटक सामान्यतः 256 बिट्सपेक्षा कमी लांबीचा असतो, परंतु फार कमी नसतो. कोड सोपा करण्यासाठी, आपण नकाशा 512 बिट्सपर्यंत मर्यादित ठेवतो, आणि चार फील्ड्सच्या अ‍ॅरेचे हॅश करतो, आणि प्रत्येक फील्डमध्ये आपण फक्त 128 बिट्स वापरतो. या उद्देशासाठी pack128 फंक्शन (नवीन टॅबमध्ये उघडते) 128 बिट्सच्या अ‍ॅरेला field मध्ये बदलते.

def hashMap(bool[${width+2}][${height+2}] map) -> field {

ही ओळ फंक्शनची व्याख्या सुरू करते. hashMap ला map नावाचा एकच पॅरामीटर मिळतो, जो एक द्विमितीय (two-dimensional) bool(ean) अ‍ॅरे आहे. नकाशाचा आकार width+2 बाय height+2 आहे ज्याची कारणे खाली स्पष्ट केली आहेत.

आपण ${width+2} आणि ${height+2} वापरू शकतो कारण Zokrates प्रोग्राम्स या अ‍ॅप्लिकेशनमध्ये टेम्पलेट स्ट्रिंग्स (नवीन टॅबमध्ये उघडते) म्हणून साठवले जातात. ${ आणि } मधील कोडचे JavaScript द्वारे मूल्यांकन केले जाते, आणि अशा प्रकारे प्रोग्राम वेगवेगळ्या नकाशाच्या आकारांसाठी वापरला जाऊ शकतो. नकाशा पॅरामीटरच्या सभोवताली कोणत्याही बॉम्बशिवाय एक स्थान रुंद सीमा असते, ज्यामुळे आपल्याला रुंदी आणि उंचीमध्ये दोन जोडण्याची आवश्यकता असते.

रिटर्न व्हॅल्यू एक field आहे ज्यामध्ये हॅश असतो.

bool[512] mut map1d = [false; 512];

नकाशा द्विमितीय आहे. तथापि, pack128 फंक्शन द्विमितीय अ‍ॅरेसह कार्य करत नाही. म्हणून आपण प्रथम map1d वापरून नकाशाला 512-बाइट अ‍ॅरेमध्ये सपाट (flatten) करतो. डीफॉल्टनुसार Zokrates व्हेरिएबल्स स्थिरांक (constants) असतात, परंतु आपल्याला लूपमध्ये या अ‍ॅरेला मूल्ये नियुक्त करण्याची आवश्यकता असते, म्हणून आपण ते mut (नवीन टॅबमध्ये उघडते) म्हणून परिभाषित करतो.

आपल्याला अ‍ॅरे इनिशियलाइझ करण्याची आवश्यकता आहे कारण Zokrates मध्ये undefined नाही. [false; 512] एक्स्प्रेशनचा अर्थ 512 false मूल्यांचा अ‍ॅरे (नवीन टॅबमध्ये उघडते) असा होतो.

u32 mut counter = 0;

आपण map1d मध्ये आधीच भरलेले बिट्स आणि जे भरलेले नाहीत त्यांच्यात फरक करण्यासाठी आपल्याला काउंटरची देखील आवश्यकता आहे.

for u32 x in 0..${width+2} {

अशा प्रकारे तुम्ही Zokrates मध्ये for लूप (नवीन टॅबमध्ये उघडते) घोषित करता. Zokrates for लूपला निश्चित मर्यादा असणे आवश्यक आहे, कारण ते लूपसारखे दिसत असले तरी, कंपायलर प्रत्यक्षात ते "अनरोल" करतो. ${width+2} हे एक्स्प्रेशन एक कंपाइल टाइम कॉन्स्टंट आहे कारण कंपायलरला कॉल करण्यापूर्वी TypeScript कोडद्वारे width सेट केले जाते.

for u32 y in 0..${height+2} {
         map1d[counter] = map[x][y];
         counter = counter+1;
      }
   }

नकाशातील प्रत्येक स्थानासाठी, ते मूल्य map1d अ‍ॅरेमध्ये ठेवा आणि काउंटर वाढवा.

field[4] hashMe = [
        pack128(map1d[0..128]),
        pack128(map1d[128..256]),
        pack128(map1d[256..384]),
        pack128(map1d[384..512])
    ];

map1d मधून चार field मूल्यांचा अ‍ॅरे तयार करण्यासाठी pack128 वापरले जाते. Zokrates मध्ये array[a..b] म्हणजे अ‍ॅरेचा तो भाग जो a पासून सुरू होतो आणि b-1 वर संपतो.

return poseidon(hashMe);
}

या अ‍ॅरेला हॅशमध्ये रूपांतरित करण्यासाठी poseidon वापरा.

हॅश प्रोग्राम

गेम आयडेंटिफायर्स तयार करण्यासाठी सर्व्हरला थेट hashMap कॉल करणे आवश्यक आहे. तथापि, Zokrates सुरू करण्यासाठी प्रोग्रामवर फक्त main फंक्शन कॉल करू शकते, म्हणून आपण एक प्रोग्राम तयार करतो ज्यामध्ये main असते जे हॅश फंक्शनला कॉल करते.

${hashFragment}

def main(bool[${width+2}][${height+2}] map) -> field {
    return hashMap(map);
}

डिग (dig) प्रोग्राम

हा अ‍ॅप्लिकेशनच्या झिरो-नॉलेज भागाचा गाभा आहे, जिथे आपण डिग (dig) परिणामांची पडताळणी करण्यासाठी वापरले जाणारे पुरावे तयार करतो.

${hashFragment}

// (x,y) स्थानावरील सुरुंगांची संख्या
def map2mineCount(bool[${width+2}][${height+2}] map, u32 x, u32 y) -> u8 {
   return if map[x+1][y+1] { 1 } else { 0 };
}

नकाशाची सीमा का

शून्य-ज्ञान पुरावे अरिथमॅटिक सर्किट्स (नवीन टॅबमध्ये उघडते) वापरतात, ज्यांना if स्टेटमेंटचा सोपा पर्याय नसतो. त्याऐवजी, ते कंडिशनल ऑपरेटर (नवीन टॅबमध्ये उघडते) च्या समतुल्य वापरतात. जर a शून्य किंवा एक असू शकत असेल, तर तुम्ही if a { b } else { c } ची गणना ab+(1-a)c म्हणून करू शकता.

यामुळे, Zokrates if स्टेटमेंट नेहमी दोन्ही शाखांचे मूल्यांकन करते. उदाहरणार्थ, जर तुमच्याकडे हा कोड असेल:

bool[5] arr = [false; 5];
u32 index=10;
return if index>4 { 0 } else { arr[index] }

यात त्रुटी येईल, कारण त्याला arr[10] ची गणना करणे आवश्यक आहे, जरी त्या मूल्याला नंतर शून्याने गुणले जाणार असले तरीही.

याच कारणामुळे आपल्याला नकाशाच्या सभोवताली एक स्थान रुंद सीमा आवश्यक आहे. आपल्याला एखाद्या स्थानाभोवती असलेल्या खाणींची एकूण संख्या मोजण्याची आवश्यकता आहे, आणि याचा अर्थ असा की आपण जिथे खोदत आहोत त्या स्थानाच्या एक रांग वर आणि खाली, डावीकडे आणि उजवीकडे असलेले स्थान आपल्याला पाहण्याची आवश्यकता आहे. याचा अर्थ असा की Zokrates ला प्रदान केलेल्या नकाशा अ‍ॅरेमध्ये ती स्थाने अस्तित्वात असणे आवश्यक आहे.

def main(private bool[${width+2}][${height+2}] map, u32 x, u32 y) -> (field, u8) {

डीफॉल्टनुसार Zokrates पुराव्यांमध्ये त्यांचे इनपुट्स समाविष्ट असतात. एखाद्या जागेभोवती पाच खाणी आहेत हे जाणून घेण्याचा काही उपयोग नाही जोपर्यंत तुम्हाला ती जागा कोणती आहे हे प्रत्यक्षात माहीत नसते (आणि तुम्ही ते फक्त तुमच्या विनंतीशी जुळवू शकत नाही, कारण मग सिद्धकर्ता भिन्न मूल्ये वापरू शकतो आणि तुम्हाला त्याबद्दल सांगणार नाही). तथापि, Zokrates ला नकाशा प्रदान करताना आपल्याला तो गुप्त ठेवण्याची आवश्यकता आहे. यावर उपाय म्हणजे private पॅरामीटर वापरणे, जो पुराव्याद्वारे उघड केला जात नाही.

यामुळे गैरवापराचा आणखी एक मार्ग खुला होतो. सिद्धकर्ता योग्य निर्देशांक वापरू शकतो, परंतु स्थानाभोवती आणि शक्यतो त्या स्थानावरच कितीही खाणी असलेला नकाशा तयार करू शकतो. हा गैरवापर टाळण्यासाठी, आपण शून्य-ज्ञान पुराव्यामध्ये नकाशाचा हॅश समाविष्ट करतो, जो गेम आयडेंटिफायर आहे.

return (hashMap(map),

येथील रिटर्न व्हॅल्यू एक ट्यूपल (tuple) आहे ज्यामध्ये नकाशा हॅश अ‍ॅरे तसेच डिग (dig) परिणाम समाविष्ट आहे.

if map2mineCount(map, x, y) > 0 { 0xFF } else {

जर त्या स्थानावरच बॉम्ब असेल तर आपण 255 हे विशेष मूल्य म्हणून वापरतो.

map2mineCount(map, x-1, y-1) + map2mineCount(map, x, y-1) + map2mineCount(map, x+1, y-1) +
            map2mineCount(map, x-1, y) + map2mineCount(map, x+1, y) +
            map2mineCount(map, x-1, y+1) + map2mineCount(map, x, y+1) + map2mineCount(map, x+1, y+1)
         }
   );
}

जर खेळाडूला खाण लागली नसेल, तर स्थानाभोवतीच्या क्षेत्रासाठी खाणींची संख्या जोडा आणि ती परत करा.

TypeScript मधून Zokrates वापरणे

Zokrates मध्ये कमांड लाइन इंटरफेस आहे, परंतु या प्रोग्राममध्ये आपण तो TypeScript कोड (नवीन टॅबमध्ये उघडते) मध्ये वापरतो.

ज्या लायब्ररीमध्ये Zokrates व्याख्या आहेत तिला zero-knowledge.ts (नवीन टॅबमध्ये उघडते) म्हणतात.

import { initialize as zokratesInitialize } from "zokrates-js"

Zokrates JavaScript बाइंडिंग्ज (नवीन टॅबमध्ये उघडते) इम्पोर्ट करा. आपल्याला फक्त initialize (नवीन टॅबमध्ये उघडते) फंक्शनची आवश्यकता आहे कारण ते एक प्रॉमिस परत करते जे सर्व Zokrates व्याख्यांचे निराकरण करते.

export const zkFunctions = async (width: number, height: number) : Promise<any> => {

Zokrates प्रमाणेच, आपण फक्त एकच फंक्शन एक्सपोर्ट करतो, जे असिंक्रोनस (नवीन टॅबमध्ये उघडते) देखील आहे. जेव्हा ते शेवटी परत येते, तेव्हा ते अनेक फंक्शन्स प्रदान करते जे आपण खाली पाहू.

const zokrates = await zokratesInitialize()

Zokrates इनिशियलाइझ करा, लायब्ररीमधून आपल्याला आवश्यक असलेले सर्व काही मिळवा.

पुढे आपल्याकडे हॅश फंक्शन आणि आपण वर पाहिलेले दोन Zokrates प्रोग्राम्स आहेत.

const digCompiled = zokrates.compile(digProgram)
const hashCompiled = zokrates.compile(hashProgram)

येथे आपण ते प्रोग्राम्स कंपाइल करतो.

// झिरो-नॉलेज पडताळणीसाठी की तयार करा.
// प्रॉडक्शन सिस्टीमवर तुम्हाला सेटअप सेरेमनी वापरायची असेल.
// (https://zokrates.github.io/toolbox/trusted_setup.html#initializing-a-phase-2-ceremony).
const keySetupResults = zokrates.setup(digCompiled.program, "")
const verifierKey = keySetupResults.vk
const proverKey = keySetupResults.pk

प्रॉडक्शन सिस्टीमवर आपण अधिक गुंतागुंतीची सेटअप सेरेमनी (नवीन टॅबमध्ये उघडते) वापरू शकतो, परंतु प्रात्यक्षिकासाठी हे पुरेसे आहे. वापरकर्त्यांना सिद्धकर्ता की (prover key) माहीत असणे ही समस्या नाही - जोपर्यंत गोष्टी सत्य नसतात तोपर्यंत ते गोष्टी सिद्ध करण्यासाठी त्याचा वापर करू शकत नाहीत. कारण आपण एंट्रॉपी (दुसरा पॅरामीटर, "") निर्दिष्ट करतो, परिणाम नेहमी समानच असणार आहेत.

टीप: Zokrates प्रोग्राम्सचे कंपायलेशन आणि की (key) तयार करणे या संथ प्रक्रिया आहेत. प्रत्येक वेळी त्यांची पुनरावृत्ती करण्याची आवश्यकता नाही, फक्त जेव्हा नकाशाचा आकार बदलतो तेव्हाच. प्रॉडक्शन सिस्टीमवर तुम्ही ते एकदाच कराल आणि नंतर आउटपुट साठवून ठेवाल. मी येथे ते न करण्याचे एकमेव कारण म्हणजे साधेपणा.

calculateMapHash

const calculateMapHash = function (hashMe: boolean[][]): string {
  return (
    "0x" +
    BigInt(zokrates.computeWitness(hashCompiled, [hashMe]).output.slice(1, -1))
      .toString(16)
      .padStart(64, "0")
  )
}

computeWitness (नवीन टॅबमध्ये उघडते) फंक्शन प्रत्यक्षात Zokrates प्रोग्राम चालवते. ते दोन फील्ड्ससह एक स्ट्रक्चर परत करते: output, जे JSON स्ट्रिंग म्हणून प्रोग्रामचे आउटपुट आहे, आणि witness, जी परिणामाचा शून्य-ज्ञान पुरावा तयार करण्यासाठी आवश्यक असलेली माहिती आहे. येथे आपल्याला फक्त आउटपुटची आवश्यकता आहे.

आउटपुट हे "31337" स्वरूपातील एक स्ट्रिंग आहे, जो अवतरण चिन्हांमध्ये (quotation marks) बंद केलेला दशांश (decimal) क्रमांक आहे. परंतु आपल्याला viem साठी आवश्यक असलेले आउटपुट 0x60A7 स्वरूपातील हेक्साडेसिमल क्रमांक आहे. म्हणून आपण अवतरण चिन्हे काढण्यासाठी .slice(1,-1) वापरतो आणि नंतर उर्वरित स्ट्रिंग, जी एक दशांश संख्या आहे, तिला BigInt (नवीन टॅबमध्ये उघडते) मध्ये चालवण्यासाठी BigInt वापरतो. .toString(16) या BigInt ला हेक्साडेसिमल स्ट्रिंगमध्ये रूपांतरित करते, आणि "0x"+ हेक्साडेसिमल संख्यांसाठी मार्कर जोडते.

// खोदा आणि निकालाचा शून्य-ज्ञान पुरावा परत करा
// (सर्व्हर-साइड कोड)

शून्य-ज्ञान पुराव्यामध्ये सार्वजनिक इनपुट्स (x आणि y) आणि परिणाम (नकाशाचा हॅश आणि बॉम्बची संख्या) समाविष्ट असतात.

    const zkDig = function(map: boolean[][], x: number, y: number) : any {
        if (x<0 || x>=width || y<0 || y>=height)
            throw new Error("Trying to dig outside the map")

Zokrates मध्ये निर्देशांक (index) मर्यादेबाहेर आहे की नाही हे तपासणे ही एक समस्या आहे, म्हणून आपण ते येथे करतो.

const runResults = zokrates.computeWitness(digCompiled, [map, `${x}`, `${y}`])

डिग (dig) प्रोग्राम कार्यान्वित करा.

        const proof = zokrates.generateProof(
            digCompiled.program,
            runResults.witness,
            proverKey)

        return proof
    }

generateProof (नवीन टॅबमध्ये उघडते) वापरा आणि पुरावा परत करा.

const solidityVerifier = `
        // Map size: ${width} x ${height}
        \n${zokrates.exportSolidityVerifier(verifierKey)}
        `

एक Solidity पडताळणीकर्ता, एक स्मार्ट कॉन्ट्रॅक्ट जे आपण ब्लॉकचेनवर प्रस्थापित करू शकतो आणि digCompiled.program द्वारे व्युत्पन्न केलेल्या पुराव्यांची पडताळणी करण्यासाठी वापरू शकतो.

    return {
        zkDig,
        calculateMapHash,
        solidityVerifier,
    }
}

शेवटी, इतर कोडला आवश्यक असलेले सर्व काही परत करा.

सुरक्षा चाचण्या

सुरक्षा चाचण्या महत्त्वाच्या आहेत कारण कार्यक्षमतेतील त्रुटी (bug) शेवटी उघडकीस येते. परंतु जर ॲप्लिकेशन असुरक्षित असेल, तर कोणीतरी फसवणूक करून आणि इतरांच्या मालकीची संसाधने घेऊन जाईपर्यंत ते बऱ्याच काळासाठी लपून राहण्याची शक्यता असते.

परवानग्या

या गेममध्ये एक विशेषाधिकार प्राप्त घटक आहे, तो म्हणजे सर्व्हर. ServerSystem (नवीन टॅबमध्ये उघडते) मधील फंक्शन्स कॉल करण्याची परवानगी असलेला हा एकमेव वापरकर्ता आहे. परवानगीयुक्त (permissioned) फंक्शन्सना केवळ सर्व्हर खाते म्हणून कॉल करण्याची परवानगी आहे हे पडताळण्यासाठी आपण cast (नवीन टॅबमध्ये उघडते) वापरू शकतो.

सर्व्हरची खाजगी की setupNetwork.ts मध्ये आहे (नवीन टॅबमध्ये उघडते).

  1. anvil (ब्लॉकचेन) चालवणाऱ्या संगणकावर, हे एन्व्हायर्नमेंट व्हेरिएबल्स सेट करा.

    WORLD_ADDRESS=0x8d8b6b8414e1e3dcfd4168561b9be6bd3bf6ec4b
    UNAUTHORIZED_KEY=0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
    AUTHORIZED_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
    
  2. पडताळणीकर्ता पत्ता अनधिकृत पत्ता म्हणून सेट करण्याचा प्रयत्न करण्यासाठी cast वापरा.

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

    केवळ cast अपयश नोंदवत नाही, तर तुम्ही ब्राउझरवरील गेममध्ये MUD Dev Tools उघडू शकता, Tables वर क्लिक करू शकता आणि app__VerifierAddress निवडू शकता. पत्ता शून्य नाही हे पहा.

  3. पडताळणीकर्ता पत्ता सर्व्हरचा पत्ता म्हणून सेट करा.

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

    app__VerifiedAddress मधील पत्ता आता शून्य असावा.

एकाच System मधील सर्व MUD फंक्शन्स एकाच ॲक्सेस कंट्रोलमधून जातात, त्यामुळे मी ही चाचणी पुरेशी मानतो. जर तुम्हाला तसे वाटत नसेल, तर तुम्ही ServerSystem (नवीन टॅबमध्ये उघडते) मधील इतर फंक्शन्स तपासू शकता.

झिरो-नॉलेज गैरवापर

Zokrates पडताळणी करण्यासाठी लागणारे गणित या ट्युटोरिअलच्या (आणि माझ्या क्षमतेच्या) कक्षेबाहेरचे आहे. तथापि, झिरो-नॉलेज कोड योग्यरित्या केला नसल्यास तो अयशस्वी होतो हे पडताळण्यासाठी आपण त्यावर विविध तपासण्या करू शकतो. या सर्व चाचण्यांसाठी आपल्याला zero-knowledge.ts (नवीन टॅबमध्ये उघडते) बदलणे आणि संपूर्ण ॲप्लिकेशन रीस्टार्ट करणे आवश्यक असेल. केवळ सर्व्हर प्रक्रिया रीस्टार्ट करणे पुरेसे नाही, कारण यामुळे ॲप्लिकेशन एका अशक्य स्थितीत (state) जाते (खेळाडूचा गेम सुरू असतो, परंतु तो गेम सर्व्हरसाठी उपलब्ध नसतो).

चुकीचे उत्तर

सर्वात सोपी शक्यता म्हणजे शून्य-ज्ञान पुरावा (zero-knowledge proof) मध्ये चुकीचे उत्तर देणे. ते करण्यासाठी, आपण zkDig मध्ये जातो आणि ओळ 91 सुधारित करतो (नवीन टॅबमध्ये उघडते):

proof.inputs[3] = "0x" + "1".padStart(64, "0")

याचा अर्थ असा की योग्य उत्तर काहीही असले तरी आपण नेहमी एक बॉम्ब असल्याचा दावा करू. या आवृत्तीसह खेळण्याचा प्रयत्न करा, आणि तुम्हाला pnpm dev स्क्रीनच्या server टॅबमध्ये ही त्रुटी दिसेल:

cause: {
        code: 3,
        message: 'execution reverted: revert: Zero knowledge verification fail',
        data: '0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000
000000000000000000000000000000000000000000000000205a65726f206b6e6f776c6564676520766572696669636174696f6
e206661696c'
      },

त्यामुळे अशा प्रकारची फसवणूक अयशस्वी होते.

चुकीचा पुरावा

जर आपण योग्य माहिती दिली, परंतु केवळ चुकीचा पुरावा डेटा दिला तर काय होईल? आता, ओळ 91 याने बदला:

proof.proof = {
  a: ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],
  b: [
    ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],
    ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],
  ],
  c: ["0x" + "1".padStart(64, "0"), "0x" + "2".padStart(64, "0")],
}

ते अद्यापही अयशस्वी होते, परंतु आता ते कोणत्याही कारणाशिवाय अयशस्वी होते कारण ते पडताळणीकर्ता कॉल दरम्यान घडते.

वापरकर्ता झिरो ट्रस्ट कोडची पडताळणी कशी करू शकतो?

स्मार्ट कॉन्ट्रॅक्ट पडताळणे तुलनेने सोपे असते. सामान्यतः, डेव्हलपर सोर्स कोड ब्लॉक एक्सप्लोरर वर प्रकाशित करतो, आणि ब्लॉक एक्सप्लोरर पडताळणी करतो की सोर्स कोड कॉन्ट्रॅक्ट प्रस्थापना व्यवहार मधील कोडमध्ये संकलित (compile) होतो. MUD System च्या बाबतीत हे थोडं अधिक गुंतागुंतीचे (नवीन टॅबमध्ये उघडते) आहे, पण फार नाही.

झिरो-नॉलेजच्या बाबतीत हे अधिक कठीण आहे. पडताळणीकर्ता काही स्थिरांक (constants) समाविष्ट करतो आणि त्यावर काही आकडेमोड करतो. यावरून काय सिद्ध केले जात आहे हे समजत नाही.

    function verifyingKey() pure internal returns (VerifyingKey memory vk) {
        vk.alpha = Pairing.G1Point(uint256(0x0f43f4fe7b5c2326fed4ac6ed2f4003ab9ab4ea6f667c2bdd77afb068617ee16), uint256(0x25a77832283f9726935219b5f4678842cda465631e72dbb24708a97ba5d0ce6f));
        vk.beta = Pairing.G2Point([uint256(0x2cebd0fbd21aca01910581537b21ae4fed46bc0e524c055059aa164ba0a6b62b), uint256(0x18fd4a7bc386cf03a95af7163d5359165acc4e7961cb46519e6d9ee4a1e2b7e9)], [uint256(0x11449dee0199ef6d8eebfe43b548e875c69e7ce37705ee9a00c81fe52f11a009), uint256(0x066d0c83b32800d3f335bb9e8ed5e2924cf00e77e6ec28178592eac9898e1a00)]);

यावरील उपाय म्हणजे, किमान जोपर्यंत ब्लॉक एक्सप्लोरर त्यांच्या युजर इंटरफेसमध्ये Zokrates पडताळणी जोडत नाहीत तोपर्यंत, ॲप्लिकेशन डेव्हलपर्सनी Zokrates प्रोग्राम्स उपलब्ध करून देणे आणि किमान काही वापरकर्त्यांनी योग्य पडताळणी की (verification key) वापरून ते स्वतः संकलित (compile) करणे.

असे करण्यासाठी:

  1. Zokrates इन्स्टॉल करा (नवीन टॅबमध्ये उघडते).

  2. Zokrates प्रोग्रामसह dig.zok ही फाईल तयार करा. खालील कोड असे गृहीत धरतो की तुम्ही मूळ नकाशाचा आकार 10x5 ठेवला आहे.

  3. Zokrates कोड संकलित करा आणि पडताळणी की तयार करा. पडताळणी की मूळ सर्व्हरमध्ये वापरलेल्या त्याच एंट्रॉपी सह तयार केली जाणे आवश्यक आहे, या प्रकरणात एक रिकामी स्ट्रिंग (empty string) (नवीन टॅबमध्ये उघडते).

    zokrates compile --input dig.zok
    zokrates setup -e ""
    
  4. स्वतःचा Solidity पडताळणीकर्ता तयार करा, आणि तो ब्लॉकचेन वरील पडताळणीकर्त्याशी कार्यात्मकदृष्ट्या समान असल्याची पडताळणी करा (सर्व्हर एक टिप्पणी (comment) जोडतो, परंतु ते महत्त्वाचे नाही).

    zokrates export-verifier
    diff verifier.sol ~/20240901-secret-state/packages/contracts/src/verifier.sol
    

डिझाइनचे निर्णय

कोणत्याही पुरेशा गुंतागुंतीच्या ॲप्लिकेशनमध्ये स्पर्धात्मक डिझाइनची उद्दिष्टे असतात ज्यासाठी तडजोड करणे आवश्यक असते. चला काही तडजोडी पाहूया आणि सध्याचा उपाय इतर पर्यायांपेक्षा का चांगला आहे ते समजून घेऊया.

झिरो-नॉलेज का

माइनस्वीपरसाठी तुम्हाला खरोखर झिरो-नॉलेजची आवश्यकता नाही. सर्व्हर नेहमी नकाशा ठेवू शकतो आणि गेम संपल्यावर तो सर्व उघड करू शकतो. त्यानंतर, गेमच्या शेवटी, स्मार्ट कॉन्ट्रॅक्ट नकाशाच्या हॅशची गणना करू शकते, ते जुळते की नाही याची पडताळणी करू शकते आणि जर ते जुळत नसेल तर सर्व्हरला दंड करू शकते किंवा गेम पूर्णपणे रद्द करू शकते.

मी हा सोपा उपाय वापरला नाही कारण तो फक्त चांगल्या प्रकारे परिभाषित केलेल्या अंतिम स्थितीसह (end state) लहान गेमसाठी कार्य करतो. जेव्हा एखादा गेम संभाव्यतः अमर्याद असतो (जसे की स्वायत्त जगांच्या (नवीन टॅबमध्ये उघडते) बाबतीत), तेव्हा तुम्हाला अशा उपायाची आवश्यकता असते जो स्थिती उघड न करता सिद्ध करतो.

एक ट्युटोरिअल म्हणून या लेखासाठी समजायला सोपा असा एक लहान गेम आवश्यक होता, परंतु हे तंत्र मोठ्या गेमसाठी सर्वात उपयुक्त आहे.

Zokrates का?

Zokrates (नवीन टॅबमध्ये उघडते) ही एकमेव झिरो-नॉलेज लायब्ररी उपलब्ध नाही, परंतु ती एका सामान्य, इम्परेटिव्ह (नवीन टॅबमध्ये उघडते) प्रोग्रामिंग भाषेसारखी आहे आणि बुलियन व्हेरिएबल्सना सपोर्ट करते.

तुमच्या ॲप्लिकेशनसाठी, वेगवेगळ्या आवश्यकतांसह, तुम्ही Circum (नवीन टॅबमध्ये उघडते) किंवा Cairo (नवीन टॅबमध्ये उघडते) वापरण्यास प्राधान्य देऊ शकता.

Zokrates कधी कंपाईल करावे

या प्रोग्राममध्ये आम्ही प्रत्येक वेळी सर्व्हर सुरू झाल्यावर (नवीन टॅबमध्ये उघडते) Zokrates प्रोग्राम्स कंपाईल करतो. हे स्पष्टपणे संसाधनांचा अपव्यय आहे, परंतु हे एक ट्युटोरिअल आहे, जे साधेपणासाठी ऑप्टिमाइझ केलेले आहे.

जर मी प्रोडक्शन-लेव्हल ॲप्लिकेशन लिहित असतो, तर मी तपासले असते की माझ्याकडे या माइनफिल्ड आकाराच्या कंपाईल केलेल्या Zokrates प्रोग्राम्सची फाईल आहे का, आणि असल्यास ती वापरली असती. ऑनचेन पडताळणीकर्ता कॉन्ट्रॅक्ट प्रस्थापित करण्यासाठीही हेच खरे आहे.

पडताळणीकर्ता आणि सिद्धकर्ता की तयार करणे

की तयार करणे (नवीन टॅबमध्ये उघडते) ही आणखी एक शुद्ध गणना आहे जी दिलेल्या माइनफिल्ड आकारासाठी एकापेक्षा जास्त वेळा करण्याची आवश्यकता नाही. पुन्हा, साधेपणासाठी हे फक्त एकदाच केले जाते.

याव्यतिरिक्त, आपण सेटअप सेरेमनी (नवीन टॅबमध्ये उघडते) वापरू शकतो. सेटअप सेरेमनीचा फायदा असा आहे की शून्य-ज्ञान पुराव्यामध्ये फसवणूक करण्यासाठी तुम्हाला प्रत्येक सहभागीकडून एंट्रॉपी किंवा काही मध्यवर्ती परिणामाची आवश्यकता असते. जर किमान एक सेरेमनी सहभागी प्रामाणिक असेल आणि ती माहिती हटवत असेल, तर शून्य-ज्ञान पुरावे विशिष्ट हल्ल्यांपासून सुरक्षित असतात. तथापि, माहिती सर्व ठिकाणाहून हटवली गेली आहे याची पडताळणी करण्यासाठी कोणतीही यंत्रणा नाही. जर शून्य-ज्ञान पुरावे अत्यंत महत्त्वाचे असतील, तर तुम्हाला सेटअप सेरेमनीमध्ये सहभागी व्हायचे असते.

येथे आपण perpetual powers of tau (नवीन टॅबमध्ये उघडते) वर अवलंबून आहोत, ज्यामध्ये डझनभर सहभागी होते. हे कदाचित पुरेसे सुरक्षित आणि खूप सोपे आहे. आम्ही की तयार करताना एंट्रॉपी देखील जोडत नाही, ज्यामुळे वापरकर्त्यांना झिरो-नॉलेज कॉन्फिगरेशनची पडताळणी करणे सोपे होते.

पडताळणी कुठे करावी

आपण शून्य-ज्ञान पुराव्यांची पडताळणी एकतर ऑनचेन (ज्यासाठी गॅस खर्च होतो) किंवा क्लायंटमध्ये (verify (नवीन टॅबमध्ये उघडते) वापरून) करू शकतो. मी पहिला पर्याय निवडला, कारण यामुळे तुम्हाला एकदा पडताळणीकर्त्याची पडताळणी करता येते आणि जोपर्यंत त्याचा कॉन्ट्रॅक्ट पत्ता समान राहतो तोपर्यंत तो बदलणार नाही यावर विश्वास ठेवता येतो. जर पडताळणी क्लायंटवर केली गेली असती, तर तुम्हाला प्रत्येक वेळी क्लायंट डाउनलोड करताना प्राप्त होणाऱ्या कोडची पडताळणी करावी लागली असती.

तसेच, हा गेम सिंगल प्लेयर असला तरी, बरेच ब्लॉकचेन गेम्स मल्टी-प्लेयर असतात. ऑनचेन पडताळणीचा अर्थ असा आहे की तुम्ही शून्य-ज्ञान पुराव्याची फक्त एकदाच पडताळणी करता. क्लायंटमध्ये हे करण्यासाठी प्रत्येक क्लायंटला स्वतंत्रपणे पडताळणी करणे आवश्यक असेल.

नकाशा TypeScript मध्ये फ्लॅटन करायचा की Zokrates मध्ये?

साधारणपणे, जेव्हा प्रक्रिया TypeScript किंवा Zokrates मध्ये केली जाऊ शकते, तेव्हा ती TypeScript मध्ये करणे चांगले असते, जे खूप वेगवान असते आणि त्यासाठी शून्य-ज्ञान पुराव्यांची आवश्यकता नसते. उदाहरणार्थ, हेच कारण आहे की आपण Zokrates ला हॅश देत नाही आणि ते बरोबर असल्याची पडताळणी करायला लावत नाही. हॅशिंग Zokrates च्या आत करावे लागते, परंतु परत केलेला हॅश आणि ऑनचेन हॅश यांच्यातील जुळणी त्याच्या बाहेर होऊ शकते.

तथापि, आपण तरीही Zokrates मध्ये नकाशा फ्लॅटन करतो (नवीन टॅबमध्ये उघडते), तर आपण ते TypeScript मध्ये करू शकलो असतो. याचे कारण असे की इतर पर्याय, माझ्या मते, अधिक वाईट आहेत.

  • Zokrates कोडला बुलियनचा एक-आयामी ॲरे (one dimensional array) प्रदान करा आणि द्वि-आयामी नकाशा मिळवण्यासाठी x*(height+2) +y सारख्या अभिव्यक्तीचा वापर करा. यामुळे कोड (नवीन टॅबमध्ये उघडते) थोडा अधिक गुंतागुंतीचा होईल, म्हणून मी ठरवले की ट्युटोरिअलसाठी परफॉर्मन्सचा फायदा तितका मोलाचा नाही.

  • Zokrates ला एक-आयामी ॲरे आणि द्वि-आयामी ॲरे दोन्ही पाठवा. तथापि, या उपायाने आपल्याला काहीही मिळत नाही. Zokrates कोडला हे पडताळून पाहावे लागेल की त्याला दिलेला एक-आयामी ॲरे खरोखरच द्वि-आयामी ॲरेचे योग्य सादरीकरण आहे. त्यामुळे परफॉर्मन्समध्ये कोणताही फायदा होणार नाही.

  • Zokrates मध्ये द्वि-आयामी ॲरे फ्लॅटन करा. हा सर्वात सोपा पर्याय आहे, म्हणून मी तो निवडला.

नकाशे कुठे साठवायचे

या ॲप्लिकेशनमध्ये gamesInProgress (नवीन टॅबमध्ये उघडते) हे मेमरीमधील फक्त एक व्हेरिएबल आहे. याचा अर्थ असा की जर तुमचा सर्व्हर बंद पडला आणि तो रीस्टार्ट करण्याची आवश्यकता असेल, तर त्याने साठवलेली सर्व माहिती नष्ट होते. खेळाडू केवळ त्यांचा गेम सुरू ठेवू शकत नाहीत असे नाही, तर ते नवीन गेम देखील सुरू करू शकत नाहीत कारण ऑनचेन घटकाला वाटते की त्यांचा गेम अद्याप प्रगतीपथावर आहे.

प्रोडक्शन सिस्टीमसाठी हे स्पष्टपणे खराब डिझाइन आहे, ज्यामध्ये तुम्ही ही माहिती डेटाबेसमध्ये साठवाल. मी येथे व्हेरिएबल वापरण्याचे एकमेव कारण म्हणजे हे एक ट्युटोरिअल आहे आणि साधेपणा हा मुख्य विचार आहे.

निष्कर्ष: कोणत्या परिस्थितीत हे योग्य तंत्र आहे?

तर, आता तुम्हाला माहित आहे की असा गेम कसा लिहायचा ज्यामध्ये सर्व्हर अशी गुप्त स्थिती साठवतो जी ऑनचेन असण्याची गरज नाही. पण तुम्ही हे कोणत्या प्रकरणांमध्ये केले पाहिजे? यासाठी दोन मुख्य बाबी विचारात घ्याव्या लागतील.

  • दीर्घकाळ चालणारा गेम: वर नमूद केल्याप्रमाणे, एका छोट्या गेममध्ये गेम संपल्यानंतर तुम्ही स्थिती प्रकाशित करू शकता आणि त्यानंतर सर्व गोष्टींची पडताळणी करू शकता. परंतु जेव्हा गेमला खूप किंवा अनिश्चित वेळ लागतो आणि स्थिती गुप्त ठेवणे आवश्यक असते, तेव्हा हा पर्याय नसतो.

  • काही प्रमाणात केंद्रीकरण स्वीकार्य: शून्य-ज्ञान पुरावे अखंडतेची पडताळणी करू शकतात, की एखादी संस्था परिणामांमध्ये फेरफार करत नाहीये. ते काय करू शकत नाहीत, तर ती संस्था अद्याप उपलब्ध असेल आणि संदेशांना उत्तरे देईल याची खात्री करणे. ज्या परिस्थितींमध्ये उपलब्धता देखील विकेंद्रित असणे आवश्यक असते, तिथे शून्य-ज्ञान पुरावे हे पुरेसे समाधान नाही आणि तुम्हाला मल्टी-पार्टी कॉम्प्युटेशन (नवीन टॅबमध्ये उघडते) ची आवश्यकता असते.

माझ्या अधिक कामासाठी येथे पहा (नवीन टॅबमध्ये उघडते).

आभार

  • अल्वारो अलोन्सो यांनी या लेखाचा मसुदा वाचला आणि Zokrates बद्दलचे माझे काही गैरसमज दूर केले.

उर्वरित कोणत्याही चुकांसाठी मी जबाबदार आहे.