हम additions (जोड़) को वेरीफाई करने के लिए डिज़ाइन किए गए एक बेसिक zk-dapp के स्टेप-बाय-स्टेप एक्सप्लोरेशन का प्रदर्शन करेंगे। यह एप्लिकेशन यूज़र्स को यह साबित करने में सक्षम बनाता है कि ब्लॉकचेन पर वास्तविक नंबरों का खुलासा किए बिना, दो नंबरों, X और Y का योग Z के बराबर है। हालाँकि इस समस्या को हल करने के लिए ज़रूरी नहीं है कि zero-knowledge proofs की आवश्यकता हो, लेकिन हम सरलता बनाए रखने और समझ बढ़ाने के लिए इस उदाहरण में उनका उपयोग करेंगे।
आइए कोड में गोता लगाएँ, या dapp को रन करने के लिए स्किप करें।
हम noir-zk-fullstack-example रेपो को लोकली क्लोन करके शुरुआत करेंगे;
git clone https://github.com/RareSkills/noir-zk-fullstack-example.git
नोट: कोड को प्रभावी ढंग से समझने के लिए, noir और typescript का वर्किंग नॉलेज होना आवश्यक है।
डिपेंडेंसीज़ (Dependencies) इंस्टॉल करना
हमारे पास package.json फ़ाइल में पहले से ही विशिष्ट वर्ज़न बताए गए हैं। इंस्टॉल करने के लिए, रन करें:
npm install
yarn का उपयोग न करें क्योंकि यह आवश्यक NPM के विशिष्ट वर्ज़न्स को प्राप्त करने में असमर्थ है।
डिप्लॉय (Backend)
प्रोजेक्ट को बिल्ड करने और कॉन्ट्रैक्ट्स को लोकली डिप्लॉय करने के लिए, http://localhost:8545 पर एक लोकल डेवलपमेंट EVM इनिशिएट करना आवश्यक है। इसे पूरा करने के लिए, हम पहले फ़ाइल का नाम .env.example से बदलकर .env करते हैं, फिर निम्नलिखित कमांड को एग्जीक्यूट करने के लिए एक नया टर्मिनल खोलते हैं:
npx hardhat node
आपके पास रन करने के लिए विभिन्न नेटवर्क्स चुनने की सुविधा है। ऐसा करने के लिए, आपको निम्नलिखित बदलाव करने होंगे: सबसे पहले, डिप्लॉयर की प्राइवेट की (private key) और Alchemy की API की (API key) जोड़कर .env फ़ाइल के कंटेंट्स को मॉडिफाई करें। इसके बाद, hardhat.config.ts फ़ाइल पर नेविगेट करें और एक नया नेटवर्क कॉन्फ़िगरेशन शामिल करें।
एक बार हो जाने के बाद, आप वांछित नेटवर्क निर्दिष्ट करने के लिए NETWORK एनवायरनमेंट वेरिएबल का उपयोग करके डिप्लॉय कर सकते हैं। उदाहरण के लिए NETWORK=mumbai npm run build या NETWORK=sepoia npm run build। इस dapp के उद्देश्य के लिए, हम इस कमांड का उपयोग करके लोकली डिप्लॉय करेंगे:
NETWORK=localhost npm run build
उपर्युक्त कमांड को एग्जीक्यूट करने से उल्लिखित क्रम में चार अतिरिक्त कमांड्स का एग्जीक्यूशन ट्रिगर होता है:
- hardhat run scripts/genContract.ts
- hardhat compile
- hardhat run --network ${NETWORK} scripts/deploy.ts
- next build
जब ये एग्जीक्यूट होते हैं तो क्या होता है?
-
hardhat run scripts/genContract.ts
import { NoirServer } from '../utils/noir/noirServer'; async function main() { const noir = new NoirServer();await noir.compile(); noir.getSmartContract() process.exit(); } // We recommend this pattern to be able to use async/await everywhere // and properly handle errors. main().catch(error => { console.error(error); process.exitCode = 1; });genContract.tsस्क्रिप्ट को एग्जीक्यूट करने से NoirServer क्लास से compile() मेथड कॉल होता है जो./circuits/srcडायरेक्टरी में लिखे गए noir सर्किट को कंपाइल करता है और ACIR (Abstract Circuit Intermediate Representation) भी जनरेट करता है। साथ ही जनरेट किए गए ACIR के साथ barretenberg के setup_generic_prover_and_verifier को कॉल करके this.prover और this.verifier को इनिशियलाइज़ करता है।async compile() { // I'm running on the server so I can use the file system initialiseResolver((id: any) => { try { const code = fs.readFileSync(`circuits/src/${id}`, { encoding: 'utf8' }) as string; return code } catch (err) { console.error(err); throw err; } }); const compiled_noir = compile({ entry_point: 'main.nr', }); this.compiled = compiled_noir; this.acir = acir_read_bytes(this.compiled.circuit); [this.prover, this.verifier] = await setup_generic_prover_and_verifier(this.acir); };इसके अतिरिक्त, getSmartContract मेथड को कॉल किया जाता है, जो
./contract/plonk_vk.solपर एक solidity कॉन्ट्रैक्ट जनरेट करता है। यह कॉन्ट्रैक्ट एग्जीक्यूशन प्रोसेस के हिस्से के रूप में बनाया गया है।getSmartContract() { const sc = this.verifier.SmartContract(); // The user must have a folder called 'contract' in the root directory. If not, we create it. if (!fs.existsSync(path.join(__dirname, '../../contract'))) { console.log('Contract folder does not exist. Creating...'); fs.mkdirSync(path.join(__dirname, '../../contract')); } // If the user already has a file called 'plonk_vk.sol' in the 'contract' folder, we delete it. if (fs.existsSync(path.join(__dirname, '../../contract/plonk_vk.sol'))) { fs.unlinkSync(path.join(__dirname, '../../contract/plonk_vk.sol')); } // We write the contract to a file called 'plonk_vk.sol' in the 'contract' folder. fs.writeFileSync(path.join(__dirname, '../../contract/plonk_vk.sol'), sc, { flag: 'w', }); return sc; } -
hardhat compile
यह कमांड
./contractडायरेक्टरी में स्थित कॉन्ट्रैक्ट(s) को कंपाइल करता है। इस विशिष्ट मामले में, यह plonk_vk.sol कॉन्ट्रैक्ट को कंपाइल करता है। -
hardhat run --network ${NETWORK} scripts/deploy.ts
import { writeFileSync } from 'fs'; import { ethers } from 'hardhat'; async function main() { // Deploy the verifier contractconst Verifier = await ethers.getContractFactory('TurboVerifier'); const verifier = await Verifier.deploy(); // Get the address of the deployed verifier contract const verifierAddr = await verifier.deployed(); // Create a config object const config = { chainId: ethers.provider.network.chainId, verifier: verifierAddr.address, }; // Print the config console.log('Deployed at', config); writeFileSync('utils/addresses.json', JSON.stringify(config), { flag: 'w' }); process.exit(); } // We recommend this pattern to be able to use async/await everywhere // and properly handle errors. main().catch(error => { console.error(error); process.exitCode = 1; });यह स्क्रिप्ट plonk_vk.sol को NETWORK एनवायरनमेंट वेरिएबल को असाइन किए गए नेटवर्क पर डिप्लॉय करती है और डिप्लॉय किए गए एड्रेस को
./utils/addresses.jsonमें लिखती है। -
next build
प्रोडक्शन के लिए हमारे एप्लिकेशन का एक ऑप्टिमाइज़्ड वर्ज़न जनरेट करता है।
Proof कैलकुलेट करना (Frontend)
डेवलपमेंट सर्वर शुरू करने के लिए, निम्नलिखित कमांड एग्जीक्यूट करें:
npm run dev
अपने वेब ब्राउज़र पर http://localhost:3000 पर नेविगेट करें। अपने MetaMask वॉलेट को dapp से कनेक्ट करें और अपने Metamask नेटवर्क को Localhost नेटवर्क पर स्विच करें। फिर, दो इनपुट वैल्यूज़ प्रदान करें और Calculate proof बटन पर क्लिक करें। यह proof कैलकुलेशन शुरू करेगा और ऑनचेन (onchain) वेरीफाई भी करेगा।
./components डायरेक्टरी के भीतर, components.tsx फ़ाइल में दो उल्लेखनीय फंक्शन्स हैं जो उन एक्शन्स को हैंडल करते हैं:
- calculateProof
- verifyProof
समझने के लिए कोड पर कमेंट्स दिए गए हैं।
1. calculateProof
// Calculates proof
const calculateProof = async () => {
// only launch if we do have an acir to calculate the proof from
// set a pending state to show a spinner
setPending(true);
if (input.x == "" || input.y == "") {
toast.error('Fields can not be empty!');
setPending(false);
} else {
// launching a new worker for the proof calculation
const worker = new Worker(new URL('../utils/prover.ts', import.meta.url));
// handling the response from the worker
worker.onmessage = e => {
if (e.data instanceof Error) {
toast.error('Error while calculating proof');
setPending(false);
} else {
toast.success('Proof calculated');
setProof(e.data);
setPending(false);
}
};
// sending the acir and input to the worker
worker.postMessage({ input });
}
};
यह सबसे पहले चेक करता है कि इनपुट फ़ील्ड्स खाली तो नहीं हैं। यदि इनपुट फ़ील्ड्स में वैल्यूज़ हैं, तो यह उन इनपुट्स को नए बनाए गए वर्कर (worker) को भेजने के लिए आगे बढ़ता है और ./utils/prover.ts फ़ाइल में onmessage फंक्शन को भी कॉल करता है।
// @ts-ignore
import { NoirBrowser } from '../utils/noir/noirBrowser';
// // Add an event listener for the message event
onmessage = async event => {
try {
const { input } = event.data;
const hexInputObj = Object.entries(input).reduce((newObj, [key, value]) => {
newObj[key] = (value as number).toString(16).padStart(2, '0');
return newObj;
}, {});
const noir = new NoirBrowser();
await noir.compile();
const proof = await noir.createProof({ input: hexInputObj })
console.log(hexInputObj)
postMessage(proof);
} catch (er) {
console.log(er);
postMessage(er);
} finally {
close();
}
};
proof बनाने के लिए, barretenberg लाइब्रेरी से create_proof फंक्शन को इनवोक (invoked) किया जाता है। यह तीन आर्ग्यूमेंट्स स्वीकार करता है: this.prover ऑब्जेक्ट, acir, और input।
async createProof({input} : {input: any}) {
const proof = await create_proof(this.prover, this.acir, input);
return proof;
}
1. verifyProof
const verifyProof = async () => {
// only launch if we do have an acir and a proof to verify
if (proof) {
// launching a new worker for the verification
const worker = new Worker(new URL('../utils/verifier.ts', import.meta.url));
console.log('worker launched');
// handling the response from the worker
worker.onmessage = async e => {
if (e.data instanceof Error) {
toast.error('Error while verifying proof');
} else {
toast.success('Proof verified');
// Verifies proof on-chain
const ethers = new Ethers();
const ver = await ethers.contract.verify(proof);
if (ver) {
toast.success('Proof verified on-chain!');
setVerification(true);
} else {
toast.error('Proof failed on-chain verification');
setVerification(false);
}
}
};
// sending the acir and proof to the worker
worker.postMessage({ proof });
}
};
यह पहले चेक करता है कि क्या वेरिफिकेशन के लिए कोई proof उपलब्ध है। यदि कोई proof मौजूद है, तो यह एक नया वर्कर लॉन्च करता है। फिर proof को वर्कर को भेजा जाता है, और ./utils/verifier.ts फ़ाइल में onmessage फंक्शन को barretenberg के verify_proof फंक्शन को कॉल करके आने वाले proof को हैंडल करने के लिए कॉल किया जाता है।
async verifyProof({proof} : {proof: any}) {
const verification = await verify_proof(this.verifier, proof);
return verification;
}
यदि पूरी प्रोसेस उम्मीद के मुताबिक काम कर रही है, तो फंक्शन को true रिटर्न करना चाहिए।
dApp रन करना
1. रेपो को लोकली क्लोन करें
git clone https://github.com/RareSkills/noir-zk-fullstack-example.git
2. डिपेंडेंसीज़ इंस्टॉल करें
npm install
3. http://localhost:8545 पर लोकल डेवलपमेंट EVM स्टार्ट करें
फ़ाइल का नाम .env.example से बदलकर .env करें, फिर निम्नलिखित कमांड को एग्जीक्यूट करने के लिए एक नया टर्मिनल खोलें:
npx hardhat node
4. प्रोजेक्ट को बिल्ड करें
NETWORK=localhost npm run build
5. डेवलपमेंट सर्वर स्टार्ट करें
npm run dev
अपने वेब ब्राउज़र पर http://localhost:3000 खोलें। अपने MetaMask वॉलेट को dapp से कनेक्ट करें और अपने Metamask नेटवर्क को Localhost नेटवर्क पर स्विच करें।
यदि आप इसे नहीं ढूंढ पा रहे हैं, तो Metamask नेटवर्क सेटिंग्स खोलें, एक नया नेटवर्क जोड़ें और इसे निम्नलिखित डिटेल्स के साथ कॉन्फ़िगर करें:
- Network name
- Localhost 8545
- New RPC URL
- http://localhost:8545
- Chain ID - 1337 - Currency Symbol - ETH
नेटवर्क कॉन्फ़िगरेशन को सेव करें, अपने MetaMask वॉलेट को Localhost नेटवर्क पर स्विच करें और dApp को टेस्ट करें :)
निष्कर्ष (Conclusion)
Noir के सक्रिय डेवलपमेंट के कारण, प्रोजेक्ट में अक्सर अपडेट्स और सुधार होते रहते हैं। परिणामस्वरूप, विभिन्न पैकेजों के नवीनतम वर्ज़न्स का एक-दूसरे के साथ इनकंपैटिबिलिटी (incompatibilities) दिखाना असामान्य नहीं है।
वर्ज़न इनकंपैटिबिलिटी के कारण होने वाली संभावित समस्याओं से बचने और एक निर्बाध डेवलपमेंट अनुभव सुनिश्चित करने के लिए नवीनतम रिलीज़ नोट्स और कम्युनिटी डिस्कशन्स के साथ अपडेट रहना महत्वपूर्ण है।
और जानें
zero knowledge प्रोग्रामिंग के बारे में अधिक जानने के लिए, हमारा zero knowledge course देखें। एडवांस स्मार्ट कॉन्ट्रैक्ट डेवलपमेंट के लिए, हमारा Solidity Bootcamp देखें।
मूल रूप से 28 मई, 2023 को प्रकाशित