कभी-कभी इन्हें bilinear mappings भी कहा जाता है, bilinear pairings हमें तीन नंबर , , और लेने की अनुमति देते हैं, जहाँ होता है, उन्हें एन्क्रिप्ट करके , , बना दिया जाता है, जहाँ एक एन्क्रिप्शन फ़ंक्शन है, फिर इन दो एन्क्रिप्टेड वैल्यूज़ को एक वेरीफ़ायर (verifier) के पास भेजा जाता है जो यह वेरिफ़ाई कर सकता है कि है लेकिन वह ओरिजिनल (मूल) वैल्यूज़ को नहीं जानता है। हम पहले दो ओरिजिनल नंबरों को जाने बिना यह साबित करने के लिए bilinear pairings का उपयोग कर सकते हैं कि तीसरा नंबर पहले दो का गुणनफल (product) है।
हम एक हाई लेवल पर bilinear pairings को समझाएंगे और Python में कुछ उदाहरण प्रदान करेंगे।
पूर्व-आवश्यकताएं (Prerequisites)
- रीडर को पता होना चाहिए कि elliptic curves के संदर्भ में point addition और scalar multiplication क्या होते हैं।
- रीडर को इस संदर्भ में discrete logarithm problem के बारे में भी पता होना चाहिए: एक पॉइंट से मल्टीप्लाई किए गए स्केलर का परिणाम एक और पॉइंट होगा, और आमतौर पर elliptic curve पॉइंट दिए जाने पर स्केलर की गणना करना अव्यवहार्य (infeasible) है।
- रीडर को पता होना चाहिए कि finite field और cyclic group क्या होते हैं, और elliptic curves के संदर्भ में एक generator क्या होता है। हम generators को G वेरिएबल से संदर्भित करेंगे।
- रीडर को Ethereum Precompiles के बारे में जानकारी होनी चाहिए।
हम EC (elliptic curve) पॉइंट्स को दर्शाने के लिए कैपिटल लेटर्स (बड़े अक्षरों) का और finite fields के तत्वों (“scalars”) को दर्शाने के लिए लोअर केस लेटर्स (छोटे अक्षरों) का उपयोग करेंगे। जब हम तत्व (element) कहते हैं, तो यह एक finite field में एक इंटीजर हो सकता है या यह एक elliptic curve पर एक पॉइंट हो सकता है। संदर्भ इसे स्पष्ट कर देगा।
उपरोक्त सभी को पूरी तरह समझे बिना भी इस ट्यूटोरियल को पढ़ना संभव है, लेकिन इस विषय के बारे में अच्छी समझ विकसित करना कठिन होगा।
Bilinear pairings कैसे काम करते हैं
जब एक स्केलर को elliptic curve पर एक पॉइंट से मल्टीप्लाई किया जाता है, तो एक अन्य elliptic curve पॉइंट उत्पन्न होता है। यानी जहाँ एक स्केलर है, और जेनरेटर (generator) है। और दिए जाने पर हम को निर्धारित नहीं कर सकते।
मान लें है। हम जो करने की कोशिश कर रहे हैं वह है:
और एक वेरीफ़ायर को समझाना है कि और के discrete logs का गुणा (multiply) करने पर का discrete log प्राप्त होता है।
यदि , और , , तथा है, तो हम एक ऐसा फ़ंक्शन चाहते हैं जिससे
और जब हो, तो वह के बराबर न हो। यह ग्रुप में , , और के सभी संभव संयोजनों (combinations) के लिए सत्य होना चाहिए।
हालाँकि, bilinear pairings का उपयोग करते समय हम आमतौर पर को इस तरह व्यक्त नहीं करते हैं। जिन कारणों पर हम बाद में चर्चा करेंगे, यह आमतौर पर इस प्रकार कैलकुलेट किया जाता है:
जेनरेटर पॉइंट है, और इसे के रूप में सोचा जा सकता है। इस संदर्भ में। उदाहरण के लिए, का मतलब है कि हमने को बार किया। का मतलब सिर्फ इतना है कि हमने लिया और कुछ नहीं जोड़ा। इसलिए एक मायने में, यह कहने के समान है।
इसलिए हमारा bilinear pairing एक ऐसा फ़ंक्शन है कि यदि आप इसमें दो elliptic curve पॉइंट्स डालते हैं तो आपको एक ऐसा आउटपुट मिलता है जो उन दो पॉइंट्स के discrete logs के गुणनफल (product) के अनुरूप (corresponds) होता है।
नोटेशन (Notation)
एक bilinear pairing को आमतौर पर के रूप में लिखा जाता है। यहाँ, का natural logarithm से कोई लेना-देना नहीं है, और तथा elliptic curve पॉइंट्स हैं।
सामान्यीकरण (Generalization), यह जाँचना कि क्या दो गुणनफल समान हैं
मान लें कि किसी ने हमें चार elliptic curve पॉइंट्स , , , और दिए और दावा किया कि और के discrete logs का गुणनफल और के समान है, अर्थात् । एक bilinear pairing का उपयोग करके, हम , , , या को जाने बिना यह जाँच सकते हैं कि क्या यह सत्य है। हम बस यह करते हैं:
“Bilinear” का क्या अर्थ है
Bilinear का अर्थ है कि यदि कोई फ़ंक्शन दो आर्गुमेंट्स लेता है, और उनमें से एक को स्थिर (constant) रखा जाता है, और दूसरा बदलता (varies) है, तो आउटपुट गैर-स्थिर (non-constant) आर्गुमेंट के साथ रेखीय (linearly) रूप से बदलता है।
यदि bilinear है, और स्थिर है, तो , के साथ linearly रूप से बदलता है और , के साथ linearly रूप से बदलता है।
हम इससे यह निष्कर्ष निकाल सकते हैं कि एक elliptic curve bilinear pairing में निम्नलिखित गुण (property) होता है:
क्या रिटर्न कर रहा है?
सच कहें तो, आउटपुट गणितीय रूप से इतना डरावना है कि इसे वास्तव में समझाने की कोशिश करना उल्टा (counterproductive) होगा। यही कारण है कि पुस्तक में पहले Groups को समझाने में बहुत समय बिताया गया था, क्योंकि Groups क्या है यह समझना क्या रिटर्न कर रहा है इसे समझने की तुलना में काफी आसान है।
एक bilinear pairing का आउटपुट एक ग्रुप एलिमेंट (group element) होता है, विशेष रूप से एक finite cyclic group का एलिमेंट।
को एक ब्लैक बॉक्स के रूप में मानना सबसे अच्छा है, ठीक वैसे ही जैसे अधिकांश प्रोग्रामर हैश फ़ंक्शन्स (hash functions) को ब्लैक बॉक्स की तरह मानते हैं।
हालाँकि, ब्लैक बॉक्स होने के बावजूद, हम अभी भी आउटपुट की प्रॉपर्टीज़ के बारे में बहुत कुछ जानते हैं, जिसे हम कहते हैं:
- एक cyclic group है, इसलिए इसमें एक क्लोज्ड बाइनरी ऑपरेटर (closed binary operator) होता है।
- का बाइनरी ऑपरेटर एसोसिएटिव (associative) होता है।
- में एक आइडेंटिटी एलिमेंट (identity element) होता है।
- के प्रत्येक एलिमेंट का एक इन्वर्स (inverse) होता है।
- क्योंकि ग्रुप cyclic है, इसलिए इसका एक जेनरेटर (generator) होता है।
- चूँकि ग्रुप cyclic और finite है, इसलिए finite cyclic groups के होमोमोर्फिक (homomorphic) होते हैं। अर्थात, हमारे पास finite field के तत्वों को के तत्वों के साथ होमोमोर्फिक रूप से मैप करने का कोई तरीका होता है।
क्योंकि ग्रुप cyclic है, हमारे पास , , , और इसी तरह की धारणा है। का बाइनरी ऑपरेटर मोटे तौर पर वही है जिसे हम “multiplication” कहेंगे, इसलिए ।
यदि आप वास्तव में जानना चाहते हैं कि “कैसा दिखता है”, तो यह एक 12-Dimensional (12-आयामी) ऑब्जेक्ट है। हालाँकि आइडेंटिटी एलिमेंट दिखने में इतना डरावना नहीं है:
सिमेट्रिक और असिमेट्रिक ग्रुप्स (Symmetric and Asymmetric Groups)
उपरोक्त नोटेशन का तात्पर्य है कि हम हर जगह एक ही जेनरेटर और elliptic curve ग्रुप का उपयोग कर रहे हैं जब हम कहते हैं
हालाँकि व्यवहार में, यह पता चलता है कि bilinear pairings बनाना तब आसान होता है जब दोनों आर्गुमेंट्स के लिए एक अलग ग्रुप (लेकिन समान ऑर्डर) का उपयोग किया जाता है।
विशेष रूप से, हम कहते हैं
उपयोग किए गए ग्रुप्स में से कोई भी समान नहीं है।
हालाँकि, जिस प्रॉपर्टी की हमें परवाह है, वह अभी भी लागू होती है।
उपरोक्त समीकरण में, ग्रुप को स्पष्ट रूप से नहीं दिखाया गया है लेकिन यह का कोडोमेन (codomain - आउटपुट स्पेस) है।
कोई यह सोच सकता है कि और अलग-अलग मापदंडों (parameters) वाले अलग-अलग elliptic curve समीकरण हैं (लेकिन पॉइंट्स की संख्या समान है) और यह मान्य होगा क्योंकि वे अलग-अलग ग्रुप्स हैं।
एक सिमेट्रिक पेयरिंग (symmetric pairing) में, bilinear pairing फ़ंक्शन के दोनों आर्गुमेंट्स के लिए समान elliptic curve ग्रुप का उपयोग किया जाता है। इसका मतलब है कि दोनों आर्गुमेंट्स में उपयोग किया जाने वाला जेनरेटर और elliptic curve ग्रुप समान है। इस मामले में, पेयरिंग फ़ंक्शन को अक्सर इस प्रकार दर्शाया जाता है:
एक असिमेट्रिक पेयरिंग (asymmetric pairing) में, आर्गुमेंट्स अलग-अलग ग्रुप्स का उपयोग करते हैं। उदाहरण के लिए, पहला आर्गुमेंट दूसरे आर्गुमेंट की तुलना में एक अलग जेनरेटर और elliptic curve ग्रुप का उपयोग कर सकता है। पेयरिंग फ़ंक्शन अभी भी वांछित गुणों को संतुष्ट कर सकता है
व्यवहार में हम असिमेट्रिक ग्रुप्स (asymmetric groups) का उपयोग करते हैं, और हमारे द्वारा उपयोग किए जाने वाले ग्रुप्स के बीच का अंतर अगले अनुभाग में समझाया गया है।
वही ग्रुप है जिसके बारे में हमने पिछले अध्यायों में बात की थी, और Ethereum के संदर्भ में, यह वही G1 है जिसे हम लाइब्रेरी से आयात (import) करते हैं:
from py_ecc.bn128 import G1
हम उसी लाइब्रेरी से G2 को इस प्रकार आयात (import) कर सकते हैं:
from py_ecc.bn128 import G1, G2
लेकिन क्या है?
Field Extensions और Python में G2 पॉइंट
Bilinear pairings काफी हद तक इस बात से एग्नोस्टिक (agnostic) होते हैं कि आप किस प्रकार के ग्रुप्स का विकल्प चुनते हैं, लेकिन Ethereum का फ़ील्ड एक्सटेंशन्स (field extensions) के साथ elliptic curves का उपयोग करता है। यदि आप ZK-SNARKS का उपयोग करने वाले Solidity कोड को पढ़ने में सक्षम होना चाहते हैं, तो आपको कम से कम इस बात का मोटा-मोटा अंदाज़ा होना चाहिए कि ये क्या हैं।
हम आमतौर पर EC पॉइंट्स को दो पॉइंट्स और के रूप में सोचते हैं। फ़ील्ड एक्सटेंशन्स के साथ, और स्वयं दो-आयामी (two-dimensional) ऑब्जेक्ट्स जोड़े बन जाते हैं। यह इस बात के अनुरूप है कि कैसे कॉम्प्लेक्स नंबर्स (complex numbers) वास्तविक संख्याओं (real numbers) का “विस्तार” करते हैं और उन्हें 2 आयामों (एक वास्तविक घटक और एक काल्पनिक घटक) वाली किसी चीज़ में बदल देते हैं।
फ़ील्ड एक्सटेंशन एक बहुत ही अमूर्त अवधारणा (abstract concept) है, और स्पष्ट रूप से, एक फ़ील्ड और उसके एक्सटेंशन के बीच का संबंध विशुद्ध रूप से कार्यात्मक (functional) दृष्टि से मायने नहीं रखता है।
बस इसे इस तरह से सोचें:

में एक elliptic curve वह elliptic curve है जहाँ और दोनों एलिमेंट दो आयामी ऑब्जेक्ट (two dimensional objects) होते हैं।
Python में G2 पॉइंट
थ्योरी बहुत हो गई, चलिए इसे कोड करते हैं और पॉइंट देखते हैं। py_ecc लाइब्रेरी को इस प्रकार इंस्टॉल करें।
python -m pip install py_ecc
अब हम इसमें से उन फ़ंक्शन्स को इम्पोर्ट (import) करते हैं जिनकी हमें आवश्यकता है
from py_ecc.bn128 import G1, G2, pairing, add, multiply, eq
print(G1)
# (1, 2)
print(G2)
#((10857046999023057135944570762232829481370756359578518086990519993285655852781, 11559732032986387107991004021392285783925812861821192530917403151452391805634), (8495653923123431417604973247489272438418190587263600148770280649306958101930, 4082367875863433681332203403145435568316851327593401208105741076214120093531))
यदि आप ध्यान से देखें, तो आप G2 को देखेंगे, आप देखेंगे कि G2 टुपल्स (tuples) की एक जोड़ी है। पहला टुपल दो-आयामी (two-dimensional) पॉइंट है और दूसरा टुपल दो-आयामी पॉइंट है।
G1 और G2 उनके संबंधित ग्रुप्स के लिए जेनरेटर पॉइंट्स (generator points) हैं।
G1 और G2 दोनों का समान ऑर्डर (curve पर पॉइंट्स की संख्या) होता है:
from py_ecc.bn128 import G1, G2, eq, curve_order, multiply, eq, curve_order
x = 10 # chosen randomly
assert eq(multiply(G2, x + curve_order), multiply(G2, x))
assert eq(multiply(G1, x + curve_order), multiply(G1, x))
भले ही G2 पॉइंट्स थोड़े अजीब लगें, लेकिन उनका व्यवहार अन्य cyclic groups के समान ही होता है, विशेषकर G1 ग्रुप जिससे हम परिचित हैं। इसका मतलब है कि हम उम्मीद के मुताबिक स्केलर मल्टीप्लिकेशन (जो वास्तव में बार-बार किया गया एडिशन है) के साथ अन्य पॉइंट्स का निर्माण कर सकते हैं।
print(eq(add(G1, G1), multiply(G1, 2)))
# True
print(eq(add(G2, G2), multiply(G2, 2)))
# True
यह स्पष्ट होना चाहिए कि आप केवल एक ही ग्रुप के एलिमेंट्स को जोड़ (add) सकते हैं।
add(G1, G2) # TypeError
वैसे, यह लाइब्रेरी कुछ अरिथमेटिक ऑपरेटर्स (arithmetic operators) को ओवरराइड (override) करती है (आप Python में ऐसा कर सकते हैं), जिसका अर्थ है कि आप निम्नलिखित कर सकते हैं:
print(G1 + G1 + G1 == G1*3)
# True
# The above is the same as this:
eq(add(add(G1, G1), G1), multiply(G1, 3))
# True
Python में Bilinear Pairings
इस लेख की शुरुआत में, हमने कहा था कि bilinear pairings का उपयोग यह जाँचने के लिए किया जा सकता है कि क्या और के discrete logs का गुणा करके का discrete log प्राप्त होता है, यानी ।
हम इसे Python में कैसे कर सकते हैं:
from py_ecc.bn128 import G1, G2, pairing, multiply, eq
P = multiply(G1, 3)
Q = multiply(G2, 8)
R = multiply(G1, 24)
assert eq(pairing(Q, P), pairing(G2, R))
थोड़ा परेशान करने वाली बात यह है कि लाइब्रेरी की आवश्यकता है कि आप pairing में पहले आर्गुमेंट के रूप में G2 पॉइंट पास करें।
गुणनफलों (products) की समानता
साथ ही इस लेख की शुरुआत में हमने कहा था कि एक पेयरिंग जाँच कर सकता है:
हम इसे Python में कैसे कर सकते हैं:
from py_ecc.bn128 import G1, G2, pairing, multiply, eq
P_1 = multiply(G1, 3)
P_2 = multiply(G2, 8)
Q_1 = multiply(G1, 6)
Q_2 = multiply(G2, 4)
assert eq(pairing(P_2, P_1), pairing(Q_2, Q_1))
का बाइनरी ऑपरेटर (binary operator)
में एलिमेंट्स को “multiplication” का उपयोग करके कंबाइन (combine) किया जाता है लेकिन ध्यान रखें कि यह वास्तव में Python में एक सिंटैक्टिक ओवरराइड (syntactic override) है:
from py_ecc.bn128 import G1, G2, pairing, multiply, eq
# 2 * 3 = 6
P_1 = multiply(G1, 2)
P_2 = multiply(G2, 3)
# 4 * 5 = 20
Q_1 = multiply(G1, 4)
Q_2 = multiply(G2, 5)
# 10 * 12 = 120 (6 * 20 = 120 also)
R_1 = multiply(G1, 10)
R_2 = multiply(G2, 12)
assert eq(pairing(P_2, P_1) * pairing(Q_2, Q_1), pairing(R_2, R_1))
# Fails!
लेकिन assertion विफल (fails) हो जाता है!
के एलिमेंट किसी बेस (base) की “घात” (powers) की तरह व्यवहार करते हैं।
बीजगणित (algebra) से याद करें कि
मान लीजिए कि हम में एक एलिमेंट को के रूप में जनरेट करते हैं। हम इस एलिमेंट को के रूप में सोच सकते हैं, लेकिन इसे के रूप में सोचना कहीं अधिक मददगार होगा। इस संदर्भ में यह जानने की आवश्यकता नहीं है कि क्या है, बस यह कि यह मौजूद है।
इसलिए, हमारे ऊपर दिए गए कोड को काम करने लायक बनाने के लिए, और को बदलें ताकि उनका गुणनफल (multiply) 26 हो जाए।
हमारा कोड प्रभावी रूप से गणना (computing) कर रहा है:
from py_ecc.bn128 import G1, G2, pairing, multiply, eq
# 2 * 3 = 6
P_1 = multiply(G1, 2)
P_2 = multiply(G2, 3)
# 4 * 5 = 20
Q_1 = multiply(G1, 4)
Q_2 = multiply(G2, 5)
# 13 * 2 = 26
R_1 = multiply(G1, 13)
R_2 = multiply(G2, 2)
# b ^ {2 * 3} * b ^ {4 * 5} = b ^ {13 * 2}
# b ^ 6 * b ^ 20 = b ^ 26
assert eq(pairing(P_2, P_1) * pairing(Q_2, Q_1), pairing(R_2, R_1))
Ethereum में Bilinear Pairings
EIP 197 स्पेसिफिकेशन (Specification)
py_ecc लाइब्रेरी वास्तव में Ethereum Foundation द्वारा मेन्टेन (maintain) की जाती है, और यही PyEVM implementation में एड्रेस 0x8 पर प्रीकंपाइल (precompile) को पावर देती है।
EIP-197 में परिभाषित Ethereum प्रीकंपाइल G1 और G2 के पॉइंट्स पर काम करता है, और अंतर्निहित (implicitly) रूप से के पॉइंट्स पर काम करता है।
इस प्रीकंपाइल का स्पेसिफिकेशन (specification) पहली बार में थोड़ा अजीब लगेगा। यह G1 और G2 पॉइंट्स की एक सूची लेता है जो इस प्रकार लेआउट (laid out) किए गए हैं:
A₁B₁A₂B₂...AₙBₙ : Aᵢ ∈ G1, Bᵢ ∈ G2
इन्हें मूल रूप से इस प्रकार बनाया गया था
A₁ = a₁G1
B₁ = b₁G2
A₂ = a₂G1
B₂ = b₂G2
...
Aₙ = aₙG1
Bₙ = bₙG2
यदि निम्नलिखित सत्य है तो प्रीकंपाइल 1 रिटर्न करता है
a₁b₁ + a₂b₂ + ... + aₙbₙ = 0
और अन्यथा शून्य (zero)।
यह पहली बार में थोड़ा हैरान करने वाला (head-scratcher) हो सकता है! इसका अर्थ यह लगता है कि प्रीकंपाइल प्रत्येक पॉइंट का discrete log ले रहा है, जिसे आमतौर पर अव्यवहार्य (infeasible) माना जाता है। इसके अलावा, यह पहले के Python उदाहरणों से पेयरिंग की तरह व्यवहार क्यों नहीं करता है? पहले के उदाहरणों ने में एक एलिमेंट रिटर्न किया था, लेकिन यह प्रीकंपाइल एक बूलियन (boolean) रिटर्न करता है।
EIP 197 डिज़ाइन निर्णय के लिए औचित्य (Justification)
पहली समस्या यह है कि में एलिमेंट्स बड़े होते हैं, विशेष रूप से, वे 12-आयामी (12-dimensional) ऑब्जेक्ट होते हैं।
यह मेमोरी में बहुत अधिक जगह लेगा जिससे गैस की लागत (gas costs) अधिक होगी। इसके अलावा, अधिकांश ZK वेरिफिकेशन एल्गोरिदम कैसे काम करते हैं (यह इस लेख के दायरे से बाहर है), हम आमतौर पर एक पेयरिंग के आउटपुट की वैल्यू की जाँच नहीं करते हैं, बल्कि केवल यह जाँचते हैं कि यह अन्य पेयरिंग्स के बराबर है या नहीं। विशेष रूप से, Groth16 (tornado cash द्वारा उपयोग किया जाने वाला ज़ीरो नॉलेज एल्गोरिदम) में अंतिम चरण (final step) निम्नलिखित जैसा दिखता है
जहाँ प्रत्येक वेरिएबल अपने सबस्क्रिप्ट नोटेशन (subscript notation) के आधार पर या तो या का एक elliptic curve पॉइंट है (हम अपने नोटेशन के साथ एकरूपता (consistent) बनाए रखने के लिए अपर-केस (upper-case) ग्रीक लेटर्स का उपयोग कर सकते थे, लेकिन वे लैटिन वर्णमाला (Latin alphabet) के बहुत समान दिखते हैं)।
इस स्तर पर इन वेरिएबल्स का अर्थ महत्वपूर्ण नहीं है। यह तथ्य कि इसे “गुणनफलों (products)” (elliptic curve pairing) के योग के रूप में लिखा जा सकता है, यही मायने रखता है। विशेष रूप से, हम इसे इस प्रकार लिख सकते हैं
और अब यह प्रीकंपाइल स्पेसिफिकेशन (precompile specification) से पूरी तरह मेल खाता है!
यह सिर्फ Groth16 नहीं है, अधिकांश zk एल्गोरिदम में वेरिफिकेशन फॉर्मूला (verification formula) ऐसा ही होता है, यही कारण है कि प्रीकंपाइल को एक सिंगल पेयरिंग की वैल्यू रिटर्न करने के बजाय पेयरिंग्स के योग (sums of pairings) के साथ काम करने के लिए डिज़ाइन किया गया था।
यदि हम Tornado Cash के वेरिफिकेशन कोड को देखें, तो हम देख सकते हैं कि यह बिल्कुल इसी को लागू (implementing) कर रहा है (यहाँ तक कि ग्रीक अक्षर भी मेल खाते हैं, लेकिन अगर आप इसे अभी तक नहीं समझे हैं तो चिंता न करें)। का सीधा सा मतलब है कि यह एक पॉइंट है, का मतलब पॉइंट है, आदि।

पेयरिंग फ़ंक्शन के अंदर वह स्थान है जहाँ address(8) को कॉल किया जाता है ताकि पेयरिंग कैलकुलेशन को पूरा किया जा सके और यह निर्धारित किया जा सके कि प्रूफ़ (proof) वैलिड (valid) है या नहीं।
कभी-कभी, EIP 197 के संदर्भ में ग्रुप को के रूप में संदर्भित किया जाता है।
Discrete logarithms का योग (Sum)
यहाँ मुख्य विचार (key insight) यह है कि यदि
तो यह भी सत्य होना चाहिए कि
ग्रुप में।
प्रीकंपाइल वास्तव में discrete logarithm की गणना नहीं कर रहा है, यह केवल यह जाँच रहा है कि क्या पेयरिंग्स का योग शून्य है।
पेयरिंग्स का योग शून्य होता है यदि और केवल यदि discrete logarithms के गुणनफलों (products) का योग शून्य हो।
Bilinear Pairings का एंड-टू-एंड Solidity उदाहरण
आइए a, b, c और d के इन इनपुट्स को लें।
a = 4
b = 3
c = 6
d = 2
-ab + cd = 0
इसे फॉर्मूले में रखकर हम प्राप्त कर सकते हैं
Python में यह इसके बराबर होगा
from py_ecc.bn128 import neg, multiply, G1, G2
a = 4
b = 3
c = 6
d = 2
# negate G1 * a to make the equation sum up to 0
print(neg(multiply(G1, a)))
#(3010198690406615200373504922352659861758983907867017329644089018310584441462, 17861058253836152797273815394432013122766662423622084931972383889279925210507)
print(multiply(G2, b))
# ((2725019753478801796453339367788033689375851816420509565303521482350756874229, 7273165102799931111715871471550377909735733521218303035754523677688038059653), (2512659008974376214222774206987427162027254181373325676825515531566330959255, 957874124722006818841961785324909313781880061366718538693995380805373202866))
print(multiply(G1, c))
# (4503322228978077916651710446042370109107355802721800704639343137502100212473, 6132642251294427119375180147349983541569387941788025780665104001559216576968)
print(multiply(G2, d))
# ((18029695676650738226693292988307914797657423701064905010927197838374790804409, 14583779054894525174450323658765874724019480979794335525732096752006891875705), (2140229616977736810657479771656733941598412651537078903776637920509952744750, 11474861747383700316476719153975578001603231366361248090558603872215261634898))
यहाँ एक संरचित (structured) प्रारूप में आउटपुट दिया गया है
aG1_x = 3010198690406615200373504922352659861758983907867017329644089018310584441462,
aG1_y = 17861058253836152797273815394432013122766662423622084931972383889279925210507,
bG2_x1 = 2725019753478801796453339367788033689375851816420509565303521482350756874229,
bG2_x2 = 7273165102799931111715871471550377909735733521218303035754523677688038059653,
bG2_y1 = 2512659008974376214222774206987427162027254181373325676825515531566330959255,
bG2_y2 = 957874124722006818841961785324909313781880061366718538693995380805373202866,
cG1_x = 4503322228978077916651710446042370109107355802721800704639343137502100212473,
cG1_y = 6132642251294427119375180147349983541569387941788025780665104001559216576968,
dG2_x1 = 18029695676650738226693292988307914797657423701064905010927197838374790804409,
dG2_x2 = 14583779054894525174450323658765874724019480979794335525732096752006891875705,
dG2_y1 = 2140229616977736810657479771656733941598412651537078903776637920509952744750,
dG2_y2 = 11474861747383700316476719153975578001603231366361248090558603872215261634898
अब जब हमारे पास और ग्रुप्स में पॉइंट्स के रूप में एन्क्रिप्टेड वैल्यूज़ हैं, तो कोई और या एक प्रोग्राम यह कन्फर्म (पुष्टि) कर सकता है कि हमने की गणना सही ढंग से की है, और इसके लिए a, b, c, या d की व्यक्तिगत वैल्यूज़ जानने की आवश्यकता नहीं है। यहाँ एक Solidity कॉन्ट्रैक्ट है जो ecPairing प्रीकंपाइल का उपयोग करके यह कन्फर्म करता है कि हमने वैलिड (valid) वैल्यूज़ के साथ समीकरणों की गणना की है।
हम Foundry में यूनिट टेस्ट (unit test) करने के लिए Pairings.sol नाम की एक फ़ाइल बनाते हैं (हम इसके बाद टेस्ट फ़ाइल प्रदान करेंगे)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
contract Pairings {
/**
* returns true if == 0,
* returns false if != 0,
* reverts with "Wrong pairing" if invalid pairing
*/
function run(uint256[12] memory input) public view returns (bool) {
assembly {
let success := staticcall(gas(), 0x08, input, 0x0180, input, 0x20)
if success {
return(input, 0x20)
}
}
revert("Wrong pairing");
}
}
हम अपनी ecPairing कैलकुलेशन को कन्फर्म करने के लिए अपने Pairings कॉन्ट्रैक्ट को डिप्लॉय (deploy) और कॉल करने के लिए इस Foundry टेस्ट फ़ाइल का उपयोग करते हैं। निम्नलिखित फ़ाइल को हम TestPairings.sol कहते हैं।
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../src/Pairings.sol";
contract PairingsTest is Test {
Pairings public pairings;
function setUp() public {
pairings = new Pairings();
}
function testPairings() public view {
uint256 aG1_x = 3010198690406615200373504922352659861758983907867017329644089018310584441462;
uint256 aG1_y = 17861058253836152797273815394432013122766662423622084931972383889279925210507;
uint256 bG2_x1 = 2725019753478801796453339367788033689375851816420509565303521482350756874229;
uint256 bG2_x2 = 7273165102799931111715871471550377909735733521218303035754523677688038059653;
uint256 bG2_y1 = 2512659008974376214222774206987427162027254181373325676825515531566330959255;
uint256 bG2_y2 = 957874124722006818841961785324909313781880061366718538693995380805373202866;
uint256 cG1_x = 4503322228978077916651710446042370109107355802721800704639343137502100212473;
uint256 cG1_y = 6132642251294427119375180147349983541569387941788025780665104001559216576968;
uint256 dG2_x1 = 18029695676650738226693292988307914797657423701064905010927197838374790804409;
uint256 dG2_x2 = 14583779054894525174450323658765874724019480979794335525732096752006891875705;
uint256 dG2_y1 = 2140229616977736810657479771656733941598412651537078903776637920509952744750;
uint256 dG2_y2 = 11474861747383700316476719153975578001603231366361248090558603872215261634898;
uint256[12] memory points = [
aG1_x,
aG1_y,
bG2_x2,
bG2_x1,
bG2_y2,
bG2_y1,
cG1_x,
cG1_y,
dG2_x2,
dG2_x1,
dG2_y2,
dG2_y1
];
bool x = pairings.run(points);
console2.log("result:", x);
}
}
ध्यान दें कि जिस तरह से G2 पॉइंट्स को व्यवस्थित (arranged) किया गया है, वह उस तरह से नहीं है जिस तरह Python, G2 पॉइंट्स को लेआउट (lays out) करता है।
यह पास हो जाता है और कंसोल पर true प्रिंट करता है। ध्यान दें कि पॉइंट्स को उनके वेरिएबल नाम, वे किस ग्रुप से संबंधित हैं, और क्या वे elliptic curve पॉइंट के x या y का प्रतिनिधित्व करते हैं, इसके द्वारा लेबल किया गया है।
यह ध्यान रखना महत्वपूर्ण है कि ecPairing प्रीकंपाइल एक ऐरे (array) की अपेक्षा या आवश्यकता नहीं रखता है और इनलाइन-असेंबली (inline-assembly) के साथ एक का उपयोग करने का हमारा विकल्प केवल वैकल्पिक (optional) है। कोई Solidity के साथ भी ऐसा ही कर सकता है:
function run(bytes calldata input) public view returns (bool) {
// optional, the precompile checks this too and reverts (with no error) if false, this helps narrow down possible errors
if (input.length % 192 != 0) revert("Points must be a multiple of 6");
(bool success, bytes memory data) = address(0x08).staticcall(input);
if (success) return abi.decode(data, (bool));
revert("Wrong pairing");
}
और टेस्ट फ़ाइल को इस तरह अपडेट करें:
function testPairings() public view {
uint256 aG1_x = 3010198690406615200373504922352659861758983907867017329644089018310584441462;
uint256 aG1_y = 17861058253836152797273815394432013122766662423622084931972383889279925210507;
uint256 bG2_x1 = 2725019753478801796453339367788033689375851816420509565303521482350756874229;
uint256 bG2_x2 = 7273165102799931111715871471550377909735733521218303035754523677688038059653;
uint256 bG2_y1 = 2512659008974376214222774206987427162027254181373325676825515531566330959255;
uint256 bG2_y2 = 957874124722006818841961785324909313781880061366718538693995380805373202866;
uint256 cG1_x = 4503322228978077916651710446042370109107355802721800704639343137502100212473;
uint256 cG1_y = 6132642251294427119375180147349983541569387941788025780665104001559216576968;
uint256 dG2_x1 = 18029695676650738226693292988307914797657423701064905010927197838374790804409;
uint256 dG2_x2 = 14583779054894525174450323658765874724019480979794335525732096752006891875705;
uint256 dG2_y1 = 2140229616977736810657479771656733941598412651537078903776637920509952744750;
uint256 dG2_y2 = 11474861747383700316476719153975578001603231366361248090558603872215261634898;
bytes memory points = abi.encode(
aG1_x,
aG1_y,
bG2_x2,
bG2_x1,
bG2_y2,
bG2_y1,
cG1_x,
cG1_y,
dG2_x2,
dG2_x1,
dG2_y2,
dG2_y1
);
bool x = pairings.run(points);
console2.log("result:", x);
}
यह प्रारंभिक कार्यान्वयन (initial implementation) की तरह ही पास होगा और true रिटर्न करेगा क्योंकि यह प्रीकंपाइल को बिल्कुल समान कॉलडेटा (calldata) भेजता है।
फर्क सिर्फ इतना है कि पहले कार्यान्वयन (implementation) में, टेस्ट फ़ाइल पेयरिंग कॉन्ट्रैक्ट को पॉइंट्स का एक ऐरे (array) भेजती है जो पहले 32 बाइट्स (ऐरे की लंबाई) को स्लाइस करने के लिए इनलाइन-असेंबली (inline-assembly) का उपयोग करता है और बाकी को प्रीकंपाइल को भेज देता है। और दूसरे कार्यान्वयन में, टेस्ट फ़ाइल abi एन्कोडेड (encoded) पॉइंट्स को पेयरिंग कॉन्ट्रैक्ट को भेजती है जो इसे उसी रूप में (as it is) प्रीकंपाइल को फॉरवर्ड कर देता है।
RareSkills से और जानें
यह सामग्री हमारे Zero Knowledge Course से ली गई है। अधिक जानने के लिए कृपया प्रोग्राम देखें।
मैं वास्तव में bilinear pairings के पीछे के गणित (math) को समझना चाहता हूँ
आपको चेतावनी दी गई है, गणित काफी गहन (intense) है, और इसे समझने से आपको वास्तव में ZK Proofs लागू (implement) करने में मदद नहीं मिलेगी, जो इस पुस्तक का लक्ष्य है। आपने शायद SHA-256 या Keccak256 के आंतरिक पहलुओं (internals) को जाने बिना उनका उत्पादक रूप (productively) से उपयोग किया है, और हम दृढ़ता से (strongly) सुझाव देते हैं कि आप अपनी यात्रा के इस चरण में पेयरिंग्स के साथ भी ऐसा ही व्यवहार करें। फिर भी, यदि आप हमारी चेतावनियों से विचलित नहीं हुए हैं, और फिर भी आप इसमें गहराई से उतरना चाहते हैं तो यहाँ एक अच्छा संसाधन (resource) है: Pairings for Beginners। Here be dragons.
मूल रूप से 18 जुलाई, 2023 को प्रकाशित