रैंडम नंबर
blockchain पर रैंडमनेस थोड़ी मुश्किल होती है क्योंकि ब्लॉकचेन डिटरमिनिस्टिक (deterministic) होता है, लेकिन रैंडमनेस के लिए नॉन-डिटरमिनिज्म (non-determinism) की आवश्यकता होती है (अन्यथा यह पूर्वानुमान योग्य बन जाता है)। यह लेख मानकर चलता है कि उपयोगकर्ता को पहले से ही solidity की कुछ जानकारी है, विशेष रूप से ऑपरेशन्स block.number(), block.hash(), और डिजिटल सिग्नेचर्स के बारे में।
यदि आप block.timestamp या पिछले blockhash जैसे डेटा का उपयोग करते हैं, तो कोई भी स्मार्ट कॉन्ट्रैक्ट का उपयोग करके यह अनुमान लगा सकता है कि किसी ट्रांज़ैक्शन का परिणाम वांछित (desired) होगा या नहीं। इस विषय पर आपको परखने के लिए Capture the ether में हैक्स की एक श्रृंखला मौजूद है।
यदि आपको रैंडम नंबर्स की आवश्यकता है, तो कुछ डिज़ाइन पैटर्न्स हैं जिन पर आप विचार कर सकते हैं।
Commit Reveal
यद्यपि निष्पादन (execution) के समय ब्लॉकचेन ट्रांज़ैक्शन्स पूरी तरह से डिटरमिनिस्टिक होते हैं, लेकिन वे भविष्य की भविष्यवाणी नहीं कर सकते हैं। विशेष रूप से, भविष्य के blockhash की भविष्यवाणी नहीं की जा सकती है (नीचे वर्णित एक अपवाद के साथ)।
यह इस तरह काम करता है: एक ट्रांज़ैक्शन यह कमिट करता है कि वर्तमान ब्लॉक n से 20 ब्लॉक्स आगे भविष्य में, 20 + n पर जो भी blockhash होगा, वही blockhash रैंडम नंबर होगा। चूँकि आप ब्लॉक n + 2 के blockhash की भविष्यवाणी नहीं कर सकते, जहाँ n वर्तमान ब्लॉक है, तो ब्लॉक n + 20 को रैंडम माना जाता है।
हैश फंक्शन प्राप्त करने के लिए आपको 256 ब्लॉक्स के लुकबैक (lookback) की अनुमति होती है, इसलिए उपयोगकर्ता को ब्लॉक n + 20 और n + 276 के बीच किसी समय एक दूसरा ट्रांज़ैक्शन शुरू करना चाहिए। दूसरा ट्रांज़ैक्शन रिवील (reveal) होता है। बेशक, उपयोगकर्ता यह देख सकता है कि रैंडम नंबर उनके पक्ष में आया है या नहीं, इसलिए एप्लिकेशन को इस तरह से कॉन्फ़िगर किया जाना चाहिए कि यदि परिणाम उपयोगकर्ता के अनुकूल हो, तो उसे दूसरा ट्रांज़ैक्शन भेजने के लिए प्रोत्साहित (incentivized) किया जा सके।
उदाहरण के लिए, एक फेयर कॉइन फ्लिप उपयोगकर्ता को भुगतान करेगा यदि, मान लीजिए, blockhash एक सम संख्या (even number) था। यदि उपयोगकर्ता देखता है कि blockhash विषम (odd) था तो वह दूसरा ट्रांज़ैक्शन नहीं करेगा, क्योंकि वैसे भी वे कुछ नहीं जीतते हैं। जीतने के लिए, blockhash को सम होना चाहिए, और केवल तभी वे दूसरा ट्रांज़ैक्शन भेजने की जहमत उठाएंगे।
ब्लॉक प्रोड्यूसर्स द्वारा इस स्कीम के साथ छेड़छाड़ की जा सकती है। हालाँकि ब्लॉक प्रोड्यूसर्स किसी सटीक हैश वैल्यू को थोप नहीं सकते, लेकिन वे ट्रांज़ैक्शन्स को तब तक री-ऑर्डर (re-order) कर सकते हैं जब तक कि ब्लॉक हैश एक सम संख्या (या कुछ ऐसा जो उनके अनुकूल हो) उत्पन्न न कर दे।
आप उपयोगकर्ता से ब्लॉक n पर एक सीक्रेट नंबर का हैश कमिट करवाकर इस व्यवहार से बचाव कर सकते हैं। ब्लॉक n + 20 पर, उपयोगकर्ता हैश की प्री-इमेज (pre-image) का खुलासा करता है और उस प्री-इमेज को blockhash के साथ जोड़ (concatenated) दिया जाता है। उन दोनों वैल्यूज़ के कॉनकेटिनेशन को हैश किया जाता है, और उस हैश का उपयोग रैंडम नंबर के रूप में किया जाता है।
ब्लॉक प्रोड्यूसर हैश की प्री-इमेज को नहीं जान सकता, इसलिए वे इसके साथ छेड़छाड़ नहीं कर सकते। लेकिन यह अभी भी पूरी तरह से ब्लॉक-प्रोड्यूसर प्रूफ नहीं है।
यदि ब्लॉक प्रोड्यूसर लॉटरी खेल रहा है, तो वे एक ज्ञात सीक्रेट नंबर कमिट कर सकते हैं, फिर blockhash के साथ इस तरह छेड़छाड़ कर सकते हैं कि अंतिम परिणाम उनके अनुकूल हो।
अब जब Ethereum प्रूफ ऑफ स्टेक (proof of stake) पर चला गया है, तो इस हमले को अंजाम देना अधिक कठिन है, क्योंकि दुर्भावनापूर्ण प्रोड्यूसर को रिवील के सटीक ब्लॉक पर ही ब्लॉक प्रोड्यूसर होना चाहिए।
लेकिन अगर आप दुर्भावनापूर्ण ब्लॉक प्रोड्यूसर्स से सुरक्षित रहना चाहते हैं, तो आपको Chainlink VRF का उपयोग करना चाहिए (जिसका वर्णन आगे किया गया है)।
यदि पर्याप्त रूप से प्रेरित हो, तो आर्थिक रूप से सक्षम हमलावर इस स्कीम का फायदा उठा सकता है। मान लीजिए कि यदि blockhash सम है तो हम खिलाड़ी को भुगतान करते हैं। हमलावर ब्लॉक 20 और 276 के बीच रिवील ट्रांज़ैक्शन को रोकने के लिए नेटवर्क को उच्च गैस ट्रांज़ैक्शन्स से भर (flood) सकता है। याद रखें, यदि लुक बैक 256 से अधिक है तो blockhash शून्य उत्पन्न करता है। यह हमलावर के लिए बहुत महंगा होगा, लेकिन यह अभी भी एक संभावित अटैक वेक्टर है।
Chainlink VRF
Chainlink VRF (Verifiable Random Function) के साथ रैंडम नंबर्स कैसे जनरेट करें, इसके बारे में ऑनलाइन पहले ही बहुत कुछ लिखा जा चुका है। उनके documentation बहुत अच्छे हैं और उनका पालन करना आसान है। लेकिन संक्षेप में यह इस प्रकार काम करता है।
वह स्मार्ट कॉन्ट्रैक्ट जो एक रैंडम नंबर चाहता है, रैंडम नंबर का अनुरोध करने के लिए chainlink स्मार्ट कॉन्ट्रैक्ट को कॉल करता है (और लागत को कवर करने के लिए कुछ LINK का भुगतान करता है)।
Chainlink अनुरोध को स्वीकार करेगा और निर्दिष्ट संख्या में ब्लॉक्स की प्रतीक्षा करेगा और उस कॉन्ट्रैक्ट को कॉलबैक (call back) करेगा जिसने रैंडम नंबर का अनुरोध किया था। रैंडम नंबर जनरेट करने के लिए Chainlink का एल्गोरिदम पारदर्शी (transparent) है, इसलिए कोई भी यह सत्यापित कर सकता है कि इसे निष्पक्ष तरीके से बनाया गया था।
Chainlink ऐसा एक ट्रांज़ैक्शन में नहीं कर सकता, अन्यथा कोई दुर्भावनापूर्ण खिलाड़ी अपनी पसंद का परिणाम न मिलने पर ट्रांज़ैक्शन को रिवर्ट (revert) कर सकता है।
चेन रीऑर्गनाइजेशन (chain reorganization) की संभावना के कारण, एप्लिकेशन को यह निर्दिष्ट करना चाहिए कि उच्च मूल्य वाले यूज़ केसेस के लिए कॉलबैक भविष्य में कुछ और ब्लॉक्स बाद हो।
Chainlink दूसरा ट्रांज़ैक्शन शुरू करता है, इसलिए यह उपयोगकर्ता को दूसरा ट्रांज़ैक्शन अधिकृत करने की परेशानी से बचाता है। हालाँकि, इस सुविधा की एक वास्तविक कीमत होती है, क्योंकि उपयोगकर्ता को न केवल गैस लागत का भुगतान करना होता है, बल्कि उन्हें (या एप्लिकेशन को) इस सेवा का उपयोग करने के लिए LINK टोकन का भी भुगतान करना होता है।
Offchain signature
मुझे gaspack.xyz के साथ हुई बातचीत को इसका श्रेय देना होगा, जो इस विचार का मूल लेकर आए थे।
उपरोक्त समाधानों के साथ एक स्पष्ट UX समस्या यह है कि उनमें किसी न किसी प्रकार की देरी की आवश्यकता होती है, और संभावित रूप से दो ट्रांज़ैक्शन्स की। किसी ब्लॉकचेन गेम में, खिलाड़ियों को यह देरी पसंद नहीं आ सकती है।
किसी कमज़ोर (vulnerable) स्मार्ट कॉन्ट्रैक्ट को बनाए बिना आप ऐसा कैसे कर सकते हैं?
रैंडम नंबर्स प्राप्त करने का एक सेमी-डिसेंट्रलाइज्ड तरीका यह है कि एक ऑफचेन रैंडम नंबर जनरेटर द्वारा एक रैंडम नंबर, सेंडर और भविष्य के एक ब्लॉक नंबर को जनरेट और क्रिप्टोग्राफिक रूप से साइन कराया जाए।
उस रैंडम नंबर को भविष्य के blockhash के साथ जोड़ (concatenated) दिया जाएगा, और परिणामी स्ट्रिंग को हैश किया जाएगा। इससे रैंडम नंबर उत्पन्न होता है। रिवॉर्ड वितरित करने के लिए ज़िम्मेदार स्मार्ट कॉन्ट्रैक्ट सेंडर और ब्लॉक नंबर के विरुद्ध सिग्नेचर को सत्यापित करता है।
भले ही ऑफचेन रैंडम नंबर जनरेटर पूरी तरह से रैंडम न हो, या थोड़ा दुर्भावनापूर्ण भी हो, यह भविष्य के blockhashes की भविष्यवाणी नहीं कर सकता है, और यह नहीं जानता कि इसके रैंडम नंबर को किसके साथ जोड़ा जाएगा। चूंकि सिग्नेचर केवल एक विशेष ब्लॉक पर मान्य होता है, खिलाड़ी पासा (dice) फेंकने से पहले किसी अनुकूल ब्लॉक की प्रतीक्षा नहीं कर सकता।
इस स्कीम के कमज़ोर होने के तीन तरीके हैं:
- माइनर रैंडम नंबर जनरेटर दोनों को नियंत्रित करता है और निर्दिष्ट समय पर ब्लॉक प्रोड्यूसर भी होता है। जब तक ब्लॉक प्रोड्यूसर और रैंडम नंबर जनरेटर मिलीभगत (collude) नहीं करते, तब तक यह स्कीम सुरक्षित है।
- रैंडम नंबर बार-बार एक ही संख्या उत्पन्न करता है। इस मामले में, माइनर आसानी से अनुमान लगा सकता है कि उसे हैश के साथ कैसे छेड़छाड़ करनी चाहिए।
- खिलाड़ियों के लिए कई रैंडम नंबर्स प्राप्त करने से बचने का एक तरीका होना चाहिए ताकि वे परिणाम अनुकूल होने तक कोशिश न करते रहें। इसके लिए अनिवार्य रूप से किसी प्रकार की ऑफ-चेन गेटिंग (off-chain gating) की आवश्यकता होगी जो संभवतः कम पारदर्शी और कम सुरक्षित होगी।
रैंडम नंबर प्रोड्यूसर को और अधिक डिसेंट्रलाइज करके इन कमियों को कुछ हद तक कम करना संभव है। उदाहरण के लिए, आप रैंडम नंबर्स के स्रोत के रूप में bitcoin नेटवर्क पर ब्लॉक हैश का उपयोग कर सकते हैं। यदि कोई गड़बड़ चल रही है तो यह उसे पारदर्शी बना देगा।
मूल रूप से 2 दिसंबर, 2022 को प्रकाशित