परिचय
यह अध्याय Circom कोड और Rank 1 Constraint System (R1CS) के बीच के संबंध को दर्शाता है जिसमें यह कंपाइल होता है।
Circom को समझने के लिए R1CSs को समझना अत्यंत महत्वपूर्ण है, इसलिए यदि आपने अभी तक नहीं किया है तो Rank 1 Constraint Systems को अवश्य दोहरा लें।
Circom की कार्यप्रणाली को समझाने के लिए, हम कुछ उदाहरणों के साथ शुरुआत करेंगे।
Example 1: साधारण गुणा
मान लीजिए कि हम यह जांचने के लिए ZK proofs बनाने का प्रयास कर रहे हैं कि क्या किसी को दो यादृच्छिक संख्याओं (arbitrary numbers) के गुणनफल (product) का पता है: c = a * b।
दूसरे शब्दों में, किन्हीं a और b के लिए, हम यह सत्यापित (verify) करना चाहते हैं कि उपयोगकर्ता ने c के लिए सही मान की गणना की है।
सूडोकोड (pseudocode) में, सत्यापन कुछ इस प्रकार होगा (ध्यान दें, यह Circom कोड नहीं है):
def someVerification(a, b, c):
res = a * b
assert res == c, "invalid calculation"
नतीजतन, हमारे R1CS में केवल एक कंस्ट्रेंट (constraint) होगा, जो निम्नलिखित है:
assert c == a * b
R1CS ऐसे कंस्ट्रेंट्स को एक संरचित मैट्रिक्स प्रारूप में व्यक्त करता है। जैसा कि हमने chapter on R1CS में देखा था, उसके अनुसार विटनेस वेक्टर (witness vector) को [1, a, b, c] के रूप में लिखा जाना चाहिए, और संबंधित R1CS को इस प्रकार लिखा जा सकता है:
यदि a = 3, b = 4, और c = 12 है, तो उपरोक्त ऑपरेशन इस प्रकार होगा:
हम उपरोक्त कंस्ट्रेंट को Circom में इस प्रकार लिखेंगे:
template SomeCircuit() {
// inputs
signal input a;
signal input b;
signal input c;
// constraints
c === a * b;
}
component main = SomeCircuit();
- इनपुट्स
a,b,cदिए जाने पर, सर्किट यह सत्यापित करता है किa * bवास्तव मेंcके बराबर है। - सर्किट का काम सत्यापित करना है, गणना करना नहीं। यही कारण है कि
c(गणना का आउटपुट) भी आवश्यक इनपुट्स में से एक है। ===ऑपरेटर उस कंस्ट्रेंट को परिभाषित करता है जिसे पहले R1CS रूप में व्यक्त किया गया था।===एक एशर्सन (assertion) की तरह व्यवहार करता है, इसलिए यदि अमान्य इनपुट प्रदान किए जाते हैं तो सर्किट संतुष्ट (satisfied) नहीं होगा। उपरोक्त कोड में,c === a * b,cकोaऔरbके गुणनफल के बराबर मान रखने के लिए बाध्य करता है।
zkRepl, Circom के लिए एक ऑनलाइन IDE
त्वरित प्रयोगों (quick experiments) के लिए, zkRepl एक शानदार और सुविधाजनक टूल है।
हम इनपुट्स को कमेंट के रूप में प्रदान करके zkRepl में उपरोक्त कोड का आसानी से परीक्षण कर सकते हैं:

नोट: zkrepl का उपयोग करते समय इनपुट को कमेंट में एक JSON ऑब्जेक्ट के रूप में प्रदान किया जाता है। यह परीक्षण करने के लिए कि क्या कोड कंपाइल होता है और इनपुट सर्किट को संतुष्ट करता है, shift-enter का उपयोग करें।
“non-linear constraints” 1 के बराबर है (लाल बॉक्स देखें) क्योंकि अंतर्निहित R1CS में दो सिग्नल्स के बीच गुणा के साथ एक रो कंस्ट्रेंट (row constraint) है। यह अपेक्षित ही है क्योंकि हमारे पास एक ही === है।
template , component , main
- Templates सर्किट के लिए एक ब्लूप्रिंट को परिभाषित करते हैं, ठीक वैसे ही जैसे OOP (Object Oriented Programming) में एक क्लास ऑब्जेक्ट्स के लिए संरचना (structure) को परिभाषित करती है।
- एक component किसी टेम्पलेट का इंस्टेंशिएशन (instantiation) होता है, ठीक उसी तरह जैसे Object Oriented Programming में ऑब्जेक्ट किसी क्लास का इंस्टेंस (instance) होता है।
// create template
template SomeCircuit() {
// .... stuff
}
// instantiate template
component main = SomeCircuit();
component main = SomeCircuit() की आवश्यकता इसलिए है क्योंकि Circom को उस सर्किट संरचना को परिभाषित करने के लिए एक सिंगल टॉप-लेवल कंपोनेंट, main, की आवश्यकता होती है जिसे कंपाइल किया जाएगा।
signal input
- Signal inputs वे मान होते हैं जो कंपोनेंट के बाहर से प्रदान किए जाएंगे। (Circom यह बाध्य नहीं करता है कि वास्तव में कोई मान प्रदान किया गया है या नहीं — यह सुनिश्चित करना डेवलपर पर निर्भर है कि मान वास्तव में प्रदान किए गए हैं। यदि वे नहीं किए जाते हैं, तो यह एक सुरक्षा भेद्यता (security vulnerability) का कारण बन सकता है — इसे आगे के अध्याय में विस्तार से बताया जाएगा।)
- Input signals इम्यूटेबल (immutable) होते हैं और उन्हें बदला नहीं जा सकता।
- Signals बिल्कुल वही वेरिएबल्स होते हैं जो Rank 1 Constraint System के विटनेस वेक्टर (witness vector) में होते हैं।
Circom का Finite Field
Circom एक finite field में अरिथमेटिक (arithmetic) करता है जिसका ऑर्डर 21888242871839275222246405745257275088548364400416034343698204186575808495617 है, जिसे हम सरलता के लिए कहेंगे। यह एक 254-बिट संख्या है, जो bn128 इलिप्टिक कर्व (elliptic curve) के कर्व ऑर्डर से मेल खाती है। यह कर्व व्यापक रूप से उपयोग किया जाता है, विशेष रूप से यह वही है जिसे EVM में प्रीकंपाइल्स (precompiles) के माध्यम से उपलब्ध कराया गया है। चूंकि Circom का उद्देश्य Ethereum पर ZK-SNARK एप्लिकेशन विकसित करने के लिए उपयोग किया जाना था, इसलिए फील्ड साइज़ को bn128 कर्व के कर्व ऑर्डर से मिलाना तर्कसंगत (makes sense) है।
Circom कमांड-लाइन आर्गुमेंट के माध्यम से डिफ़ॉल्ट ऑर्डर को बदलने की अनुमति देता है।
पाठकों के लिए निम्नलिखित बातें स्पष्ट होनी चाहिए:
mod pके तहतp,0के समतुल्य (congruent) है;p-1, finite fieldmod pमें सबसे बड़ा पूर्णांक (integer) है।p-1से बड़े मान पास करने पर ओवरफ्लो (overflow) होगा।
Example 2: BinaryXY
आइए इस खंड को समाप्त करने के लिए एक दूसरा उदाहरण देखें।
एक ऐसे सर्किट पर विचार करें जो यह सत्यापित करता है कि उसे पास किए गए मान बाइनरी (binary) हैं या नहीं, यानी, 0 या 1।
यदि इनपुट वेरिएबल्स x और y हैं, तो कंस्ट्रेंट्स का सिस्टम यह होगा:
(1): x * (x - 1) === 0
(2): y * (y - 1) === 0
याद रखें कि, परिभाषा के अनुसार, R1CS में प्रत्येक कंस्ट्रेंट में वेरिएबल्स के बीच अधिकतम एक गुणा हो सकता है।
x(x-1) === 0 यह जांचता है कि x एक बाइनरी डिजिट है या नहीं*
- इस पॉलीनोमियल एक्सप्रेशन (polynomial expression) के केवल 2 रूट्स (roots) हैं।
- यानी, x = 0 या x = 1।
Circom में व्यक्त
template IsBinary() {
signal input x;
signal input y;
x * (x - 1) === 0;
y * (y - 1) === 0;
}
component main = IsBinary();
वैकल्पिक अभिव्यक्ति: Arrays का उपयोग करना
Circom में, हमारे पास अपने इनपुट्स को अलग-अलग सिग्नल्स के रूप में घोषित करने या एक ऐरे (array) घोषित करने का विकल्प होता है जिसमें सभी इनपुट शामिल होते हैं। Circom में अलग-अलग इनपुट x और y प्रदान करने के बजाय सभी इनपुट्स को in नामक सिग्नल्स के एक array में समूहित करना अधिक पारंपरिक (conventional) है।
इसी परंपरा का पालन करते हुए, हम पिछले सर्किट को इस प्रकार प्रस्तुत करेंगे। जैसा कि आप सामान्यतः उम्मीद करेंगे, Arrays की इंडेक्सिंग शून्य (zero) से शुरू होती है:
template IsBinary() {
// array of 2 input signals
signal input in[2];
in[0] * (in[0] - 1) === 0;
in[1] * (in[1] - 1) === 0;
}
// instantiate template
component main = IsBinary();
केवल वे witnesses स्वीकार किए जाते हैं जो constraints को संतुष्ट करते हैं
Circom केवल उसी इनपुट के लिए प्रूफ़ (proof) उत्पन्न कर सकता है जो वास्तव में सर्किट को संतुष्ट करता है। निम्नलिखित सर्किट (ठीक ऊपर दिए गए कोड से कॉपी किया गया) में, हम [0, 2] को एक इनपुट के रूप में देते हैं जो ऐरे के किसी भी तत्व के लिए केवल {0,1} स्वीकार करता है।
0 के लिए, हमारे पास 0 * (0 - 1) === 0 है, जो ठीक है। हालाँकि, 2 * (2-1) === 2 के लिए, हमारे पास एक कंस्ट्रेंट का उल्लंघन (constraint violation) है जैसा कि नीचे दिए गए चित्र में लाल बॉक्स में दर्शाया गया है।

कमांड लाइन में Circom
यह अनुभाग सामान्य Circom कमांड्स का परिचय देता है। हम मान कर चलते हैं कि पाठक ने पहले ही Circom और आवश्यक डिपेंडेंसीज (dependencies) स्थापित कर ली हैं।
एक नई डायरेक्टरी बनाएँ और उसके अंदर somecircuit.circom नाम की एक फ़ाइल में निम्नलिखित कोड जोड़ें:
pragma circom 2.1.8;
template SomeCircuit() {
// inputs
signal input a;
signal input b;
signal input c;
// constraints
c === a * b;
}
component main = SomeCircuit();
1. सर्किट्स को कंपाइल करना
कंपाइल करने के लिए, टर्मिनल में निम्नलिखित कमांड निष्पादित (execute) करें:
circom somecircuit.circom --r1cs --sym --wasm
--r1csफ़्लैग का मतलब r1cs फ़ाइल आउटपुट करना है,--symफ़्लैग वेरिएबल्स को इंसान द्वारा पढ़े जाने योग्य (human-readable) नाम देता है (अधिक जानकारी sym docs में पाई जा सकती है), और--wasmका उपयोग एक इनपुट JSON दिए जाने पर (बाद के अनुभाग में दिखाया गया है) R1CS के विटनेस (witness) को पॉप्युलेट करने के लिए wasm कोड उत्पन्न करने के लिए किया जाता है।- आवश्यकतानुसार कंपाइल किए जाने वाले सर्किट का नाम
somecircuit.circomबदल लें।
यह अपेक्षित आउटपुट (expected output) है:

- ध्यान दें कि non-linear constraints 1 के रूप में सूचीबद्ध हैं, जो
a * b === cका सूचक है। - Wires, R1CS में कॉलम्स (columns) की संख्या है। इस उदाहरण में, हमारे पास एक निरंतर (constant) कॉलम और तीन सिग्नल्स
a,b,cहैं।
कंपाइलर निम्नलिखित बनाता है:
somecircuit.r1csफ़ाइलsomecircuit.symफ़ाइलsomecircuit_jsडायरेक्टरी
.r1cs फ़ाइल
- इस फ़ाइल में बाइनरी प्रारूप (binary format) में सर्किट का R1CS कंस्ट्रेंट सिस्टम शामिल होता है।
- प्रूफ़िंग/वेरीफाइंग स्टेटमेंट्स (उदा. snarkjs, libsnark) के निर्माण के लिए विभिन्न टूल स्टैक्स के साथ उपयोग किया जा सकता है।
ध्यान दें कि R1CS फ़ाइलें एक तरह से बाइनरी जैसी होती हैं, इस अर्थ में कि cat <file> चलाने पर आपको अर्थहीन अक्षर (gibberish) दिखाई देंगे।
snarkjs r1cs print somecircuit.r1cs चलाने पर, हमें निम्नलिखित इंसान द्वारा पढ़े जाने योग्य आउटपुट प्राप्त होता है:
[INFO] snarkJS: [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.a ] * [ main.b ] - [ 21888242871839275222246405745257275088548364400416034343698204186575808495616main.c ] = 0
Circom में, अरिथमेटिक ऑपरेशंस एक finite field के भीतर किए जाते हैं, इसलिए 21888242871839275222246405745257275088548364400416034343698204186575808495616 वास्तव में -1 का प्रतिनिधि है। हालाँकि, R1CS फ़ाइल में, कंस्ट्रेंट ऑपरेटर == या === के बजाय = है।
हम -1 mod p, (Python में: -1 % p) की जाँच करके इसकी पुष्टि कर सकते हैं, जहाँ p Circom के finite field का ऑर्डर है। यदि हम उन बड़े मानों को जिन्हें snarkjs r1cs print somecircuit.r1cs ने प्रिंट किया है, नेगेटिव नंबर्स में अनुवादित करते हैं, तो हमें मिलता है:
[-1 * main.a] * [main.b] - [-1 * main.c] = 0
अब हम उपरोक्त अभिव्यक्ति (expression) को अधिक परिचित a * b === c में बदलेंगे। बीजगणित (algebra) आगे दिखाया गया है:
[-1 * main.a] * [main.b] - [-1 * main.c] = 0
[-main.a] * [main.b] - [-main.c] = 0 // distribute -1
[main.a] * [main.b] + [-main.c] = 0 // multiply both sides by -1
[main.a] * [main.b] = [main.c] // move -main.c to the other side
फिर से, ध्यान दें कि यह somecircuit.circom में वर्णित कंस्ट्रेंट (a * b === c) से मेल खाता है।
.sym फ़ाइल
somecircuit.sym फ़ाइल एक symbols file है जो कंपाइलेशन के दौरान उत्पन्न होती है। यह फ़ाइल आवश्यक है क्योंकि:
- यह डीबगिंग (debugging) के लिए इंसान द्वारा पढ़े जाने योग्य वेरिएबल नामों को R1CS में उनके संबंधित स्थानों पर मैप करती है।
- यह कंस्ट्रेंट सिस्टम को अधिक समझने योग्य प्रारूप में प्रिंट करने में मदद करती है, जिससे आपके सर्किट को सत्यापित और डीबग करना आसान हो जाता है।
somecircuit_js डायरेक्टरी
somecircuit_js डायरेक्टरी में विटनेस जेनरेशन (witness generation) के लिए आर्टिफैक्ट्स (artifacts) होते हैं:
somecircuit.wasmgenerate_witness.jswitness_calculator.js
generate_witness.js फ़ाइल वह है जिसका उपयोग हम अगले अनुभाग में करेंगे, अन्य दो फ़ाइलें generate_witness.js के लिए हेल्पर्स (helpers) हैं।
सर्किट के लिए इनपुट मान प्रदान करके, ये आर्टिफैक्ट्स आवश्यक मध्यवर्ती मानों (intermediate values) की गणना करेंगे और एक विटनेस बनाएंगे जिसका उपयोग ZK proof उत्पन्न करने के लिए किया जा सकता है।
2. Witness की गणना करना
विटनेस उत्पन्न करने के लिए, हमें सर्किट के लिए सार्वजनिक इनपुट मान (public input values) प्रदान करने होंगे। हम ऐसा somecircuit_js डायरेक्टरी में एक inputs.json फ़ाइल बनाकर करते हैं।
मान लें कि हम इनपुट मान a=1, b=2, c=2 के लिए एक विटनेस बनाना चाहते हैं। JSON फ़ाइल कुछ इस प्रकार होगी:
{"a": "1","b": "2","c": "2"}
Circom संख्याओं के बजाय स्ट्रिंग्स (strings) की अपेक्षा करता है क्योंकि JavaScript से बड़े पूर्णांकों (integers) के साथ सटीकता से काम नहीं करता है (स्रोत)।
इस कमांड को somecircuit_js डायरेक्टरी में चलाएँ:
node generate_witness.js **somecircuit.wasm** inputs.json witness.wtns
आउटपुट एक witness.wtns फ़ाइल के रूप में कंप्यूटर द्वारा निकाला गया विटनेस (computed witness) है।
कंप्यूटेड Witness की जांच करें: witness.wtns
यदि आप cat witness.wtns चलाते हैं, तो आउटपुट अर्थहीन (gibberish) होगा।

ऐसा इसलिए है क्योंकि witness.wtns एक बाइनरी फ़ाइल है जो snarkjs द्वारा स्वीकृत प्रारूप में है।
इंसान द्वारा पढ़े जाने योग्य रूप प्राप्त करने के लिए, हम इसे JSON में एक्सपोर्ट (export) करते हैं: snarkjs wtns export json witness.wtns। फिर हम cat witness.json का उपयोग करके JSON देखते हैं:

- पहला
1विटनेस का स्थिर हिस्सा (constant portion) है, जो हमेशा1होता है। हमारे पासa = 1,b = 2, औरc = 2है क्योंकि हमारा इनपुट JSON{"a": "1","b": "2","c": "2"}था। - snarkjs
witness.jsonआउटपुट करने के लिएwitness.wtnsफ़ाइल को इनजेस्ट (ingest) करता है। - कंप्यूटर द्वारा निकाला गया विटनेस, विटनेस वेक्टर के R1CS लेआउट का पालन करता है:
[1, a, b, c]=[1, 1, 2, 2]
उदाहरण: isbinary.circom
आइए एक थोड़े कम सामान्य उदाहरण को देखते हैं: isbinary.circom। कंस्ट्रेंट्स का रूप पाठक को परिचित लगना चाहिए (उदाहरण 2 को याद करें)।
template IsBinary() {
// array of 2 input signals
signal input in[2];
in[0] * (in[0] - 1) === 0;
in[1] * (in[1] - 1) === 0;
}
// instantiate template
component main = IsBinary();
सर्किट को कंपाइल करना
circom isbinary.circom --r1cs --sym --wasm- टर्मिनल आउटपुट पर सैनिटी चेक (Sanity check):
non-linear constraints: 2

यह तर्कसंगत है, क्योंकि हमारे सर्किट में दो एशर्सन्स (assertions) हैं, जिनमें से प्रत्येक में सिग्नल्स का गुणा शामिल है।
इसके बाद हम R1CS फ़ाइल की जांच करते हैं: कमांड snarkjs r1cs print isbinary.r1cs का परिणाम निम्नलिखित आउटपुट देता है:
[INFO] snarkJS: [ 218882428718392752222464057452572750885483644004160343436982041865758084956161 +main.in[0] ] * [ main.in[0] ] - [ ] = 0
[INFO] snarkJS: [ 218882428718392752222464057452572750885483644004160343436982041865758084956161 +main.in[1] ] * [ main.in[1] ] - [ ] = 0
ध्यान दें कि यह बड़ी संख्या पहले हाइलाइट किए गए -1 mod p कोएफिशिएंट (coefficient) से थोड़ी अलग है (यानी: 21888242871839275222246405745257275088548364400416034343698204186575808495616)
अंत में अतिरिक्त अंक, 1 पर ध्यान दें:
2188824287183927522224640574525727508854836440041603434369820418657580849561621888242871839275222246405745257275088548364400416034343698204186575808495616(1)
अंत में 1 होने का कारण snarkjs द्वारा आउटपुट को फॉर्मेट करने के तरीके में एक खामी है। यह -1 * 1 कहने का “प्रयास” कर रहा है लेकिन उनके बीच कोई स्पेस (space) नहीं है।
अब हम snarkjs के आउटपुट को बीजगणितीय (algebraically) रूप से मूल कंस्ट्रेंट्स में बदलेंगे:
(in[0] - 1) * in[0] === 0
(in[1] - 1) * in[0] === 0
इसका डेरीवेशन (derivation) इस प्रकार है:
// original circom output
[ 218882428718392752222464057452572750885483644004160343436982041865758084956161 +main.in[0] ] * [ main.in[0] ] - [ ] = 0
[ 218882428718392752222464057452572750885483644004160343436982041865758084956161 +main.in[1] ] * [ main.in[1] ] - [ ] = 0
// remove empty terms
[ (21888242871839275222246405745257275088548364400416034343698204186575808495616)1 +main.in[0] ] * [ main.in[0] ] = 0
[ (21888242871839275222246405745257275088548364400416034343698204186575808495616)1 +main.in[1] ] * [ main.in[1] ] = 0
// rewrite p - 1 as -1
[ (-1)1 +main.in[0] ] * [ main.in[0] ] = 0
[ (-1)1 +main.in[1] ] * [ main.in[1] ] = 0
// simplify
[ main.in[0] - 1] * [ main.in[0] ] = 0
[ main.in[1] - 1] * [ main.in[1] ] = 0
Witness उत्पन्न करना
./isbinary_jsडायरेक्टरी में एकinputs.jsonफ़ाइल बनाएँ।- हम
in[0] = 1,in[1] = 0मान पास करने का विकल्प चुनेंगे। - हम
inputs.jsonके लिए निम्नलिखित का उपयोग करेंगे।
{"in": ["1","0"]}
witness.wtnsउत्पन्न करें:node generate_witness.js isbinary.wasm inputs.json witness.wtns(isbinary_jsडायरेक्टरी में)- चूँकि अब
witness.wtnsबन चुका है, इसलिए हम इसकी जांच करने के लिए इसे JSON में एक्सपोर्ट करेंगे:
snarkjs wtns export json witness.wtns cat witness.jsonनिष्पादित करने पर हमें निम्नलिखित आउटपुट प्राप्त होगा:
[
"1", // 1
"1", // in[0]
"0" // in[1]
]
- कंप्यूटर द्वारा निकाला गया सिग्नल विटनेस वेक्टर के R1CS लेआउट,
[1, in[0], in[1]]से मेल खाता है, और उनके संबंधित मान भी मेल खाते हैं।
ZK Proof उत्पन्न करना
एक बार R1CS बन जाने के बाद, पाठक Circom documentation to generate the ZK Proof और एक साथ में आने वाले स्मार्ट कॉन्ट्रैक्ट वेरीफायर (smart contract verifier) को बनाने के चरणों का पालन कर सकते हैं।
अभ्यास प्रश्न
हमारे ZK Puzzles रेपो (repo) से इन पहेलियों (puzzles) को हल करके इस अध्याय से अपनी समझ/सीख का परीक्षण करें। प्रत्येक पहेली के लिए आपको गायब लॉजिक (missing logic) भरने की आवश्यकता है। आप केवल यूनिट टेस्ट (unit tests) चलाकर अपने उत्तरों की जांच कर सकते हैं।