गहन तकनीकी विश्लेषण: zk-SNARKs के साथ गुमनाम वोटिंग
Vocdoni के गुमनाम मतदान की तकनीकी विशेषताएँ जानिए और यह भी कि हम इस ढाँचे तक पहुँचे कैसे।
F
Ferran
· पढ़ने में 11 मिनट
गुमनाम मतदान की परिभाषा
तकनीकी विवरण में उतरने से पहले यह बताना ज़रूरी है कि हम गुमनाम मतदान को परिभाषित कैसे करते हैं, और इस ढाँचे तक पहुँचे क्यों। उत्पाद घोषणा वाले लेख में हमने दो तरह की गुमनामी बताई थी: मतपत्र गोपनीकरण और मतदाता गुमनामी।
मतपत्र गोपनीकरण में गुमनामी हर वोट की सामग्री छिपाने से आती है, जबकि मतदाता गुमनामी हर मतदाता और उसके वोट के बीच की कड़ी तोड़ देती है, वोट को छिपाए बिना। गुमनाम मतदान का हमारा कार्यान्वयन मतदाता गुमनामी पर टिका है। ऐसा क्यों?
पहली बात, मतपत्र गोपनीकरण अपने साथ कुछ बड़े समझौते लेकर आता है। किसी मतदान प्रक्रिया की end-to-end सत्यापन क्षमता के लिए मतदाताओं का अपने डाले हुए मतपत्रों की सामग्री देख पाना और उन्हें ट्रैक कर पाना ज़रूरी है। गुमनामी हम मतपत्र गोपनीकरण से पक्की करें तो सत्यापन क्षमता हाथ से निकल जाती है, क्योंकि हर वोट की सामग्री छिपी हो तो न यह जाँचा जा सकता है कि आपका वोट गिना गया, न वोट की अखंडता का ऑडिट हो सकता है।
मतपत्र गोपनीकरण का एक और नुकसान यह है कि डिजिटल मतदान प्रणाली में इसे लागू करना मुश्किल है। मतपत्र गोपनीकरण की एक तकनीक होमोमॉर्फिक एन्क्रिप्शन है, जिसे पारंपरिक मतदान प्रणालियों को क्रिप्टोग्राफ़िक गुमनामी देने के लिए सुझाया गया है। होमोमॉर्फिक एन्क्रिप्शन एन्क्रिप्टेड मानों पर गणना करने देती है, यानी एक भी मतपत्र की सामग्री देखे बिना वोटों की गिनती निकाली जा सकती है। सिद्धांत में यह ठीक बैठती है, पर होमोमॉर्फिक एन्क्रिप्शन के साथ इतने नुकसान जुड़े हैं कि मतदान तकनीक की रीढ़ के तौर पर यह कम भरोसेमंद विकल्प रह जाती है।
सबसे बड़ी बात, यह तरीका एन्क्रिप्टेड मतपत्रों पर सिर्फ़ बुनियादी गणना करने देता है, जिससे क्वाड्रैटिक या वरीयता-क्रममतदान जैसी व्यवस्थाएँ लागू करना और मुश्किल हो जाता है। होमोमॉर्फिक एन्क्रिप्शन नतीजों की गणना की गारंटी तो देती है, पर मतदाता के मतपत्र के end-to-end सत्यापन की नहीं। यह व्यवस्था ऐसा कोई क्रिप्टोग्राफ़िक प्रमाण नहीं देती जो पक्का करे कि मतदाता का मतपत्र नतीजों में शामिल हुआ और सही ढंग से दर्ज हुआ। हमारा यह भी आकलन है कि किसी मतदान प्रक्रिया के होमोमॉर्फिक प्रमाण को जाँचने में बहुत ज़्यादा कंप्यूटिंग संसाधन लगेंगे, और मतदान जितना बड़ा होगा यह खर्च उतना ही बढ़ता जाएगा। इससे सार्वभौमिक रूप से सत्यापन योग्य प्रणाली का फ़ायदा ही खत्म हो सकता है, क्योंकि सत्यापन सिर्फ़ ताकतवर और महँगी मशीनों पर, इतने लंबे समय में हो पाएगा कि व्यावहारिक ही न रहे।
मतदाता गुमनामी के होमोमॉर्फिक एन्क्रिप्शन पर कई फ़ायदे हैं। पहला, end-to-end सत्यापन क्षमता कहीं ऊँचे दर्जे तक मुमकिन हो जाती है। मतदाता और उसके मतपत्र के बीच की कड़ी किसी भी तीसरे पक्ष की नज़र से ओझल रहनी चाहिए, पर मतदाता खुद अपना मतपत्र पहचान सकता है। मतपत्र खुद दिखते हैं, इसलिए कोई भी उपयोगकर्ता अपने वोट को डालने के क्षण से लेकर नतीजों में शामिल होने तक ट्रैक कर सकता है। साथ ही तीसरे पक्ष यह पहचान सकते हैं कि हर मतपत्र किसी वैध मतदाता का है और उसकी सामग्री सही गिनी गई।
मतदाता गुमनामी आम तौर पर कहीं ज़्यादा कंप्यूटिंग-कुशल ढाँचे मुमकिन बनाती है। ऐसी प्रणालियाँ गोपनीकरण हर मतदाता के मतपत्र प्रमाण पर एक बार करती हैं, न कि हर नया मतपत्र आने पर पूरे नतीजों पर दोबारा। नतीजे सबके सामने रखने के लिए कोई भारी गणना नहीं करनी पड़ती। गुमनामी से समझौता किए बिना चुनाव के नतीजे मतदान प्रक्रिया के दौरान भी प्रकाशित किए जा सकते हैं (बशर्ते समय-सहसंबंध हमलों से बचाव कर लिया गया हो)।
इसीलिए हमने मतदाता गुमनामी को अपनी मतदाता सूची प्रमाण व्यवस्था में ही गूँथने का फ़ैसला किया।
मतदाता सूची का Merkle ट्री
मतदाता सूची व्यवस्था की शुरुआत मतदाता सूची Merkle ट्री से होती है। यह हैश की गई सार्वजनिक कुंजियों की एक संरचना है, जिसकी हर कुंजी सूची के एक पात्र मतदाता को दर्शाती है। यहाँ Merkle ट्री के इस्तेमाल से कोई भी वैध मतदाता यह साबित कर सकता है कि उसके पास सूची में शामिल censusKey है, और इसके लिए उसे किसी दूसरे मतदाता की कुंजी जानने की ज़रूरत नहीं पड़ती। मतदाता फिर यह प्रमाण अपने मतपत्र के साथ जमा करता है। इसी वोट लिफ़ाफ़े के साथ एक nullifier भी जुड़ा होता है: उपयोगकर्ता की censusKey और electionID से निकाला गया डेटा का एक टुकड़ा, जो उसके वोट से विशिष्ट रूप से मेल खाता है।
सिर्फ़ इस प्रमाण के सहारे चलें तो प्रक्रिया का आयोजक हर वोट लिफ़ाफ़े को उसके nullifier के ज़रिए किसी मतदाता की censusKey से जोड़ सकता है, और मतदान की गुमनामी टूट जाएगी। इससे बचने के लिए Vocdoni मतदान की गुमनामी पक्की करने को zk-SNARKs का इस्तेमाल करता है।
zk-SNARK का पूरा रूप है zero-knowledgeSuccinct Non-interactive ARgument of Knowledge (यानी शून्य-ज्ञान वाला संक्षिप्त और गैर-संवादात्मक ज्ञान-प्रमाण)। zk-SNARK किसी उपयोगकर्ता को यह साबित करने देता है कि उसके पास कोई जानकारी है, बिना वह जानकारी उजागर किए।
हमारे मामले में zk-SNARKs मतदाताओं को अपनी secretKey उजागर किए बिना यह साबित करने देते हैं कि वे मतदाता सूची में हैं। ठीक-ठीक कहें तो यह काम zk-Circuit से होता है, यानी एक ऐसा सॉफ़्टवेयर सर्किट जो ज़ीरो-नॉलेज प्रूफ़ (ZKP) बनाने के लिए गढ़ा गया है।
हमने एक ऐसा सर्किट बनाया है जो उपयोगकर्ताओं को अपनी secretKey उजागर किए बिना मतदाता सूची में अपनी सदस्यता का ZKP बनाने देता है। सर्किट निजी और सार्वजनिक, दोनों तरह के इनपुट लेता है, और जो डेटा मतदाता की पहचान खोल सकता है उसे निजी रखा जाता है। सार्वजनिक इनपुट वोट लिफ़ाफ़े के भीतर जमा होते हैं, ताकि वैलिडेटर उन्हें प्रमाण से मिलाकर जाँच सकें और पक्का कर सकें कि उपयोगकर्ता ने दो बार वोट नहीं डाला।
यही सर्किट मिलते-जुलते आकार की मतदाता सूची वाली किसी भी प्रक्रिया में काम आ सकता है, और हर आकार के लिए इसे सिर्फ़ एक बार बनाना पड़ता है। ठीक-ठीक कहें तो एक बार बना सर्किट उसी ट्री ऊँचाई वाले किसी भी मतदाता सूची Merkle ट्री के लिए दोबारा इस्तेमाल हो सकता है। हम बाइनरी ट्री इस्तेमाल करते हैं, इसलिए n के हर उस मान के लिए एक सर्किट चाहिए जहाँ लक्षित सूची का आकार 2^n की सीमा में आता हो (जैसे 128, 256, ..., 8192, 16384 वगैरह)।
सर्किट बनाने की प्रक्रिया trusted setup ceremony पर टिकी है। इसमें सर्किट के लिए प्रूवर और वेरिफ़ायर कुंजियाँ बनाई जाती हैं, और इसे “trusted” इसलिए कहते हैं कि इन कुंजियों का पूरा निर्माण एक ही पक्ष के हाथ में हो तो वह झूठे प्रमाण गढ़ सकता है। इसीलिए सर्किट की भरोसेमंदी के लिए एक विकेंद्रित ‘ceremony’ ज़रूरी है। ऐसी ceremony में अलग-अलग जगहों पर बैठे, आपस में टकराते हितों वाले कई पक्ष कुंजी निर्माण का एक-एक चरण करते हैं। इनमें से एक भी पक्ष ईमानदारी बरते, तो झूठा प्रमाण गढ़ना नामुमकिन हो जाएगा।
franchise proof zk-SNARK सर्किट चलाकर बनाया जाता है।
निजी इनपुट:index, secretKey, census Merkle-proof
सार्वजनिक इनपुट:census Merkle-root, nullifier, election ID, vote
आउटपुट:franchise proof
secretKey या मतदाता सूची Merkle प्रमाण उजागर किए बिना यह सर्किट यह दिखा सकता है कि:
मतदाता उस secretKey का मालिक है जो किसी खास zkCensusKey से मेल खाती है।
मतदाता की zkCensusKey मतदाता सूची Merkle ट्री में शामिल है।
मतदाता का दिया हुआ nullifier किसी खास मतदान प्रक्रिया के लिए उसकी secretKey और election ID से विशिष्ट रूप से मेल खाता है।
गणना CPU और मेमोरी दोनों पर भारी पड़ती है, फिर भी ZKP उपयोगकर्ता के क्लाइंट पर, साधारण हार्डवेयर पर ही बन जाते हैं। Vocdoni प्रोटोकॉल में प्रमाण को Vochain नोड, माइनर और प्रक्रिया पर नज़र रखने वाला कोई भी तीसरा पक्ष जाँचता है।
सर्किट को घटाकर उसकी न्यूनतम ज़रूरतों तक सीमित इसलिए किया गया है ताकि यह किसी भी तरह के क्लाइंट हार्डवेयर पर चल सके। यही वजह है कि हमने हस्ताक्षर सत्यापन के बजाय secretKey वाला तरीका अपनाया।
गुमनाम मतदाता सूची बनाने में इस्तेमाल होने वाला Merkle ट्री zk-SNARK-अनुकूल होना चाहिए। फ़िलहाल हम zk-SNARK सर्किट के लिए Circom कंपाइलर इस्तेमाल करते हैं, इसलिए हमें ऐसा ट्री चाहिए जो circomlib के Merkle ट्री कार्यान्वयन के साथ चले। ऐसे Merkle ट्री का विवरण यहाँ मिल सकता है।
Vochainarbo Merkle ट्री इस्तेमाल करता है, जो Circom के ढाँचे के अनुकूल एक Go कार्यान्वयन है। यह Merkle ट्री Poseidon हैश पर चलता है, एक 'SNARK-अनुकूल' हैश फ़ंक्शन जिसे आगे चलकर सर्किट के भीतर, बहुत ज़्यादा कंस्ट्रेंट के बिना, प्रमाणित किया जा सकता है। नीचे दिया चित्र zk-census-proof की व्यवस्था में इस्तेमाल हो रहे Merkle ट्री की लीफ़ के डेटा ढाँचे को दिखाता है।
index का मान मतदाता सूची ट्री बनाने वाला तय करता है, जिसके पास हर सूची ट्री के लिए एक index मान होता है। हर नई लीफ़ जुड़ने पर यह मान एक बढ़ जाता है। लीफ़ को इसी ढंग से गढ़ा गया है ताकि Merkle ट्री का इस्तेमाल ज़्यादा कुशलता से हो, छोटे ट्री में ज़्यादा उपयोगकर्ताओं की कुंजियाँ समा जाएँ और zk-Circuit का आकार घट जाए। वजह यह है कि किसी भी लीफ़ की key का value ही ट्री पर उस लीफ़ की जगह तय करता है।
लीफ़ की key बढ़ते हुए index के बजाय zkCensusKey से तय होती, तो किसी दी हुई ऊँचाई पर सारी उपलब्ध जगहें भरने से पहले ही हर नई लीफ़ के टकराने की अच्छी-खासी आशंका रहती। ऐसे ट्री कम संतुलित होते और ट्री की जगह के बेकार इस्तेमाल की वजह से उतनी ही बड़ी सूची के लिए बड़े सर्किट माँगते। दूसरी तरफ़ बढ़ते हुए index वाले तरीके में सारी लीफ़ जगहें बिना एक भी टकराव के भर जाती हैं। इससे उतने ही उपयोगकर्ताओं के लिए कहीं छोटे सर्किट बनते हैं।
index: इसे Vochain तब तय करता है जब उपयोगकर्ता की zkCensusKey मतदाता सूची ट्री में जोड़ी जाती है
secretKey: इसे उपयोगकर्ता बनाता है
voteValue: उपयोगकर्ता के वोट का हैश किया गया मान, जो दो बड़े पूर्णांकों से बना है।
कच्चा उपयोगकर्ता वोट मानों की एक परिवर्तनीय लंबाई वाली सरणी है और सर्किट में उसके मान जाँचने की ज़रूरत नहीं पड़ती। ये मान एन्क्रिप्टेड भी हो सकते हैं।
एन्कोड किए गए वोट मान सर्किट के तय संख्या वाले इनपुट में शायद न समाएँ, इसलिए हम कच्चे उपयोगकर्ता वोट का सार एक EVM-अनुकूल हैश फ़ंक्शन sha256(vote_bytes) से निकालते हैं। sha256 हैश का आउटपुट SNARKS में इस्तेमाल होने वाले फ़ील्ड से थोड़ा बड़ा है, इसलिए हम हैश आउटपुट (32 बाइट) को 16-16 बाइट की 2 सरणियों में बाँटते हैं, उन्हें पूर्णांक (लिटिल-एंडियन में) मानते हैं, और सर्किट इनपुट के तौर पर इस्तेमाल करते हैं।
sha256 हैश इसलिए इस्तेमाल होता है कि आगे ज़रूरत पड़ने पर इसे सर्किट के भीतर जाँचा जा सके। इस इस्तेमाल की दो बातें ध्यान में रखनी हैं: EVM में गैस के हिसाब से sha256, keccak256 से दोगुना महँगा है, पर यह circom में लागू है, इसलिए इसे सर्किट के भीतर जाँचा जा सकता है, और circom सर्किट के भीतर sha256 जाँचना कंस्ट्रेंट की संख्या के लिहाज़ से महँगा पड़ता है (इस स्पेक के मौजूदा संस्करण में यह सर्किट के भीतर नहीं जाँचा जाता)।
h := sha256.Sum256(voteBytes) // voteBytes can be the votes array converted to bytes, or the encrypted votes
b1 := new(big.Int).SetBytes(swapEndianness(h[:16])) // swap endianness, as golang big int package works in big-endian, and we use little-endian
b2 := new(big.Int).SetBytes(swapEndianness(h[16:]))
और सर्किट के लिए voteValue का json इनपुट ऐसा होगा: "voteValue": [b1, b2]
electionID: उस चुनाव का ID जिसमें उपयोगकर्ता हिस्सा ले रहा है
nullifier: इसे उपयोगकर्ता निकालता है: nullifier = poseidon.Hash(sk, electionID)
DAO के लिए गुमनाम मतदान
इस सवाल का जवाब देने के लिए कि “यह तकनीक Ethereum DAO मतदान में कैसे इस्तेमाल हो सकती है”, थोड़ा संदर्भ जोड़ना ज़रूरी है:
फ़िलहाल Ethereum की भीतरी क्रिप्टोग्राफ़ी, डेटा संरचनाएँ और Merkle ट्री zk-SNARK-अनुकूल नहीं हैं, इसलिए विकल्प सीमित हैं (अभी के लिए)।
Vocdoni फ़िलहाल Ethereum स्टोरेज प्रूफ़ मतदान चलाता है, जो बिना gas वाले ऑफ़चेन मतदान कराने का इस समय का ज़्यादा सुरक्षित और भरोसा-मुक्त तरीका है (Aragon Voice फ़्रंटएंड में लागू)
Vocdoni अभी पूरा ऑफ़चेन बाइंडिंग निष्पादन नहीं देता (Ethereum के भीतर मतदान के लिए ऑप्टिमिस्टिक तरीका चाहिए), फिर भी हमने इस दायरे में कुछ प्रूफ़-ऑफ़-कॉन्सेप्ट काम किया है, और पूरे गुण हासिल करने के लिए शोध जारी है (नए ढाँचे और प्रूफ़-ऑफ़-कॉन्सेप्ट जल्द आ रहे हैं)।
DAO पर गुमनाम मतदान के लिए Vocdoni फ़िलहाल इन चरणों से काम करता है:
Vocdoni ब्लॉकचेन में एक नया चुनाव बनाया जाता है, जिसका censusRoot Ethereum स्टेट रूट और ERC20 कॉन्ट्रैक्ट पते के बराबर होता है।
उपयोगकर्ता Web3 से एक Ethereum स्टोरेज प्रूफ़ लेते हैं, जो दिखाता है कि किसी कॉन्ट्रैक्ट और स्टेट रूट के लिए उनके पास टोकन हैं।
उपयोगकर्ता एक नई अस्थायी secretKey बनाते हैं और Vocdoni ब्लॉकचेन को एक ट्रांज़ैक्शन भेजते हैं, जिससे साबित होता है कि वे पात्र मतदाता हैं (स्टोरेज प्रूफ़ के ज़रिए)। इस ट्रांज़ैक्शन में हैश की गई secretKey शामिल होती है, जो एक rolling Census में जुड़ जाती है और जिसे Vocdoni ब्लॉकचेन का लॉजिक भीतर ही निकालता है। इस चरण को key pre-register कहते हैं।
प्री-रजिस्टर पूरा होने के बाद उपयोगकर्ता अपनी secretKey से गुमनाम वोट डाल सकते हैं