मुख्य सामग्री पर जाएं

गुप्त स्थिति के लिए शून्य-ज्ञान का उपयोग करना

सर्वर
ऑफचेन
केंद्रीकृत
शून्य-ज्ञान
zokrates
mud
गोपनीयता
उन्नत
ओरी पोमेरेंट्ज़
15 मार्च 2025
33 मिनट पढ़ें

ब्लॉकचेन पर कोई रहस्य नहीं होते हैं। ब्लॉकचेन पर जो कुछ भी पोस्ट किया जाता है वह हर किसी के पढ़ने के लिए खुला होता है। यह आवश्यक है, क्योंकि ब्लॉकचेन इस बात पर आधारित है कि कोई भी इसे सत्यापित कर सके। हालाँकि, गेम अक्सर गुप्त स्थिति पर निर्भर करते हैं। उदाहरण के लिए, माइनस्वीपर (एक नए टैब में खुलता है) का गेम बिल्कुल भी समझ में नहीं आता है यदि आप केवल एक ब्लॉक एक्सप्लोरर पर जा सकते हैं और नक्शा देख सकते हैं।

सबसे सरल समाधान गुप्त स्थिति को रखने के लिए एक सर्वर घटक का उपयोग करना है। हालाँकि, हम ब्लॉकचेन का उपयोग इसलिए करते हैं ताकि गेम डेवलपर द्वारा धोखाधड़ी को रोका जा सके। हमें सर्वर घटक की ईमानदारी सुनिश्चित करने की आवश्यकता है। सर्वर स्थिति का एक हैश प्रदान कर सकता है, और यह साबित करने के लिए शून्य-ज्ञान प्रमाण का उपयोग कर सकता है कि किसी चाल के परिणाम की गणना करने के लिए उपयोग की गई स्थिति सही है।

इस लेख को पढ़ने के बाद आप जानेंगे कि इस प्रकार का गुप्त स्थिति रखने वाला सर्वर, स्थिति दिखाने के लिए एक क्लाइंट, और दोनों के बीच संचार के लिए एक ऑनचेन घटक कैसे बनाया जाए। हमारे द्वारा उपयोग किए जाने वाले मुख्य उपकरण होंगे:

उपकरणउद्देश्यइस संस्करण पर सत्यापित
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. सुनिश्चित करें कि आपने पूर्वापेक्षाएँ स्थापित कर ली हैं (एक नए टैब में खुलता है): Node (एक नए टैब में खुलता है), 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: कुंजी तीन मानों का एक टपल है:

    • gameId: 32-बाइट मान जो उस नक्शे का हैश है जिस पर खिलाड़ी खेल रहा है (गेम पहचानकर्ता)।
    • x निर्देशांक
    • y निर्देशांक

    मान एक एकल संख्या है। यदि बम का पता चला था तो यह 255 है। अन्यथा, यह उस स्थान के आसपास बमों की संख्या प्लस एक है। हम केवल बमों की संख्या का उपयोग नहीं कर सकते, क्योंकि डिफ़ॉल्ट रूप से EVM में सभी स्टोरेज और MUD में सभी पंक्ति मान शून्य होते हैं। हमें "खिलाड़ी ने अभी तक यहां खुदाई नहीं की है" और "खिलाड़ी ने यहां खुदाई की, और पाया कि आसपास शून्य बम हैं" के बीच अंतर करने की आवश्यकता है।

इसके अलावा, क्लाइंट और सर्वर के बीच संचार ऑनचेन घटक के माध्यम से होता है। इसे भी टेबल्स का उपयोग करके लागू किया गया है।

  • PendingGame: नया गेम शुरू करने के लिए असेवित (Unserviced) अनुरोध।
  • PendingDig: किसी विशिष्ट गेम में किसी विशिष्ट स्थान पर खुदाई करने के लिए असेवित अनुरोध। यह एक ऑफचेन टेबल (एक नए टैब में खुलता है) है, जिसका अर्थ है कि इसे EVM स्टोरेज में नहीं लिखा जाता है, यह केवल घटनाएँ का उपयोग करके ऑफचेन पढ़ने योग्य है।

निष्पादन और डेटा प्रवाह

ये प्रवाह क्लाइंट, ऑनचेन घटक और सर्वर के बीच निष्पादन का समन्वय करते हैं।

आरंभीकरण (Initialization)

जब आप mprocs चलाते हैं, तो ये चरण होते हैं:

  1. mprocs (एक नए टैब में खुलता है) चार घटक चलाता है:

  2. contracts पैकेज MUD अनुबंधों को तैनात करता है और फिर PostDeploy.s.sol स्क्रिप्ट (एक नए टैब में खुलता है) चलाता है। यह स्क्रिप्ट कॉन्फ़िगरेशन सेट करती है। GitHub का कोड आठ माइन्स के साथ 10x5 माइनफ़ील्ड (एक नए टैब में खुलता है) निर्दिष्ट करता है।

  3. सर्वर (एक नए टैब में खुलता है) MUD सेट अप (एक नए टैब में खुलता है) करके शुरू होता है। अन्य बातों के अलावा, यह डेटा सिंक्रनाइज़ेशन को सक्रिय करता है, ताकि प्रासंगिक टेबल्स की एक प्रति सर्वर की मेमोरी में मौजूद रहे।

  4. सर्वर एक फ़ंक्शन को सब्सक्राइब करता है जिसे Configuration टेबल बदलने पर (एक नए टैब में खुलता है) निष्पादित किया जाना है। यह फ़ंक्शन (एक नए टैब में खुलता है) PostDeploy.s.sol के निष्पादित होने और टेबल को संशोधित करने के बाद कॉल किया जाता है।

  5. जब सर्वर आरंभीकरण फ़ंक्शन के पास कॉन्फ़िगरेशन होता है, तो [यह सर्वर के शून्य-ज्ञान भाग को आरंभ करने के लिए zkFunctions को कॉल करता है](https://github.com/qbzzt/20240901-secret-state/blob/main/packages/server/src/app.ts#L34-L35)। (एक नए टैब में खुलता है) यह तब तक नहीं हो सकता जब तक हमें कॉन्फ़िगरेशन नहीं मिल जाता क्योंकि शून्य-ज्ञान फ़ंक्शंस में माइनफ़ील्ड की चौड़ाई और ऊंचाई स्थिरांक के रूप में होनी चाहिए।

  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 को कॉल करता है, जिसका उपयोग गेम ID के रूप में किया जाता है।

  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 में कोई मान है, और गेम जीता या हारा नहीं गया है, तो क्लाइंट नक्शा प्रदर्शित करता है (एक नए टैब में खुलता है)

खुदाई (Dig)

  1. खिलाड़ी मैप सेल के बटन पर क्लिक करता है (एक नए टैब में खुलता है), जो dig फ़ंक्शन (एक नए टैब में खुलता है) को कॉल करता है। यह फ़ंक्शन ऑनचेन dig को कॉल करता है (एक नए टैब में खुलता है)

  2. ऑनचेन घटक कई सैनिटी चेक (sanity checks) करता है (एक नए टैब में खुलता है), और यदि सफल होता है तो खुदाई के अनुरोध को PendingDig (एक नए टैब में खुलता है) में जोड़ता है।

  3. सर्वर PendingDig में बदलाव का पता लगाता है (एक नए टैब में खुलता है)यदि यह मान्य है (एक नए टैब में खुलता है), तो यह परिणाम और इसके मान्य होने का प्रमाण दोनों उत्पन्न करने के लिए शून्य-ज्ञान कोड को कॉल करता है (एक नए टैब में खुलता है) (नीचे समझाया गया है)।

  4. सर्वर (एक नए टैब में खुलता है) ऑनचेन digResponse (एक नए टैब में खुलता है) को कॉल करता है।

  5. digResponse दो काम करता है। सबसे पहले, यह शून्य-ज्ञान प्रमाण (एक नए टैब में खुलता है) की जांच करता है। फिर, यदि प्रमाण सही पाया जाता है, तो यह वास्तव में परिणाम को प्रोसेस करने के लिए processDigResult (एक नए टैब में खुलता है) को कॉल करता है।

  6. processDigResult जांचता है कि गेम हारा (एक नए टैब में खुलता है) या जीता (एक नए टैब में खुलता है) गया है या नहीं, और ऑनचेन मैप, Map को अपडेट करता है (एक नए टैब में खुलता है)

  7. क्लाइंट स्वचालित रूप से अपडेट प्राप्त करता है और खिलाड़ी को प्रदर्शित नक्शे को अपडेट करता है (एक नए टैब में खुलता है), और यदि लागू हो तो खिलाड़ी को बताता है कि यह जीत है या हार।

Zokrates का उपयोग करना

ऊपर बताए गए प्रवाहों में हमने शून्य-ज्ञान वाले हिस्सों को छोड़ दिया था, और उन्हें एक ब्लैक बॉक्स की तरह माना था। अब आइए इसे खोलें और देखें कि वह कोड कैसे लिखा जाता है।

मैप की हैशिंग

हम Poseidon (एक नए टैब में खुलता है) को लागू करने के लिए इस JavaScript कोड (एक नए टैब में खुलता है) का उपयोग कर सकते हैं, जो कि हमारे द्वारा उपयोग किया जाने वाला Zokrates हैश फ़ंक्शन है। हालाँकि, जबकि यह तेज़ होगा, यह इसे करने के लिए केवल Zokrates हैश फ़ंक्शन का उपयोग करने की तुलना में अधिक जटिल भी होगा। यह एक ट्यूटोरियल है, और इसलिए कोड को सरलता के लिए अनुकूलित किया गया है, प्रदर्शन के लिए नहीं। इसलिए, हमें दो अलग-अलग Zokrates प्रोग्राम की आवश्यकता है, एक केवल मैप के हैश की गणना करने के लिए (hash) और दूसरा वास्तव में मैप पर किसी स्थान पर खुदाई के परिणाम का शून्य-ज्ञान प्रमाण बनाने के लिए (dig)।

हैश फ़ंक्शन

यह वह फ़ंक्शन है जो मैप के हैश की गणना करता है। हम इस कोड को लाइन दर लाइन समझेंगे।

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

ये दो लाइनें Zokrates मानक लाइब्रेरी (एक नए टैब में खुलता है) से दो फ़ंक्शन आयात करती हैं। पहला फ़ंक्शन (एक नए टैब में खुलता है) एक Poseidon हैश (एक नए टैब में खुलता है) है। यह field तत्वों (एक नए टैब में खुलता है) की एक सरणी (array) लेता है और एक 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 द्वारा किया जाता है, और इस तरह प्रोग्राम का उपयोग विभिन्न मैप आकारों के लिए किया जा सकता है। मैप पैरामीटर के चारों ओर बिना किसी बम के एक स्थान चौड़ी सीमा (border) होती है, यही कारण है कि हमें चौड़ाई और ऊंचाई में दो जोड़ने की आवश्यकता है।

रिटर्न मान एक field है जिसमें हैश होता है।

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

मैप द्वि-आयामी है। हालाँकि, pack128 फ़ंक्शन द्वि-आयामी सरणियों के साथ काम नहीं करता है। इसलिए हम पहले map1d का उपयोग करके मैप को 512-बाइट सरणी में समतल (flatten) करते हैं। डिफ़ॉल्ट रूप से Zokrates चर (variables) स्थिरांक (constants) होते हैं, लेकिन हमें एक लूप में इस सरणी को मान निर्दिष्ट करने की आवश्यकता है, इसलिए हम इसे mut (एक नए टैब में खुलता है) के रूप में परिभाषित करते हैं।

हमें सरणी को इनिशियलाइज़ करने की आवश्यकता है क्योंकि Zokrates में undefined नहीं है। [false; 512] अभिव्यक्ति का अर्थ है 512 false मानों की एक सरणी (एक नए टैब में खुलता है)

u32 mut counter = 0;

हमें उन बिट्स के बीच अंतर करने के लिए एक काउंटर की भी आवश्यकता है जिन्हें हमने पहले ही map1d में भर दिया है और जिन्हें हमने नहीं भरा है।

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

इस तरह आप Zokrates में एक for लूप (एक नए टैब में खुलता है) घोषित करते हैं। एक Zokrates for लूप की सीमाएँ निश्चित होनी चाहिए, क्योंकि भले ही यह एक लूप प्रतीत होता है, कंपाइलर वास्तव में इसे "अनरोल" (unroll) करता है। अभिव्यक्ति ${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] का अर्थ सरणी का वह स्लाइस (slice) है जो a से शुरू होता है और b-1 पर समाप्त होता है।

return poseidon(hashMe);
}

इस सरणी को हैश में बदलने के लिए poseidon का उपयोग करें।

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

गेम पहचानकर्ता (identifiers) बनाने के लिए सर्वर को सीधे hashMap को कॉल करने की आवश्यकता होती है। हालाँकि, Zokrates शुरू करने के लिए किसी प्रोग्राम पर केवल main फ़ंक्शन को कॉल कर सकता है, इसलिए हम एक main के साथ एक प्रोग्राम बनाते हैं जो हैश फ़ंक्शन को कॉल करता है।

${hashFragment}

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

डिग (खुदाई) प्रोग्राम

यह एप्लिकेशन के शून्य-ज्ञान वाले हिस्से का मुख्य भाग है, जहाँ हम वे प्रमाण तैयार करते हैं जिनका उपयोग खुदाई के परिणामों को सत्यापित करने के लिए किया जाता है।

${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 };
}

मैप बॉर्डर क्यों

शून्य-ज्ञान प्रमाण अंकगणितीय सर्किट (arithmetic circuits) (एक नए टैब में खुलता है) का उपयोग करते हैं, जिनका if कथन के समान कोई आसान विकल्प नहीं है। इसके बजाय, वे सशर्त ऑपरेटर (conditional operator) (एक नए टैब में खुलता है) के समतुल्य का उपयोग करते हैं। यदि 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] }

यह त्रुटि (error) देगा, क्योंकि इसे arr[10] की गणना करने की आवश्यकता है, भले ही उस मान को बाद में शून्य से गुणा किया जाएगा।

यही कारण है कि हमें मैप के चारों ओर एक स्थान चौड़ी सीमा की आवश्यकता है। हमें किसी स्थान के चारों ओर खदानों (mines) की कुल संख्या की गणना करने की आवश्यकता है, और इसका मतलब है कि हमें उस स्थान के एक पंक्ति ऊपर और नीचे, बाएँ और दाएँ देखना होगा जहाँ हम खुदाई कर रहे हैं। जिसका अर्थ है कि वे स्थान उस मैप सरणी में मौजूद होने चाहिए जो Zokrates को प्रदान की गई है।

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

डिफ़ॉल्ट रूप से Zokrates प्रमाणों में उनके इनपुट शामिल होते हैं। यह जानना कोई काम का नहीं है कि किसी स्थान के आसपास पाँच खदानें हैं जब तक कि आप वास्तव में यह न जानें कि वह कौन सा स्थान है (और आप इसे केवल अपने अनुरोध से मेल नहीं करा सकते, क्योंकि तब प्रमाणक (prover) अलग-अलग मानों का उपयोग कर सकता है और आपको इसके बारे में नहीं बता सकता है)। हालाँकि, हमें Zokrates को प्रदान करते समय मैप को गुप्त रखने की आवश्यकता है। इसका समाधान एक private पैरामीटर का उपयोग करना है, जो प्रमाण द्वारा प्रकट नहीं किया जाता है।

यह दुरुपयोग के लिए एक और रास्ता खोलता है। प्रमाणक सही निर्देशांक (coordinates) का उपयोग कर सकता है, लेकिन स्थान के चारों ओर, और संभवतः स्थान पर ही किसी भी संख्या में खदानों के साथ एक मैप बना सकता है। इस दुरुपयोग को रोकने के लिए, हम शून्य-ज्ञान प्रमाण में मैप का हैश शामिल करते हैं, जो कि गेम पहचानकर्ता है।

return (hashMap(map),

यहाँ रिटर्न मान एक टपल (tuple) है जिसमें मैप हैश सरणी के साथ-साथ खुदाई का परिणाम भी शामिल है।

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 (एक नए टैब में खुलता है) फ़ंक्शन की आवश्यकता है क्योंकि यह एक प्रॉमिस (promise) लौटाता है जो सभी Zokrates परिभाषाओं को रिज़ॉल्व करता है।

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

स्वयं Zokrates के समान, हम भी केवल एक फ़ंक्शन निर्यात करते हैं, जो एसिंक्रोनस (asynchronous) (एक नए टैब में खुलता है) भी है। जब यह अंततः वापस आता है, तो यह कई फ़ंक्शन प्रदान करता है जैसा कि हम नीचे देखेंगे।

const zokrates = await zokratesInitialize()

Zokrates को इनिशियलाइज़ करें, लाइब्रेरी से वह सब कुछ प्राप्त करें जिसकी हमें आवश्यकता है।

इसके बाद हमारे पास हैश फ़ंक्शन और दो Zokrates प्रोग्राम हैं जिन्हें हमने ऊपर देखा था।

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

यहाँ हम उन प्रोग्रामों को संकलित (compile) करते हैं।

// शून्य-ज्ञान सत्यापन के लिए कुंजियाँ बनाएँ।
// प्रोडक्शन सिस्टम पर आप एक सेटअप समारोह का उपयोग करना चाहेंगे।
// (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

एक उत्पादन (production) प्रणाली पर हम अधिक जटिल सेटअप समारोह (setup ceremony) (एक नए टैब में खुलता है) का उपयोग कर सकते हैं, लेकिन यह प्रदर्शन के लिए काफी अच्छा है। यह कोई समस्या नहीं है कि उपयोगकर्ता प्रमाणक कुंजी (prover key) जान सकते हैं - वे अभी भी इसका उपयोग चीजों को साबित करने के लिए नहीं कर सकते जब तक कि वे सत्य न हों। क्योंकि हम एन्ट्रोपी (दूसरा पैरामीटर, "") निर्दिष्ट करते हैं, परिणाम हमेशा समान होने वाले हैं।

नोट: Zokrates प्रोग्राम का संकलन और कुंजी निर्माण धीमी प्रक्रियाएँ हैं। उन्हें हर बार दोहराने की आवश्यकता नहीं है, केवल तब जब मैप का आकार बदलता है। एक उत्पादन प्रणाली पर आप उन्हें एक बार करेंगे, और फिर आउटपुट को संग्रहीत करेंगे। यहाँ मैं ऐसा नहीं कर रहा हूँ, इसका एकमात्र कारण सरलता है।

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 प्रोग्राम चलाता है। यह दो फ़ील्ड्स के साथ एक संरचना (structure) लौटाता है: output, जो JSON स्ट्रिंग के रूप में प्रोग्राम का आउटपुट है, और witness, जो परिणाम का शून्य-ज्ञान प्रमाण बनाने के लिए आवश्यक जानकारी है। यहाँ हमें केवल आउटपुट की आवश्यकता है।

आउटपुट "31337" के रूप में एक स्ट्रिंग है, जो उद्धरण चिह्नों (quotation marks) में संलग्न एक दशमलव संख्या है। लेकिन 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}`])

डिग प्रोग्राम निष्पादित (execute) करें।

        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,
    }
}

अंत में, वह सब कुछ लौटाएँ जिसकी अन्य कोड को आवश्यकता हो सकती है।

सुरक्षा परीक्षण

सुरक्षा परीक्षण महत्वपूर्ण हैं क्योंकि कार्यक्षमता से जुड़ा बग अंततः सामने आ ही जाता है। लेकिन अगर एप्लिकेशन असुरक्षित है, तो यह संभवतः लंबे समय तक छिपा रहेगा, जब तक कि कोई धोखाधड़ी करके दूसरों के संसाधनों को चुराकर इसे उजागर न कर दे।

अनुमतियाँ

इस गेम में एक विशेषाधिकार प्राप्त इकाई है, सर्वर। यह एकमात्र उपयोगकर्ता है जिसे ServerSystem (एक नए टैब में खुलता है) में फ़ंक्शंस को कॉल करने की अनुमति है। हम यह सत्यापित करने के लिए cast (एक नए टैब में खुलता है) का उपयोग कर सकते हैं कि अनुमति-प्राप्त (permissioned) फ़ंक्शंस पर कॉल केवल सर्वर खाते के रूप में ही अनुमत हैं।

सर्वर की निजी कुंजी 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 (एक नए टैब में खुलता है) को बदलने और पूरे एप्लिकेशन को पुनरारंभ करने की आवश्यकता होगी। केवल सर्वर प्रक्रिया को पुनरारंभ करना पर्याप्त नहीं है, क्योंकि यह एप्लिकेशन को एक असंभव स्थिति में डाल देता है (खिलाड़ी का गेम प्रगति पर है, लेकिन गेम अब सर्वर के लिए उपलब्ध नहीं है)।

गलत उत्तर

सबसे सरल संभावना शून्य-ज्ञान प्रमाण में गलत उत्तर प्रदान करना है। ऐसा करने के लिए, हम 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")],
}

यह अभी भी विफल रहता है, लेकिन अब यह बिना किसी कारण के विफल होता है क्योंकि यह सत्यापनकर्ता कॉल के दौरान होता है।

कोई उपयोगकर्ता शून्य-विश्वास (zero trust) कोड को कैसे सत्यापित कर सकता है?

स्मार्ट अनुबंधों को सत्यापित करना अपेक्षाकृत आसान है। आमतौर पर, डेवलपर स्रोत कोड को एक ब्लॉक एक्सप्लोरर पर प्रकाशित करता है, और ब्लॉक एक्सप्लोरर यह सत्यापित करता है कि स्रोत कोड अनुबंध तैनाती लेन-देन में मौजूद कोड में संकलित (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 प्रोग्राम उपलब्ध कराएं, और कम से कम कुछ उपयोगकर्ता उचित सत्यापन कुंजी के साथ उन्हें स्वयं संकलित करें।

ऐसा करने के लिए:

  1. Zokrates इंस्टॉल करें (एक नए टैब में खुलता है)

  2. Zokrates प्रोग्राम के साथ एक फ़ाइल, dig.zok बनाएँ। नीचे दिया गया कोड यह मानकर चलता है कि आपने मूल मैप का आकार 10x5 रखा है।

  3. Zokrates कोड को संकलित करें और सत्यापन कुंजी बनाएँ। सत्यापन कुंजी को उसी एन्ट्रोपी के साथ बनाया जाना चाहिए जिसका उपयोग मूल सर्वर में किया गया था, इस मामले में एक खाली स्ट्रिंग (एक नए टैब में खुलता है)

    zokrates compile --input dig.zok
    zokrates setup -e ""
    
  4. अपने आप Solidity सत्यापनकर्ता बनाएँ, और सत्यापित करें कि यह कार्यात्मक रूप से ब्लॉकचेन पर मौजूद सत्यापनकर्ता के समान है (सर्वर एक टिप्पणी जोड़ता है, लेकिन वह महत्वपूर्ण नहीं है)।

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

डिज़ाइन निर्णय

किसी भी पर्याप्त रूप से जटिल एप्लिकेशन में प्रतिस्पर्धी डिज़ाइन लक्ष्य होते हैं जिनके लिए समझौते (trade-offs) की आवश्यकता होती है। आइए कुछ समझौतों पर नज़र डालें और देखें कि वर्तमान समाधान अन्य विकल्पों की तुलना में बेहतर क्यों है।

शून्य-ज्ञान क्यों

माइनस्वीपर (minesweeper) के लिए आपको वास्तव में शून्य-ज्ञान की आवश्यकता नहीं है। सर्वर हमेशा मैप को अपने पास रख सकता है, और फिर गेम खत्म होने पर उसे पूरी तरह से प्रकट कर सकता है। फिर, गेम के अंत में, स्मार्ट अनुबंध मैप हैश की गणना कर सकता है, सत्यापित कर सकता है कि यह मेल खाता है, और यदि यह मेल नहीं खाता है तो सर्वर को दंडित कर सकता है या गेम को पूरी तरह से अनदेखा कर सकता है।

मैंने इस सरल समाधान का उपयोग नहीं किया क्योंकि यह केवल अच्छी तरह से परिभाषित अंतिम स्थिति वाले छोटे गेम के लिए काम करता है। जब कोई गेम संभावित रूप से अनंत होता है (जैसे कि स्वायत्त दुनिया (autonomous worlds) (एक नए टैब में खुलता है) के मामले में), तो आपको एक ऐसे समाधान की आवश्यकता होती है जो स्थिति को प्रकट किए बिना उसे प्रमाणित करे।

एक ट्यूटोरियल के रूप में इस लेख को एक छोटे गेम की आवश्यकता थी जिसे समझना आसान हो, लेकिन यह तकनीक लंबे गेम के लिए सबसे उपयोगी है।

Zokrates क्यों?

Zokrates (एक नए टैब में खुलता है) उपलब्ध एकमात्र शून्य-ज्ञान लाइब्रेरी नहीं है, लेकिन यह एक सामान्य, इम्परेटिव (imperative) (एक नए टैब में खुलता है) प्रोग्रामिंग भाषा के समान है और बूलियन (boolean) चर का समर्थन करती है।

अपने एप्लिकेशन के लिए, विभिन्न आवश्यकताओं के साथ, आप Circum (एक नए टैब में खुलता है) या Cairo (एक नए टैब में खुलता है) का उपयोग करना पसंद कर सकते हैं।

Zokrates को कब संकलित (compile) करें

इस प्रोग्राम में हम Zokrates प्रोग्राम को हर बार सर्वर शुरू होने पर (एक नए टैब में खुलता है) संकलित करते हैं। यह स्पष्ट रूप से संसाधनों की बर्बादी है, लेकिन यह एक ट्यूटोरियल है, जिसे सरलता के लिए अनुकूलित किया गया है।

यदि मैं एक उत्पादन-स्तर (production-level) का एप्लिकेशन लिख रहा होता, तो मैं जांचता कि क्या मेरे पास इस माइनफ़ील्ड आकार पर संकलित Zokrates प्रोग्राम वाली कोई फ़ाइल है, और यदि ऐसा है तो उसका उपयोग करता। ऑनचेन सत्यापनकर्ता अनुबंध तैनात करने के लिए भी यही बात लागू होती है।

सत्यापनकर्ता और प्रमाणक कुंजियाँ बनाना

कुंजी निर्माण (एक नए टैब में खुलता है) एक और शुद्ध गणना है जिसे किसी दिए गए माइनफ़ील्ड आकार के लिए एक से अधिक बार करने की आवश्यकता नहीं है। फिर से, सरलता के लिए इसे केवल एक बार किया जाता है।

इसके अतिरिक्त, हम एक सेटअप समारोह (setup ceremony) (एक नए टैब में खुलता है) का उपयोग कर सकते हैं। सेटअप समारोह का लाभ यह है कि शून्य-ज्ञान प्रमाण पर धोखा देने के लिए आपको प्रत्येक प्रतिभागी से एन्ट्रोपी या कुछ मध्यवर्ती परिणाम की आवश्यकता होती है। यदि कम से कम एक समारोह प्रतिभागी ईमानदार है और उस जानकारी को हटा देता है, तो शून्य-ज्ञान प्रमाण कुछ हमलों से सुरक्षित हैं। हालाँकि, यह सत्यापित करने के लिए कोई तंत्र नहीं है कि जानकारी हर जगह से हटा दी गई है। यदि शून्य-ज्ञान प्रमाण गंभीर रूप से महत्वपूर्ण हैं, तो आप सेटअप समारोह में भाग लेना चाहेंगे।

यहाँ हम perpetual powers of tau (एक नए टैब में खुलता है) पर भरोसा करते हैं, जिसमें दर्जनों प्रतिभागी थे। यह शायद काफी सुरक्षित है, और बहुत सरल है। हम कुंजी निर्माण के दौरान एन्ट्रोपी भी नहीं जोड़ते हैं, जिससे उपयोगकर्ताओं के लिए शून्य-ज्ञान कॉन्फ़िगरेशन को सत्यापित करना आसान हो जाता है।

कहाँ सत्यापित करें

हम शून्य-ज्ञान प्रमाणों को या तो ऑनचेन (जिसमें गैस खर्च होती है) या क्लाइंट में (verify (एक नए टैब में खुलता है) का उपयोग करके) सत्यापित कर सकते हैं। मैंने पहला चुना, क्योंकि यह आपको एक बार सत्यापनकर्ता को सत्यापित करने देता है और फिर भरोसा करता है कि यह तब तक नहीं बदलता जब तक कि इसके लिए अनुबंध का पता समान रहता है। यदि सत्यापन क्लाइंट पर किया गया था, तो आपको हर बार क्लाइंट डाउनलोड करने पर प्राप्त होने वाले कोड को सत्यापित करना होगा।

इसके अलावा, जबकि यह गेम सिंगल प्लेयर है, बहुत सारे ब्लॉकचेन गेम मल्टी-प्लेयर हैं। ऑनचेन सत्यापन का मतलब है कि आप केवल एक बार शून्य-ज्ञान प्रमाण को सत्यापित करते हैं। इसे क्लाइंट में करने के लिए प्रत्येक क्लाइंट को स्वतंत्र रूप से सत्यापित करने की आवश्यकता होगी।

TypeScript या Zokrates में मैप को समतल (Flatten) करें?

सामान्य तौर पर, जब प्रोसेसिंग TypeScript या Zokrates दोनों में की जा सकती है, तो इसे TypeScript में करना बेहतर होता है, जो बहुत तेज़ है, और इसके लिए शून्य-ज्ञान प्रमाणों की आवश्यकता नहीं होती है। उदाहरण के लिए, यही कारण है कि हम Zokrates को हैश प्रदान नहीं करते हैं और इसे सत्यापित नहीं कराते हैं कि यह सही है। हैशिंग Zokrates के अंदर की जानी चाहिए, लेकिन लौटाए गए हैश और ऑनचेन हैश के बीच का मिलान इसके बाहर हो सकता है।

हालाँकि, हम अभी भी Zokrates में मैप को समतल करते हैं (एक नए टैब में खुलता है), जबकि हम इसे TypeScript में कर सकते थे। इसका कारण यह है कि अन्य विकल्प, मेरी राय में, बदतर हैं।

  • Zokrates कोड को बूलियन का एक आयामी (one dimensional) ऐरे प्रदान करें, और दो आयामी (two dimensional) मैप प्राप्त करने के लिए x*(height+2) +y जैसे व्यंजक (expression) का उपयोग करें। यह कोड (एक नए टैब में खुलता है) को कुछ अधिक जटिल बना देगा, इसलिए मैंने तय किया कि ट्यूटोरियल के लिए प्रदर्शन लाभ इसके लायक नहीं है।

  • Zokrates को एक आयामी ऐरे और दो आयामी ऐरे दोनों भेजें। हालाँकि, इस समाधान से हमें कुछ हासिल नहीं होता है। Zokrates कोड को यह सत्यापित करना होगा कि उसे प्रदान किया गया एक आयामी ऐरे वास्तव में दो आयामी ऐरे का सही प्रतिनिधित्व है। इसलिए कोई प्रदर्शन लाभ नहीं होगा।

  • Zokrates में दो आयामी ऐरे को समतल करें। यह सबसे सरल विकल्प है, इसलिए मैंने इसे चुना।

मैप्स को कहाँ स्टोर करें

इस एप्लिकेशन में gamesInProgress (एक नए टैब में खुलता है) मेमोरी में केवल एक चर (variable) है। इसका मतलब है कि यदि आपका सर्वर बंद हो जाता है और उसे फिर से शुरू करने की आवश्यकता होती है, तो उसके द्वारा संग्रहीत सभी जानकारी खो जाती है। न केवल खिलाड़ी अपना गेम जारी रखने में असमर्थ होते हैं, बल्कि वे एक नया गेम भी शुरू नहीं कर सकते क्योंकि ऑनचेन घटक सोचता है कि उनका गेम अभी भी प्रगति पर है।

यह स्पष्ट रूप से एक उत्पादन प्रणाली (production system) के लिए खराब डिज़ाइन है, जिसमें आप इस जानकारी को डेटाबेस में संग्रहीत करेंगे। मैंने यहाँ एक चर का उपयोग केवल इसलिए किया है क्योंकि यह एक ट्यूटोरियल है और सरलता मुख्य विचार है।

निष्कर्ष: किन परिस्थितियों में यह उचित तकनीक है?

तो, अब आप जानते हैं कि एक ऐसे सर्वर के साथ गेम कैसे लिखा जाता है जो गुप्त स्थिति को संग्रहीत करता है जिसे ऑनचेन नहीं होना चाहिए। लेकिन आपको ऐसा किन मामलों में करना चाहिए? इसके दो मुख्य विचार हैं।

  • लंबे समय तक चलने वाला गेम: जैसा कि ऊपर बताया गया है, एक छोटे गेम में आप गेम खत्म होने के बाद स्थिति को प्रकाशित कर सकते हैं और फिर सब कुछ सत्यापित करवा सकते हैं। लेकिन जब गेम में लंबा या अनिश्चित समय लगता है, और स्थिति को गुप्त रखने की आवश्यकता होती है, तो यह कोई विकल्प नहीं है।

  • कुछ केंद्रीकरण स्वीकार्य: शून्य-ज्ञान प्रमाण अखंडता को सत्यापित कर सकते हैं, कि कोई इकाई परिणामों में हेरफेर नहीं कर रही है। वे यह सुनिश्चित नहीं कर सकते कि इकाई अभी भी उपलब्ध होगी और संदेश का उत्तर देगी। ऐसी स्थितियों में जहां उपलब्धता को भी विकेंद्रीकृत करने की आवश्यकता होती है, शून्य-ज्ञान प्रमाण पर्याप्त समाधान नहीं हैं, और आपको बहु-पक्षीय गणना (multi-party computation) (एक नए टैब में खुलता है) की आवश्यकता होती है।

मेरे और काम के लिए यहां देखें (एक नए टैब में खुलता है)

आभार

  • Alvaro Alonso ने इस लेख का एक ड्राफ्ट पढ़ा और Zokrates के बारे में मेरी कुछ गलतफहमियों को दूर किया।

शेष बची कोई भी त्रुटि मेरी जिम्मेदारी है।