यह लेख समझाता है कि arithmetic constraints के एक सेट को Rank One Constraint System (R1CS) में कैसे बदला जाए।
इस संसाधन का ध्यान कार्यान्वयन (implementation) पर है: हम इस रूपांतरण (transformation) को करने के अन्य सामग्रियों की तुलना में बहुत अधिक कॉर्नर केसेस (corner cases) को कवर करते हैं, ऑप्टिमाइज़ेशन (optimizations) पर चर्चा करते हैं, और समझाते हैं कि Circom लाइब्रेरी इसे कैसे पूरा करती है।
पूर्व आवश्यकताएँ (Prerequisites)
- हम मान कर चलते हैं कि पाठक यह समझता है कि गणना (computation) की वैधता को दर्शाने के लिए arithmetic circuits (zk circuits) का उपयोग कैसे किया जाता है।
- पाठक modular arithmetic से परिचित है। यहाँ सभी ऑपरेशन एक finite field में होते हैं, इसलिए का वास्तव में अर्थ है के मॉड्यूलो का additive inverse और का अर्थ है के मॉड्यूलो का multiplicative inverse गुणा ।
Rank 1 Constraint System का अवलोकन (Overview)
Rank 1 Constraint System (R1CS) एक arithmetic circuit है जिसमें यह आवश्यकता होती है कि प्रत्येक equality constraint में एक गुणा (multiplication) हो (और जोड़/additions की संख्या पर कोई प्रतिबंध नहीं होता है)।
यह arithmetic circuit के प्रतिनिधित्व (representation) को bilinear pairings के उपयोग के अनुकूल बनाता है। पेयरिंग के आउटपुट को फिर से पेयर नहीं किया जा सकता है, क्योंकि के किसी एलिमेंट का उपयोग किसी अन्य पेयरिंग के इनपुट के हिस्से के रूप में नहीं किया जा सकता है। इसलिए, हम प्रति constraint केवल एक गुणा की अनुमति देते हैं।
Witness वेक्टर
एक arithmetic circuit में, witness उन सभी सिग्नल्स के लिए एक असाइनमेंट है जो समीकरण (equation) के constraints को संतुष्ट करता है।
Rank 1 Constraint System में, witness वेक्टर एक वेक्टर होता है जिसमें सभी इनपुट वेरिएबल्स, आउटपुट वेरिएबल और मध्यवर्ती (intermediate) मानों का मान होता है। यह दर्शाता है कि आपने इनपुट, आउटपुट और सभी मध्यवर्ती मानों को जानते हुए, शुरू से अंत तक सर्किट को निष्पादित (execute) किया है।
परंपरा के अनुसार, कुछ गणनाओं को आसान बनाने के लिए पहला एलिमेंट हमेशा 1 होता है, जिसे हम बाद में प्रदर्शित करेंगे।
उदाहरण के लिए, यदि हमारे पास constraint है
जिसके समाधान को जानने का हम दावा करते हैं, तो इसका मतलब यह होना चाहिए कि हम , , और को जानते हैं। क्योंकि Rank One Constraint Systems में प्रति constraint ठीक एक गुणा की आवश्यकता होती है, उपरोक्त constraints वाले बहुपद (polynomial) को इस प्रकार लिखा जाना चाहिए:
Witness का मतलब है कि हम न केवल , , और को जानते हैं, बल्कि हमें इस विस्तारित रूप (expanded form) में हर मध्यवर्ती (intermediate) वेरिएबल को भी जानना चाहिए। विशेष रूप से, हमारा witness एक वेक्टर है:
जहाँ प्रत्येक पद (term) का एक मान होता है जो ऊपर दिए गए constraints को संतुष्ट करता है।
उदाहरण के लिए,
एक वैध (valid) witness है क्योंकि जब हम मानों को रखते हैं,
यह constraints को संतुष्ट करता है
इस उदाहरण में अतिरिक्त 1 पद (term) का उपयोग नहीं किया गया है और यह एक सुविधा है जिसे हम बाद में समझाएंगे।
उदाहरण 1: को Rank 1 Constraint System में बदलना
हमारे उदाहरण के लिए, हम कहेंगे कि हम साबित कर रहे हैं।
इसलिए, हमारा witness वेक्टर के असाइनमेंट के रूप में है।
इससे पहले कि हम एक R1CS बना सकें, हमारे constraints का रूप (form) यह होना चाहिए:
result = left_hand_side × right_hand_side
हमारे लिए सौभाग्य से, यह पहले से ही है
यह स्पष्ट रूप से एक सामान्य (trivial) उदाहरण है, लेकिन सामान्य उदाहरण आमतौर पर शुरुआत करने के लिए एक बेहतरीन जगह होते हैं।
एक वैध (valid) R1CS बनाने के लिए, आपको ऐसे सूत्रों की एक सूची की आवश्यकता होती है जिनमें ठीक एक गुणा (multiplication) हो।
हम बाद में चर्चा करेंगे कि उन मामलों को कैसे संभाला जाए जिनमें ठीक एक गुणा नहीं होता है जैसे या ।
हमारा लक्ष्य इस रूप के समीकरणों की एक प्रणाली (system) बनाना है:
जहाँ , , और , x ( पंक्तियाँ/rows और स्तंभ/columns) आकार के मैट्रिक्स (matrices) हैं।
मैट्रिक्स गुणा के बाईं ओर के वेरिएबल को एन्कोड करता है और गुणा के दाईं ओर के वेरिएबल्स को एन्कोड करता है। परिणाम (result) वेरिएबल्स को एन्कोड करता है। वेक्टर witness वेक्टर है।
विशेष रूप से, , , और ऐसे मैट्रिक्स हैं जिनमें witness वेक्टर के समान ही स्तंभों (columns) की संख्या है, और प्रत्येक स्तंभ उसी वेरिएबल का प्रतिनिधित्व करता है जिसका उपयोग इंडेक्स (index) कर रहा है।
तो हमारे उदाहरण के लिए, witness में 4 एलिमेंट्स हैं इसलिए हमारे प्रत्येक मैट्रिक्स में 4 स्तंभ होंगे, इसलिए ।
पंक्तियों (rows) की संख्या सर्किट में constraints की संख्या के अनुरूप होगी। हमारे मामले में, हमारे पास केवल एक constraint है: , इसलिए हमारे पास केवल एक पंक्ति होगी, इसलिए ।
आइए सीधे उत्तर पर चलते हैं और समझाते हैं कि हमने इसे कैसे प्राप्त किया।
इस उदाहरण में, मैट्रिक्स का प्रत्येक आइटम एक इंडिकेटर (indicator) वेरिएबल के रूप में कार्य करता है कि जिस वेरिएबल से स्तंभ संबंधित है वह मौजूद है या नहीं। (तकनीकी रूप से, यह वेरिएबल का गुणांक/coefficient है, लेकिन हम उस पर बाद में आएंगे)।
बाएं हाथ के पदों (left hand terms) के लिए, गुणा के बाईं ओर मौजूद एकमात्र वेरिएबल है, इसलिए यदि स्तंभ का प्रतिनिधित्व करते हैं, तो…
है, क्योंकि मौजूद है, और अन्य कोई भी वेरिएबल मौजूद नहीं है।
है क्योंकि गुणा के दाईं ओर एकमात्र वेरिएबल है, और
है क्योंकि गुणा के “आउटपुट” में हमारे पास केवल वेरिएबल है।
हमारे पास कहीं भी कोई स्थिरांक (constants) नहीं हैं इसलिए 1 का स्तंभ हर जगह शून्य है (हम बाद में चर्चा करेंगे कि यह गैर-शून्य (non-zero) कब होता है)।
यह समीकरण सही है, जिसे हम Python में सत्यापित (verify) कर सकते हैं
import numpy as np
# define the matrices
O = np.matrix([[0,1,0,0]])
L = np.matrix([[0,0,1,0]])
R = np.matrix([[0,0,0,1]])
# witness vector
a = np.array([1, 4223, 41, 103])
# Multiplication `*` is element-wise, not matrix multiplication.
# Result contains a bool indicating an element-wise indicator that the equality is true for that element.
result = np.matmul(O, a) == np.matmul(L, a) * np.matmul(R, a)
# check that every element-wise equality is true
assert result.all(), "result contains an inequality"
आप सोच रहे होंगे कि इसका क्या मतलब है, क्या हम बस यह नहीं कह रहे हैं कि बहुत कम संक्षिप्त (compact) तरीके से?
आप सही होंगे।
एक R1CS काफी विस्तृत (verbose) हो सकता है, लेकिन वे Quadratic Arithmetic Programs (QAPs) में अच्छी तरह से मैप होते हैं, जिन्हें संक्षिप्त (succinct) बनाया जा सकता है। लेकिन हम यहाँ QAPs पर विचार नहीं करेंगे।
लेकिन यह R1CS का एक महत्वपूर्ण बिंदु है। एक R1CS बिल्कुल वही जानकारी संप्रेषित (communicate) करता है जो मूल arithmetic constraints करते हैं, लेकिन प्रति equality constraint केवल एक गुणा के साथ। इस उदाहरण में, हमारे पास केवल एक constraint है, लेकिन हम अगले उदाहरण में और जोड़ेंगे।
उदाहरण 2: r = x * y * z * u को बदलना
इस थोड़े अधिक जटिल उदाहरण में, अब हमें मध्यवर्ती (intermediate) वेरिएबल्स से निपटना होगा। हमारी गणना की प्रत्येक पंक्ति में केवल एक गुणा हो सकता है, इसलिए हमें अपने समीकरण को इस प्रकार तोड़ना होगा:
ऐसा कोई नियम नहीं है जो कहता हो कि हमें इसे इस तरह तोड़ना था, निम्नलिखित भी वैध है:
हम इस उदाहरण के लिए पहले रूपांतरण (transformation) का उपयोग करेंगे।
, , और का आकार
चूँकि हम 7 वेरिएबल्स के साथ काम कर रहे हैं, हमारे witness वेक्टर में आठ एलिमेंट्स होंगे (पहला स्थिरांक 1 होगा) और हमारे मैट्रिक्स में आठ स्तंभ (columns) होंगे।
क्योंकि हमारे पास तीन constraints हैं, मैट्रिक्स में तीन पंक्तियाँ (rows) होंगी।
बाएं हाथ के पद (Left hand terms) और दाएं हाथ के पद (right hand terms)
यह उदाहरण “left hand term” और “right hand term” के विचार को दृढ़ता से लागू करेगा। विशेष रूप से, , , और left hand terms हैं, और , , और right hand terms हैं।
Left hand terms से मैट्रिक्स का निर्माण
आइए मैट्रिक्स का निर्माण करें। हम जानते हैं कि इसमें तीन पंक्तियाँ होंगी (चूँकि तीन constraints हैं) और आठ स्तंभ होंगे (चूँकि आठ वेरिएबल्स हैं)।
हमारे witness वेक्टर को इसके द्वारा गुणा किया जाएगा, इसलिए आइए अपने witness वेक्टर को निम्नलिखित लेआउट के साथ परिभाषित करें:
यह हमें सूचित करता है कि के स्तंभ क्या दर्शाते हैं:
की पहली पंक्ति
पहली पंक्ति में, पहले बाएं वेरिएबल के लिए, हमारे पास है:
इसका मतलब है कि बाईं ओर के संबंध में, वेरिएबल मौजूद है, और अन्य कोई भी वेरिएबल्स मौजूद नहीं हैं। इसलिए, हम पहली पंक्ति को इस प्रकार बदलते हैं:
याद करें कि के स्तंभों को इस प्रकार लेबल किया गया है:
और हम देखते हैं कि , स्तंभ में है।
की दूसरी पंक्ति
नीचे की ओर काम करते हुए, हम देखते हैं कि हमारे समीकरणों की प्रणाली (systems of equations) के बाईं ओर के लिए केवल मौजूद है।
इसलिए, हम उस पंक्ति में सब कुछ शून्य पर सेट करते हैं, सिवाय उस स्तंभ के जो का प्रतिनिधित्व करता है।
की तीसरी पंक्ति
अंत में, तीसरी पंक्ति में बाएं हाथ के ऑपरेटरों (left hand operators) में हमारे पास केवल मौजूद वेरिएबल के रूप में है
यह मैट्रिक्स को पूरा करता है
निम्नलिखित छवि (image/diagram) मैपिंग को अधिक स्पष्ट करेगी:
का वैकल्पिक रूपांतरण (Alternative transformation)
हम निम्नलिखित के बाएं हाथ के मानों (left hand values) का विस्तार (expand) करके भी यही अभ्यास पूरा कर सकते हैं
इस प्रकार:
हम ऐसा कर सकते हैं क्योंकि शून्य पदों (zero terms) को जोड़ने से मान नहीं बदलते हैं। हमें बस शून्य वेरिएबल्स का विस्तार करने के लिए सावधान रहना होगा ताकि उनमें वही “स्तंभ” हों जैसे हमने witness वेक्टर को परिभाषित किया है।
और फिर, यदि हम उपरोक्त विस्तार से गुणांकों (coefficients - बॉक्स में दिखाए गए) को बाहर निकालते हैं,
हमें के लिए वही मैट्रिक्स मिलता है जो हमने कुछ क्षण पहले उत्पन्न किया था।
Right hand terms से मैट्रिक्स का निर्माण
मैट्रिक्स हमारे समीकरण के दाएं हाथ के पदों (right hand terms) का प्रतिनिधित्व करता है:
मैट्रिक्स में , , और का प्रतिनिधित्व करने वाले 1s होने चाहिए। मैट्रिक्स की पंक्ति arithmetic constraint की पंक्ति से मेल खाती है, यानी हम constraints (पंक्तियों) को इस प्रकार क्रमांकित (number) कर सकते हैं:
तो पहली पंक्ति के स्तंभ में 1 है, दूसरी पंक्ति के स्तंभ में 1 है, और तीसरी पंक्ति के स्तंभ में 1 है। बाकी सब कुछ शून्य है।
इसका परिणाम मैट्रिक्स के लिए निम्नलिखित है:
यह आरेख (diagram) रूपांतरण (transformation) को स्पष्ट करता है।
मैट्रिक्स का निर्माण
पाठक के लिए यह निर्धारित करना एक अभ्यास है कि मैट्रिक्स है
पहले के मैट्रिक्स के अनुरूप स्तंभ लेबलों (column labels) का उपयोग करते हुए।
याद दिलाने के लिए, गुणा के परिणाम से प्राप्त होता है
और स्तंभ लेबल इस प्रकार हैं
के लिए हमारे काम की जाँच करना
import numpy as np
# enter the A B and C from above
L = np.matrix([[0,0,1,0,0,0,0,0],
[0,0,0,0,1,0,0,0],
[0,0,0,0,0,0,1,0]])
R = np.matrix([[0,0,0,1,0,0,0,0],
[0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1]])
O = np.matrix([[0,0,0,0,0,0,1,0],
[0,0,0,0,0,0,0,1],
[0,1,0,0,0,0,0,0]])
# random values for x, y, z, and u
import random
x = random.randint(1,1000)
y = random.randint(1,1000)
z = random.randint(1,1000)
u = random.randint(1,1000)
# compute the algebraic circuit
r = x * y * z * u
v1 = x*y
v2 = z*u
# create the witness vector
a = np.array([1, r, x, y, z, u, v1, v2])
# element-wise multiplication, not matrix multiplication
result = np.matmul(O, a) == np.multiply(np.matmul(L, a), np.matmul(R, a))
assert result.all(), "system contains an inequality"
उदाहरण 3: एक स्थिरांक (constant) के साथ जोड़ (Addition)
क्या होगा यदि हम निम्नलिखित के लिए एक rank one constraint system बनाना चाहते हैं?
यहीं पर वह 1 का स्तंभ काम आता है।
जोड़ (Addition) मुफ़्त है
आपने शायद ZK-SNARKs के संदर्भ में “addition is free” (जोड़ मुफ़्त है) कथन सुना होगा। इसका मतलब यह है कि जब हमारे पास एक जोड़ का ऑपरेशन होता है तो हमें एक अतिरिक्त constraint बनाने की आवश्यकता नहीं होती है।
हम उपरोक्त सूत्र (formula) को इस प्रकार लिख सकते हैं
लेकिन वह हमारे R1CS को आवश्यकता से अधिक बड़ा बना देगा।
इसके बजाय, हम इसे इस प्रकार लिख सकते हैं
फिर वेरिएबल और स्थिरांक स्वचालित रूप से “संयुक्त” (combined) हो जाते हैं जब हम अपने witness वेक्टर के साथ को से गुणा करते हैं।
हमारे witness वेक्टर का रूप [1, z, x, y] है, इसलिए हमारे मैट्रिक्स , , और इस प्रकार हैं:
जब भी additive constants होते हैं, हम बस उन्हें स्तंभ में रख देते हैं, जो परंपरा के अनुसार पहला स्तंभ होता है।
फिर से, आइए अपने गणित (math) पर कुछ यूनिट टेस्ट करें:
import numpy as np
import random
# Define the matrices
L = np.matrix([[0,0,1,0]])
R = np.matrix([[0,0,0,1]])
O = np.matrix([[-2,1,0,0]])
# pick random values to test the equation
x = random.randint(1,1000)
y = random.randint(1,1000)
z = x * y + 2 # witness vector
a = np.array([1, z, x, y])
# check the equality
result = O.dot(a) == np.multiply(np.matmul(L, a), R.dot(a))
assert result.all(), "result contains an inequality"
उदाहरण 4: एक स्थिरांक (constant) के साथ गुणा
ऊपर दिए गए सभी उदाहरणों में, हमने कभी भी वेरिएबल्स को स्थिरांक (constants) से गुणा नहीं किया। इसलिए R1CS में प्रविष्टियां (entries) हमेशा 1 थीं। जैसा कि आपने उपरोक्त उदाहरण से अनुमान लगाया होगा, मैट्रिक्स में प्रविष्टि उसी स्थिरांक का मान होती है जिससे वेरिएबल को गुणा किया जाता है, जैसा कि निम्नलिखित उदाहरण दिखाएगा।
आइए इसके लिए समाधान निकालते हैं
ध्यान दें कि जब हम “प्रति constraint एक गुणा” कहते हैं तो हमारा मतलब दो वेरिएबल्स के गुणा से होता है। एक स्थिरांक के साथ गुणा “वास्तविक” गुणा नहीं है क्योंकि यह वास्तव में एक ही वेरिएबल का बार-बार किया जाने वाला जोड़ है।
निम्नलिखित समाधान वैध (valid) है, लेकिन अनावश्यक पंक्तियाँ बनाता है:
अधिक अनुकूलतम (optimal) समाधान इस प्रकार है:
अधिक अनुकूलतम समाधान का उपयोग करते हुए, हमारे witness वेक्टर का रूप [1, out, x, y] होगा।
मैट्रिक्स को इस प्रकार परिभाषित किया जाएगा:
R1CS रूप में उपरोक्त को प्रतीकात्मक रूप से (symbolically) [1, z, x, y] से गुणा करने पर हमें हमारा मूल समीकरण वापस मिल जाता है:
इसलिए हम जानते हैं कि हमने , , और को सही ढंग से सेट किया है।
यहाँ हमारे पास एक पंक्ति (constraints) और एक “सच्चा” गुणा है। एक सामान्य नियम के रूप में:
Rank One Constraint System में constraints की संख्या गैर-स्थिर गुणाओं (non-constant multiplications) की संख्या के बराबर होनी चाहिए।
उदाहरण 5: एक बड़ा constraint
आइए कुछ कम सामान्य (less trivial) करें जो ऊपर सीखी गई हर चीज़ को शामिल करता हो
मान लीजिए हमारे पास निम्नलिखित constraint है:
हम इसे इस प्रकार तोड़ेंगे:
ध्यान दें कि कैसे सभी जोड़ वाले पदों को बाईं ओर ले जाया गया है (यही हमने जोड़ वाले उदाहरण में किया था, लेकिन यह यहाँ अधिक स्पष्ट है)।
तीसरी पंक्ति में दाईं ओर छोड़ना मनमाना (arbitrary) है। हम दोनों पक्षों को 5 से विभाजित कर सकते हैं और अंतिम constraint यह हो सकता है
हालाँकि यह witness को नहीं बदलता है, इसलिए दोनों वैध हैं। चूँकि सब कुछ एक finite field में किया जाता है, यह ऑपरेशन left-hand-side और right-hand-side को 5 के multiplicative inverse से गुणा कर रहा है।
हमारा witness वेक्टर इस रूप का होगा
और हमारे मैट्रिक्स में तीन पंक्तियाँ होंगी, चूँकि हमारे पास तीन constraints हैं:
हमने आउटपुट को लाल रंग में, बाईं ओर को हरे रंग में, और दाईं ओर को बैंगनी रंग में चिह्नित किया है। यह निम्नलिखित मैट्रिक्स उत्पन्न करता है:
स्तंभ लेबलों (column labels) के साथ
आइए हमेशा की तरह अपने काम की जाँच करें।
import numpy as np
import random
# Define the matrices
L = np.array([[0,0,3,0,0,0],
[0,0,0,0,1,0],
[0,0,5,0,0,0]])
R = np.array([[0,0,1,0,0,0],
[0,0,0,1,0,0],
[0,0,0,1,0,0]])
O = np.array([[0,0,0,0,1,0],
[0,0,0,0,0,1],
[-3,1,1,2,0,-1]])
# pick random values for x and y
x = random.randint(1,1000)
y = random.randint(1,1000)
# this is our orignal formula
out = 3 * x * x * y + 5 * x * y - x - 2 * y + 3 # the witness vector with the intermediate variables inside
v1 = 3*x*x
v2 = v1 * y
w = np.array([1, out, x, y, v1, v2])
result = O.dot(w) == np.multiply(L.dot(w),R.dot(w))
assert result.all(), "result contains an inequality"
Rank 1 Constraint Systems को एक ही बहुपद (single polynomial) से शुरू करने की आवश्यकता नहीं होती है
चीजों को सरल रखने के लिए, हम के रूप के उदाहरणों का उपयोग कर रहे हैं लेकिन अधिकांश वास्तविक arithmetic constraints, arithmetic constraints का एक सेट (set) होने वाले हैं, न कि केवल एक।
उदाहरण के लिए, मान लीजिए कि हम साबित कर रहे हैं कि एक ऐरे बाइनरी (binary) है और , 16 से कम है। constraints का सेट होगा:
इसे rank one constraint system में प्राप्त करने के लिए, हम ध्यान देते हैं कि अंतिम पंक्ति में कोई गुणा नहीं है, इसलिए हम को पहले constraint में प्रतिस्थापित (substitute) कर सकते हैं:
यह मानते हुए कि हमारा witness वेक्टर है, हम इस प्रकार R1CS बना सकते हैं:
प्रतिस्थापन (substitution) करना पूरी तरह से आवश्यक नहीं है, लेकिन यह R1CS में एक पंक्ति बचाता है। बाद के एक भाग में, हम एक वैध R1CS दिखाएंगे जहाँ हम प्रतिस्थापन नहीं करते हैं।
R1CS में सब कुछ modulo prime किया जाता है
उपरोक्त उदाहरणों में, हमने सादगी के लिए पारंपरिक अंकगणित (traditional arithmetic) का उपयोग किया, लेकिन वास्तविक दुनिया के कार्यान्वयन (real world implementations) इसके बजाय modular arithmetic का उपयोग करते हैं।
कारण सरल है: 2/3 जैसी संख्याओं को एन्कोड करने से ill-behaved फ्लोट्स (floats) बनते हैं जो कम्प्यूटेशनल रूप से गहन (computationally intensive) और त्रुटि-प्रवण (error prone) होते हैं।
यदि हम अपना सारा गणित एक अभाज्य संख्या (prime number), मान लीजिए 23 के मॉड्यूलो (modulo) में करते हैं, तो को एन्कोड करना सीधा है। यह के समान है, और दो से गुणा करना और ऋणात्मक 1 की घात (power of negative 1) तक बढ़ाना modular arithmetic में सीधे हैं।
Circom का कार्यान्वयन (implementation)
Circom में, जो Rank 1 Constraint Systems बनाने के लिए एक भाषा है, finite field अभाज्य संख्या (prime number) का उपयोग करता है (यह BN128 कर्व (curve) के क्रम (order) के बराबर है जिस पर हमने Elliptic Curves over Finite Fields में चर्चा की थी)।
इसका मतलब है कि उस प्रतिनिधित्व (representation) में है
p = 21888242871839275222246405745257275088548364400416034343698204186575808495617
# 1 - 2 = -1
(1 - 2) % p
# 21888242871839275222246405745257275088548364400416034343698204186575808495616
out = x * y के लिए Circom
यदि हम Circom में out = x * y लिखते हैं, तो यह निम्नलिखित की तरह दिखेगा:
pragma circom 2.0.0;
template Multiply2() {
signal input x;
signal input y;
signal output out;
out <== x * y;
}
component main = Multiply2();
आइए इसे एक R1CS फ़ाइल में बदलें और R1CS फ़ाइल को प्रिंट करें
circom multiply2.circom --r1cs --sym
snarkjs r1cs print multiply2.r1cs
हमें निम्नलिखित आउटपुट मिलता है:

यह हमारे R1CS समाधान से काफी अलग दिखता है, लेकिन यह वास्तव में उसी जानकारी को एन्कोड कर रहा है।
Circom के कार्यान्वयन (implementation) में ये अंतर हैं:
- शून्य (zero) मान वाले स्तंभ प्रिंट नहीं किए जाते हैं
- Circom को के रूप में लिखता है
21888242871839275222246405745257275088548364400416034343698204186575808495616 के बारे में क्या जो वास्तव में -1 है?
Circom का समाधान है
भले ही ऋणात्मक (negative) वाले अप्रत्याशित (unexpected) हो सकते हैं, witness वेक्टर [1 out x y] के साथ, यह वास्तव में के रूप के अनुरूप है। (हम एक सेकंड में देखेंगे कि Circom ने वास्तव में इसी स्तंभ असाइनमेंट का उपयोग किया था)।
आप , , और out के लिए मानों को रख सकते हैं और देख सकते हैं कि समीकरण सही साबित होता है।
आइए Circom के वेरिएबल से स्तंभ (column) असाइनमेंट को देखें। आइए wasm सॉल्वर (solver) के साथ अपने सर्किट को फिर से संकलित (recompile) करें:
circom multiply2.circom --r1cs --wasm --sym
cd multiply2_js/
हम input.json फ़ाइल बनाते हैं
echo '{"x": "11", "y": "9"}' > input.json
और witness की गणना करते हैं
node generate_witness.js multiply2.wasm input.json witness.wtns
snarkjs wtns export json witness.wtns witness.json
cat witness.json
हमें निम्नलिखित परिणाम मिलता है:

यह स्पष्ट है कि Circom उसी स्तंभ लेआउट (column layout) का उपयोग कर रहा है जिसका हम उपयोग कर रहे हैं: [1, out, x, y], चूँकि हमारे input.json में को और को पर सेट किया गया था।
यदि हम Circom के उत्पन्न (generated) witness का उपयोग करते हैं (पढ़ने में आसानी के लिए बड़ी संख्या को -1 से बदलते हुए), तो हम देखते हैं कि Circom का R1CS सही है
में के लिए का एक गुणांक (coefficient) है, में के लिए का एक गुणांक है, और में के लिए है। मॉड्यूलर (modular) रूप में, यह बिल्कुल वैसा ही है जैसा टर्मिनल ने ऊपर आउटपुट किया था:

हमारे बाकी काम की जाँच करना
समीक्षा (review) के तौर पर, हमने जिन सूत्रों का अन्वेषण किया था, वे थे:
हमने अभी ऊपर के भाग में (1) किया था, इस भाग के लिए हम इस सिद्धांत (principle) को स्पष्ट करेंगे कि गैर-स्थिर गुणाओं (non-constant multiplications) की संख्या constraints की संख्या होती है।
(2) के लिए सर्किट है:
pragma circom 2.0.8;
template Multiply4() {
signal input x;
signal input y;
signal input z;
signal input u;
signal v1;
signal v2;
signal out;
v1 <== x * y;
v2 <== z * u;
out <== v1 * v2;
}
component main = Multiply4();
हमने अब तक जो कुछ भी चर्चा की है, उसके साथ Circom का आउटपुट और एनोटेशन (annotations) स्व-व्याख्यात्मक (self-explanatory) होने चाहिए।

इसे ध्यान में रखते हुए, हमारे अन्य सूत्रों में इस प्रकार के constraints होने चाहिए:
Circom सर्किट लिखना और उपरोक्त को सत्यापित (verify) करना पाठक के लिए एक अभ्यास है।
R1CS की गणना करने के लिए आपको witness की आवश्यकता नहीं होती है
ध्यान दें कि Circom कोड में हमने R1CS की गणना करने से पहले कभी भी witness की आपूर्ति (supply) नहीं की। हमने उदाहरण को कम अमूर्त (abstract) बनाने और अपने काम की जाँच को आसान बनाने के लिए पहले witness की आपूर्ति की थी, लेकिन यह आवश्यक नहीं है। यह महत्वपूर्ण है, क्योंकि यदि किसी वेरीफायर (verifier) को R1CS बनाने के लिए witness की आवश्यकता होती, तो प्रूवर (prover) को अपना छिपा हुआ समाधान (hidden solution) देना पड़ता!
जब हम “witness” कहते हैं तो हमारा मतलब भरे हुए मानों (populated values) वाले एक वेक्टर से होता है। वेरीफायर (verifier) witness की “संरचना” (structure), यानी वेरिएबल से स्तंभ (column) असाइनमेंट को जानता है, लेकिन मानों को नहीं जानता।
एक R1CS वैध (valid) होता है भले ही वह ऑप्टिमाइज़्ड (optimized) न हो
एक बहुपद (polynomial) से R1CS में एक वैध रूपांतरण (transformation) अद्वितीय (unique) नहीं होता है। आप उसी समस्या को अधिक constraints के साथ एन्कोड कर सकते हैं, जो कम कुशल (efficient) है। यहाँ एक उदाहरण है।
कुछ R1CS ट्यूटोरियल्स में, इस तरह के सूत्र (formula) के लिए constraints
को इसमें बदल दिया जाता है:
जैसा कि हमने ध्यान दिया है, यह कुशल (efficient) नहीं है। हालाँकि, आप इस लेख में दी गई कार्यप्रणाली (methodology) का उपयोग करके इसके लिए एक वैध R1CS बना सकते हैं। हम बस इस तरह एक डमी गुणा (dummy multiplication) जोड़ते हैं:
हमारा witness वेक्टर के रूप का है और , , और इस प्रकार परिभाषित हैं:
की दूसरी पंक्ति जोड़ को पूरा करती है, और एक (one) से गुणा की दूसरी पंक्ति के पहले एलिमेंट का उपयोग करके पूरा किया जाता है।
यह पूरी तरह से वैध है, लेकिन समाधान में इसकी आवश्यकता से एक अधिक पंक्ति और एक अधिक स्तंभ है।
क्या होगा यदि कोई गुणा (multiplications) न हो?
क्या होगा यदि हम निम्नलिखित सर्किट को एन्कोड करना चाहते हैं?
अभ्यास (practice) में यह काफी बेकार है, लेकिन पूर्णता के लिए, इसे एक (one) के डमी गुणा के साथ हल किया जा सकता है।
के हमारे सामान्य witness वेक्टर लेआउट के साथ, हमारे पास निम्नलिखित मैट्रिक्स हैं:
Rank One Constraint Systems सुविधा के लिए हैं
Groth16 के मूल पेपर में Rank One Constraint System शब्द का कोई संदर्भ नहीं है। कार्यान्वयन (implementation) के दृष्टिकोण से एक R1CS उपयोगी है, लेकिन शुद्ध गणित के दृष्टिकोण से, यह स्पष्ट रूप से विभिन्न वेरिएबल्स के गुणांकों (coefficients) को लेबल करने और समूहीकृत करने के बारे में है। इसलिए जब आप इस विषय पर अकादमिक पेपर (academic papers) पढ़ते हैं, तो यह आमतौर पर गायब होता है क्योंकि यह एक अधिक अमूर्त अवधारणा (abstract concept) का एक कार्यान्वयन विवरण (implementation detail) है।
उपयोगी संसाधन (Handy Resources)
-
यह वेब टूल R1CS की गणना करता है constraints के एक सेट के लिए (लेकिन यह केवल एक इनपुट और आउटपुट वेरिएबल के साथ काम करता है)।
RareSkills के साथ और जानें
यह ब्लॉग पोस्ट हमारे zero knowledge कोर्स की शिक्षण सामग्री (learning materials) से ली गई है।