ZK सर्किट्स में “Compute then constrain” एक डिज़ाइन पैटर्न है जहाँ किसी एल्गोरिथम के सही आउटपुट की गणना (compute) पहले बिना constraints के की जाती है। फिर एल्गोरिथम से संबंधित invariants को लागू करके समाधान की शुद्धता (correctness) को verify किया जाता है।
Compute then constrain की आवश्यकता (Motivation)
यदि कोई केवल add, multiply और assign-then-constrain (==>) का उपयोग करने तक सीमित हो, तो कई गणनाओं को मॉडल करना बेहद चुनौतीपूर्ण होगा और इसके लिए बहुत बड़ी संख्या में additions और multiplications की आवश्यकता होगी।
उदाहरण के लिए, किसी संख्या के square root (वर्गमूल) की गणना करने के लिए कई iterative estimates की आवश्यकता होती है। इससे सर्किट का आकार काफी बड़ा हो जाएगा।
इसलिए, अक्सर सर्किट के बाहर computation चलाना अधिक व्यावहारिक होता है — यानी, उत्तर की गणना करते समय कोई constraint उत्पन्न न करें — फिर ऐसे constraints कॉन्फ़िगर करें जो तभी संतुष्ट (satisfied) हों जब computed उत्तर सही हो।
हम Circomlib में Bitify और Comparator लाइब्रेरीज़ से कई उदाहरण दिखाएंगे, जो इस पैटर्न का भारी उपयोग करते हैं।
Circom में “thin arrow” <-- ऑपरेटर और यह <== से कैसे अलग है
<== ऑपरेटर किसी सिग्नल को एक वैल्यू असाइन करता है और एक constraint बनाता है। चूँकि constraints का quadratic (द्विघात) होना आवश्यक है, इसलिए हम निम्नलिखित जैसे ऑपरेशन्स नहीं कर सकते:
template InputEqualsZero() {
signal input in;
signal output out;
// out = 1 if in == 0
out <== in == 0 ? 1 : 0;
}
component main = InputEqualsZero();
उपरोक्त सर्किट को compile करने पर non-quadratic constraints एरर आएगा, क्योंकि ternary ऑपरेटर को सीधे सिग्नल्स के बीच सिंगल multiplication के रूप में व्यक्त नहीं किया जा सकता है। वास्तव में, Circom सिग्नल्स पर किसी भी ऐसे ऑपरेशन को सीधे reject कर देता है जो multiplication या addition नहीं है।
शुरुआत में, ऐसा लग सकता है कि Circom हमारे लिए out को compute नहीं कर सकता है और इसके बजाय इसे public input के रूप में प्रदान करने की आवश्यकता है। हालाँकि, यदि इसमें बहुत सारे सिग्नल्स शामिल हों, तो यह बहुत असुविधाजनक होगा।
हमें Circom को यह बताने के लिए एक मैकेनिज़्म की आवश्यकता है कि “अन्य सिग्नल्स के फ़ंक्शन के रूप में इस सिग्नल के लिए वैल्यू को compute और assign करें, लेकिन constraint न बनाएं।” उस ऑपरेशन का सिंटैक्स <-- ऑपरेटर है:
template InputEqualsZero() {
signal input in;
signal output out;
// out = 1 if in == 0
out <-- in == 0 ? 1 : 0;
}
component main = InputEqualsZero();
in == 0 ? 1 : 0; ऑपरेशन को कभी-कभी “out-of-circuit computations” या “hint” कहा जाता है।
ऊपर दिया गया कोड compile हो जाएगा, लेकिन out और in पर कोई constraints लागू नहीं हैं।
<-- ऑपरेटर बहुत सुविधाजनक है क्योंकि यह हमें constraints उत्पन्न किए बिना वैल्यूज़ को compute करने की अनुमति देता है जो कुछ सिग्नल वैल्यूज़ को मैन्युअल रूप से प्रदान करने की आवश्यकता को समाप्त करता है। हालाँकि, यह सुरक्षा बग्स (security bugs) का एक स्रोत भी रहा है।
Circom यह सुनिश्चित नहीं करता (does not enforce) कि डेवलपर्स <-- का उपयोग करने के बाद उचित constraints बनाएं और यह Circom में critical और high vulnerabilities का एक सामान्य स्रोत रहा है। भले ही डेवलपर कोई constraints न जोड़े, इस प्रकार बहुत खतरनाक कोड बनाता है, कंपाइलर कोई चेतावनी (warning) या नोटिस भी नहीं देगा। Unconstrained सिग्नल्स कोई भी वैल्यू ले सकते हैं, जिससे सर्किट अर्थहीन (nonsensical) स्टेटमेंट्स के लिए ZK proofs उत्पन्न कर सकता है।
हम बाद के ट्यूटोरियल, exploiting underconstrained circuits में सिखाएंगे कि उस Circom कोड को कैसे exploit किया जाए जो <-- ऑपरेटर का दुरुपयोग करता है। अभी के लिए, इसे एक ऐसे ऑपरेशन के रूप में सोचें जो हमें खुद किसी निश्चित सिग्नल वैल्यू को प्रदान करने की परेशानी से बचाता है, जबकि बाद में उस सिग्नल को constrain करने की आवश्यकता होती है।
Computing और constraining को उदाहरणों द्वारा सबसे अच्छी तरह समझा जा सकता है, जो इस अध्याय के शेष भाग में दिए गए हैं।
Example 1: Modular Square Root
किसी संख्या का modular square root एक ऐसी संख्या है जहाँ होता है। हालाँकि, सभी फील्ड एलिमेंट्स (field elements) में modular square root नहीं होता है। वह constraint जो square root के सही computation को मॉडल करता है, वह सीधा (straightforward) है (हालांकि square root को compute करना सीधा नहीं है)।
निम्नलिखित कोड पर विचार करें, जो यह सिद्ध करता है कि out, in का modular square root है:
function sqrt(n) {
// do some magic (see the next code block)
return r;
}
template ValidSqrt() {
signal input in;
signal output out; // sqrt(in)
out <-- sqrt(in);
out * out === in; // ensure sqrt was correct
// the `*` is implicity done modulo p
}
यहाँ, out <-- sqrt(in) बिना constraints जोड़े out को square root असाइन करता है।
Circomlib में pointbits फ़ाइल modular square root को compute करने के लिए फ़ंक्शन प्रदान करती है। ध्यान दें कि फ़ंक्शन्स को Circom टेम्पलेट के बाहर घोषित किया जाना चाहिए। Circom में एक “function” बस संबंधित कोड को एक सीमित ब्लॉक (contained block) में रखने की सुविधा है।
function sqrt(n) {
if (n == 0) {
return 0;
}
// Test that have solution
var res = n ** ((-1) >> 1);
// if (res!=1) assert(false, "SQRT does not exists");
if (res!=1) return 0;
var m = 28;
var c = 19103219067921713944291392827692070036145651957329286315305642004821462161904;
var t = n ** 81540058820840996586704275553141814055101440848469862132140264610111;
var r = n ** ((81540058820840996586704275553141814055101440848469862132140264610111+1)>>1);
var sq;
var i;
var b;
var j;
while ((r != 0)&&(t != 1)) {
sq = t*t;
i = 1;
while (sq!=1) {
i++;
sq = sq*sq;
}
// b = c ^ m-i-1
b = c;
for (j=0; j< m-i-1; j ++) b = b*b;
m = i;
c = b*b;
t = t*c;
r = r*b;
}
if (r < 0 ) {
r = -r;
}
return r;
}
Modular square roots के दो समाधान होते हैं: स्वयं square root और उसका additive inverse। इस प्रकार, हम दोनों समाधानों को निम्नानुसार उत्पन्न कर सकते हैं:
template ValidSqrt() {
signal input in;
signal output out1; // sqrt(in)
signal output out2; // -sqrt(in)
out1 <-- sqrt(in);
out2 <-- out1 * -1; // Computation Step (Unconstrained)
out1 * out1 === in; // Verification Step (Constraint-Based):
out2 * out2 === in; // Verification Step
}
WARNING: यहाँ प्रस्तुत कोड Circom के डिफ़ॉल्ट फील्ड साइज़ के लिए हार्डकोड (hardcoded) किया गया है। यदि आप किसी अन्य फील्ड का उपयोग करने के लिए Circom को कॉन्फ़िगर करते हैं, तो यह गलत उत्तर दे सकता है!
उपरोक्त उदाहरण दर्शाता है कि जब constraints कोई चिंता का विषय न हों तो square root की गणना करना बहुत सरल होता है — यदि हम केवल multiplication और addition का उपयोग करके square root की गणना करने का प्रयास करते, तो सर्किट अनुचित रूप से बड़ा होता। इसके बाद परिणाम की correctness को constraints के माध्यम से लागू (enforced) किया जा सकता है।
यह दर्शाता है कि कैसे Circom एक traditional प्रोग्रामिंग भाषा और एक constraint जनरेशन DSL दोनों हो सकता है। कोड का sqrt(n) फ़ंक्शन वाला भाग traditional प्रोग्रामिंग है, लेकिन constraint in === out * out constraint उत्पन्न करता है।
Example 2: Sudoku
यदि कोई computation इतना कठिन है या constraints के माध्यम से मॉडल करने के लिए computationally महँगा है—यानी, यदि इसके लिए कई गेट्स (gates) और intermediate सिग्नल्स की आवश्यकता होती है—तो कोई भी इसे केवल एक इनपुट के रूप में प्रदान कर सकता है और यह मान सकता है कि prover ने अन्य माध्यमों से उत्तर प्राप्त किया है।
वास्तव में एक सुडोकू पहेली (Sudoku puzzle) को हल करने के लिए, किसी को संभावित समाधानों के लिए एक सर्च एल्गोरिथम चलाना होगा, संभवतः depth-first search का उपयोग करके। हालाँकि, हमें सीधे यह साबित करने की आवश्यकता नहीं है कि हमने सर्च एल्गोरिथम चलाया — एक valid समाधान प्रस्तुत करना यह साबित करने के लिए पर्याप्त है कि हमने सर्च एल्गोरिथम चलाया था। क्योंकि इंटरनेट पर पहले से ही Circom के लिए कई Sudoku tutorials मौजूद हैं, हम यहाँ कोई उदाहरण प्रस्तुत नहीं कर रहे हैं।
Example 3: Modular Inverse
मान लीजिए कि हम सिग्नल in के multiplicative inverse की गणना करना चाहते हैं, यानी, एक ऐसा सिग्नल out खोजना चाहते हैं कि out * in === 1 हो।
Multiplicative inverses की गणना करने का एक तरीका Fermat’s Little Theorem का उपयोग करना है:
हालाँकि, इतने बड़े घातांक (exponent) का उपयोग करने से (Circom का डिफ़ॉल्ट है) बहुत सारे multiplications होंगे और सर्किट बहुत बड़ा हो जाएगा। इसके बजाय, सर्किट के बाहर multiplicative inverse की गणना करना और फिर यह साबित करना बेहतर होगा कि हमारे पास सही multiplicative inverse है। उदाहरण के लिए:
template MulInv() {
signal input in;
signal output out;
// Fermat's little theorem
// compute:
// note that -2 = p - 2 mod p
var inv = in ** (-2);
out <-- inv;
// then constrain
out * in === 1;
}
component main = MulInv();
यहाँ, हमारे पास केवल एक ही constraint है: out * in === 1, इसलिए यह बहुत efficient है।
Circom में Modular division
Circom / ऑपरेटर को modular division के रूप में इंटरप्रेट (interpret) करता है, इसलिए किसी वैल्यू n के inverse की गणना इस प्रकार की जा सकती है:
inv <-- 1 / n;
ऊपर दिए गए टेम्पलेट को थोड़ा और स्पष्ट (cleanly) रूप से इस प्रकार लिखा जा सकता है:
template MulInv() {
signal input in;
signal output out;
// compute
out <-- 1 / in;
// then constrain
out * in === 1;
}
component main = MulInv();
Modular division एक non-quadratic ऑपरेशन है, इसलिए इसका उपयोग केवल वेरिएबल्स के साथ या thin arrow assignment के साथ किया जाना चाहिए — यानी इसे out-of-circuit compute करने की आवश्यकता है।
Example 4: IsZero
आवश्यकता (Motivation)
IsZero सर्किट बड़ी गणनाओं में composing (शामिल करने) के लिए बहुत उपयोगी है। उदाहरण के लिए, मान लीजिए कि हम यह साबित करना चाहते हैं कि x 16 से कम है या x 42 के बराबर है।
Constraints का निम्नलिखित सेट काम नहीं करेगा:
// equal 42
x === 42
// less than 16
x === b_0 + 2*b_1 + 4*b_2 + 8*b_3
0 === b_0 * (b_0 - 1)
0 === b_1 * (b_1 - 1)
0 === b_2 * (b_2 - 1)
0 === b_3 * (b_3 - 1)
यदि x 42 है, तो यह नीचे के constraints का उल्लंघन (violate) करेगा और यदि यह 16 से कम है तो यह x === 42 का उल्लंघन करेगा।
इस प्रकार, हम वास्तव में चाहते हैं कि subcircuits यह इंगित (indicate) करें कि एक निश्चित स्थिति सत्य है (यानी, x का 42 के बराबर होना या 16 से कम होना) बिना यह लागू किए (enforcing) कि वह स्थिति सत्य हो ही। फिर हम इन indicators पर constraints लगा सकते हैं। उदाहरण के लिए, मान लीजिए कि हमारे पास indicators x_eq_42 और x_lt_16 हैं। हम यह constrain कर सकते हैं कि उनमें से कम से कम एक निम्नलिखित के साथ सत्य है:
// at least one of the two signals is not zero
x_eq_42 * x_lt_16 === 1;
यह indicator बनाने के लिए कि x 42 के बराबर है, हम जानना चाहते हैं कि क्या वैल्यू x - 42 सटीक रूप से शून्य है या नहीं।
किसी वैल्यू के शून्य होने को इंगित करने के लिए सर्किट डिज़ाइन करना
यहाँ, हम एक ऐसा सर्किट डिज़ाइन करते हैं जो इनपुट 0 होने पर 1 रिटर्न करता है और अन्यथा 0 रिटर्न करता है (जिज्ञासुओं के लिए, इस फ़ंक्शन का नाम Kronecker Delta function है)।
यदि हम ऐसे फ़ंक्शन को पूरी तरह से addition और multiplication का उपयोग करके लिखते हैं, तो हमारा फ़ंक्शन एक polynomial होगा, जो इस बात में सीमित होता है कि यह कितनी जगहों पर 0 हो सकता है। दूसरे शब्दों में, यदि हम चाहते हैं कि हमारा फ़ंक्शन हमारे finite field में हर जगह शून्य हो, तो हमारे polynomial की डिग्री लगभग finite field के क्रम (order) जितनी बड़ी होगी, जो कि अव्यावहारिक (impractical) है।
इसके बजाय, हम constraints का एक सेट डिज़ाइन करते हैं जहाँ in और out में निम्नलिखित गुण (properties) होते हैं:
| in | out | constraint |
|---|---|---|
| 0 | 0 | violated |
| 0 | 1 | accepted |
| not 0 | 0 | accepted |
| not 0 | 1 | violated |
हमें constraints के एक ऐसे सेट की आवश्यकता है जिसमें in 0 होने पर out को 1 होना चाहिए, और in गैर-शून्य (non-zero) होने पर out को 0 होना चाहिए। इस संबंध के बारे में सोचने का एक और तरीका यह है कि “in या out में से कम से कम एक को non-zero होना चाहिए, लेकिन वे दोनों शून्य या दोनों non-zero नहीं हो सकते।”
यह कहना कि in और out में से कम से कम एक को शून्य होना चाहिए, इसे in * out === 0 constraint के साथ मॉडल किया जा सकता है।
नीचे दी गई तालिका में, हम देख सकते हैं कि in * out === 0 उस स्थिति को स्वीकार करता है जहाँ “ठीक in और out में से एक शून्य है,” और यह उस स्थिति को सही ढंग से reject करता है जहाँ in और out दोनों non-zero हैं:
| in | out | constraint | in * out === 0 |
|---|---|---|---|
| 0 | 0 | violated | accept |
| 0 | 1 | accepted | accept |
| not 0 | 0 | accept | accept |
| not 0 | 1 | violated | violate |
in * out === 0 constraint के साथ समस्या यह है कि यह उस मामले को नहीं रोकता है जहाँ in और out दोनों 0 हैं (जैसा कि ऊपर दी गई तालिका में लाल रंग से चिह्नित है)।
वह गुम (missing) property जिसे हम पकड़ने की कोशिश कर रहे हैं, वह यह है कि in और out एक साथ शून्य नहीं हो सकते।
सरल रूप से (Naively), हम इसे in + out === 1 के साथ प्राप्त कर सकते हैं। इसका मतलब होगा कि यदि in 1 है तो out 0 होना चाहिए और इसके विपरीत (vice versa)। हालाँकि, specifications कहते हैं कि in कोई भी non-zero वैल्यू हो सकता है, उदाहरण के लिए, 100, और 100 + out 1 नहीं हो सकता।
हालाँकि, यदि हम “100 को 1 में बदल सकते हैं” तो हम constraint को काम करने लायक बना सकते हैं। इसे सर्किट के बाहर in के multiplicative inverse की गणना करके और बाद में in * inv + out === 1 constraint लागू करके पूरा किया जा सकता है। यदि in शून्य है, तो हम inv को शून्य बनाते हैं क्योंकि शून्य का कोई multiplicative inverse नहीं होता है। अब हमारे पास निम्नलिखित constraints हैं:
in * inv + out === 1;
in * out === 0;
ध्यान दें कि inv स्वयं constrained नहीं है, लेकिन इस मामले में इसका कोई प्रभाव (consequence) नहीं पड़ता।
पहला constraint, in * inv + out === 1; केवल इस उद्देश्य को पूरा करता है कि in और out दोनों को शून्य होने की अनुमति न दी जाए। यदि in और out दोनों शून्य हैं, तो inv की वैल्यू की परवाह किए बिना constraint का उल्लंघन (violated) होगा।
सर्किट के बाहर किए गए computations को संक्षेप में (summarize) बताने के लिए:
- क्या
inशून्य है या नहीं। inका multiplicative inverse।
Circomlib में IsZero कम्पोनेंट इस भाग में बताए गए constraints को पूरा करता है:
template IsZero() {
signal input in;
signal output out;
signal inv;
inv <-- in!=0 ? 1/in : 0;
out <== -in*inv +1;
in*out === 0;
}
यह पहले सर्किट के बाहर inv को compute करता है, फिर यह out को 1 होने के लिए constrain करता है यदि in शून्य है, और अन्यथा out को 0 करता है।
Non-deterministic इनपुट्स
सर्किट के बाहर compute की गई वैल्यूज़ जो हमें अधिक संक्षिप्त (concise) constraints का उपयोग करने में सक्षम बनाती हैं, उन्हें “advice inputs” या “non-deterministic inputs” कहा जाता है। ऊपर दिए गए सर्किट में inv सिग्नल advice इनपुट, या non-deterministic इनपुट का एक उदाहरण है।
Example 5: IsEqual
Circomlib में IsEqual कम्पोनेंट IsZero से निकटता से संबंधित है — यह जांचता है कि क्या इनपुट्स के बीच का अंतर शून्य है (यदि हाँ, तो उन्हें एक दूसरे के बराबर होना चाहिए):
template IsEqual() {
signal input in[2];
signal output out;
component isz = IsZero();
in[1] - in[0] ==> isz.in;
isz.out ==> out;
}
Example 6: Num2Bits
Circomlib में Num2Bits टेम्पलेट एक सिग्नल को n बिट्स में decompose करता है जैसा कि टेम्पलेट पैरामीटर द्वारा निर्दिष्ट (specified) किया गया है:
template Num2Bits(n) {
signal input in; // number
signal output out[n]; // binary output
var lc1=0;
var e2=1;
for (var i = 0; i<n; i++) {
out[i] <-- (in >> i) & 1;
out[i] * (out[i] -1 ) === 0;
lc1 += out[i] * e2;
e2 = e2+e2;
}
lc1 === in;
}
कृपया ध्यान दें कि ऊपर दिए गए कोड में n के लिए, यदि finite field से बड़ा है, तो हमें एक alias bug मिल सकता है। इसे उस अध्याय में आगे समझाया गया है।
मूल रूप से, कोड least significant bit से शुरू होकर बाइनरी representation में प्रत्येक बिट के माध्यम से लूप करता है। लूप के प्रत्येक इटरेशन (iteration) पर, हम वैल्यू [1,2,4,8,…,2^i] को एक वेरिएबल e2 में स्टोर करते हैं, जो वह वैल्यू है जिसे वह बिट represent करता है। यदि वह बिट 1 है (out[i] <-- (in >> i) & 1;), तो हम उस वैल्यू को accumulator lc1 में जोड़ते हैं। लूप में प्रत्येक इटरेशन पर, हम constrain करते हैं कि पढ़ी गई बिट वास्तव में 0 या 1 है (out[i] * (out[i] -1 ) === 0; के साथ)। अंत में, हम constrain करते हैं कि computed बाइनरी वैल्यू मूल वैल्यू (lc1 === in;) से मेल खाती है।
यह बाइनरी ऐरे को कैसे compute करता है, इसे एक एनीमेशन (animation) के साथ सबसे अच्छी तरह से दिखाया गया है, जिसे हम यहाँ दिखाते हैं:
पहले के उदाहरणों के समान, बाइनरी वैल्यू को compute करना सर्किट के बाहर किया जाता है, लेकिन फिर हम बाद में constrain करते हैं ताकि यह सुनिश्चित हो सके कि बाइनरी ऐरे सही है।
Num2Bits टेम्पलेट LessThan टेम्पलेट और सिग्नल्स की relative वैल्यू की तुलना करने के लिए अन्य टेम्पलेट्स में एक कोर (core) कम्पोनेंट है।
फील्ड एलिमेंट्स (एक finite field में नंबर) की सीधे एक दूसरे से तुलना नहीं की जा सकती है — उन्हें पहले बाइनरी नंबर्स में बदलने की आवश्यकता होती है।
यह समझने के लिए कि सर्किट में बाइनरी नंबर्स के आकार की कुशलतापूर्वक तुलना कैसे करें, कृपया Arithmetic Circuits पर हमारे अध्याय में प्रासंगिक (relevant) अनुभाग की समीक्षा करें, फिर वहां की चर्चा की तुलना Circomlib में LessThan टेम्पलेट से करें।
Example 7: IsMax
यह साबित करने के लिए कि कोई आइटम किसी सूची (list) में अधिकतम (maximum) है, हमें यह दिखाना होगा कि यह 1) प्रत्येक एलिमेंट से बड़ा या उसके बराबर है और 2) यह सूची में भी मौजूद है। दूसरी आवश्यकता को समझने के लिए, विचार करें कि 100 सूची [4,5,6] का अधिकतम नहीं है, भले ही 100 सूची के प्रत्येक आइटम से बड़ा या उसके बराबर है।
नीचे दिया गया सर्किट एक पारंपरिक for लूप का उपयोग करके सर्किट के बाहर अधिकतम को compute करता है, फिर GreaterEqThan कम्पोनेंट का उपयोग करके यह सुनिश्चित करता है कि out सूची में हर दूसरे आइटम से बड़ा या उसके बराबर है।
यह सुनिश्चित करने के लिए कि out सूची में कम से कम एक आइटम के बराबर है, यह हर दूसरे सिग्नल के साथ एक IsEqual तुलना (comparison) का योग (sum) करता है। यदि योग शून्य है, तो हम जानते हैं कि out सूची में नहीं है। इसलिए, हम उस योग को शून्य न होने के लिए constrain करते हैं:
template IsMax() {
signal input in[3];
signal output out;
// compute the max as usual
var maxx = in[0];
for (var i = 1; i < 3; i++) {
if (in[i] > maxx) {
maxx = in[i];
}
}
// propose the max, but do not constrained it yet
out <-- maxx;
// max must be ≥ every other element
signal gte0;
signal gte1;
signal gte2;
// gte0 <== GreaterEqThan(252)([out, in[0]]);
// is shorthand for
// component gte0 = GreaterEqThan(252);
// gte0[0] <== out;
// gte0[1] <== in[0];
// 252 is to ensure we don't have enough
// bits to encode numbers larger than what
// fits in the default finite field, which
// would lead to aliasing issues
gte0 <== GreaterEqThan(252)([out, in[0]]);
gte1 <== GreaterEqThan(252)([out, in[1]]);
gte2 <== GreaterEqThan(252)([out, in[2]]);
gte0 === 1;
gte1 === 1;
gte2 === 1;
// max must be equal to at least one element
signal eq0;
signal eq1;
signal eq2;
eq0 <== IsEqual()([out, in[0]]);
eq1 <== IsEqual()([out, in[1]]);
eq2 <== IsEqual()([out, in[2]]);
signal iz;
iz <== IsZero()(eq0 + eq1 + eq2);
// if IsZero is 1, we have a violation
iz === 0;
}
अपने वर्तमान स्वरूप में, हमारा सर्किट केवल 3 की लंबाई वाले ऐरे का समर्थन (support) करने के लिए हार्डकोड (hardcoded) किया गया है। हालाँकि, एक arbitrary (मनमानी) लंबाई वाले इनपुट के लिए एक टेम्पलेट होना अच्छा होगा। यह आगामी अध्याय का विषय है।
Practice Problems
एक Circom फ़ंक्शन लिखें जो quadratic फॉर्मूला का उपयोग करके डिग्री 2 polynomial का रूट (root) ढूंढता है। याद रखें, सब कुछ एक finite field पर किया जाता है, इसलिए आपको पहले उदाहरण से modular square root का उपयोग करने की आवश्यकता है।
फिर, ऐसे constraints लिखें कि दोनों रूट्स (यदि वे मौजूद हैं) polynomial को संतुष्ट करते हैं। Circom टेम्पलेट में polynomial को तीन coefficients के ऐरे के रूप में पास करें।