ऑफ़लाइन डेटा अखंडता के लिए मर्कल प्रमाण
परिचय
आदर्श रूप से हम सब कुछ एथेरियम स्टोरेज में स्टोर करना चाहेंगे, जो हजारों कंप्यूटरों में संग्रहीत है और इसमें अत्यधिक उच्च उपलब्धता (डेटा को सेंसर नहीं किया जा सकता है) और अखंडता (डेटा को अनधिकृत तरीके से संशोधित नहीं किया जा सकता है) है, लेकिन 32-बाइट शब्द को संग्रहीत करने में आमतौर पर 20,000 गैस लगती है। जब मैं यह लिख रहा हूँ, तो वह लागत $6.60 के बराबर है। 21 सेंट प्रति बाइट पर यह कई उपयोगों के लिए बहुत महंगा है।
इस समस्या को हल करने के लिए एथेरियम इकोसिस्टम ने डेटा को विकेंद्रीकृत तरीके से स्टोर करने के कई वैकल्पिक तरीके विकसित किए। आमतौर पर उनमें उपलब्धता और कीमत के बीच एक ट्रेडऑफ़ शामिल होता है। हालांकि, अखंडता आमतौर पर सुनिश्चित की जाती है।
इस लेख में आप सीखेंगे कि ब्लॉकचेन पर डेटा संग्रहीत किए बिना डेटा अखंडता कैसे सुनिश्चित की जाए, मर्कल प्रमाण (opens in a new tab) का उपयोग करके।
यह कैसे काम करता है?
सिद्धांत रूप में हम डेटा का हैश ऑन-चेन संग्रहीत कर सकते हैं, और उन ट्रांजेक्शन में सभी डेटा भेज सकते हैं जिनकी इसे आवश्यकता है। हालांकि, यह अभी भी बहुत महंगा है। एक ट्रांजेक्शन के लिए डेटा के एक बाइट की लागत लगभग 16 गैस होती है, वर्तमान में लगभग आधा सेंट, या लगभग $5 प्रति किलोबाइट। $5000 प्रति मेगाबाइट पर, यह कई उपयोगों के लिए अभी भी बहुत महंगा है, यहां तक कि डेटा को हैश करने की अतिरिक्त लागत के बिना भी।
इसका समाधान डेटा के विभिन्न सबसेट को बार-बार हैश करना है, इसलिए जिस डेटा को आपको भेजने की आवश्यकता नहीं है, उसके लिए आप बस एक हैश भेज सकते हैं। आप यह एक मर्कल ट्री का उपयोग करके करते हैं, एक ट्री डेटा संरचना जहां प्रत्येक नोड उसके नीचे के नोड्स का एक हैश है:
रूट हैश एकमात्र हिस्सा है जिसे ऑन-चेन संग्रहीत करने की आवश्यकता है। एक निश्चित मूल्य साबित करने के लिए, आप वे सभी हैश प्रदान करते हैं जिन्हें रूट प्राप्त करने के लिए इसके साथ संयोजित करने की आवश्यकता होती है। उदाहरण के लिए, C को साबित करने के लिए आप D, H(A-B), और H(E-H) प्रदान करते हैं।
कार्यान्वयन
नमूना कोड यहाँ प्रदान किया गया है (opens in a new tab)।
ऑफ़-चेन कोड
इस लेख में हम ऑफ़-चेन संगणना के लिए जावास्क्रिप्ट का उपयोग करते हैं। अधिकांश विकेंद्रीकृत अनुप्रयोगों में उनका ऑफ़-चेन घटक जावास्क्रिप्ट में होता है।
मर्कल रूट बनाना
सबसे पहले हमें चेन को मर्कल रूट प्रदान करने की आवश्यकता है।
const ethers = require("ethers")
हम ethers पैकेज से हैश फ़ंक्शन का उपयोग करते हैं (opens in a new tab)।
// वह कच्चा डेटा जिसकी अखंडता हमें सत्यापित करनी है। पहले दो बाइट्स एक
// यूज़र पहचानकर्ता हैं, और अंतिम दो बाइट्स वर्तमान में यूज़र के स्वामित्व वाले
// टोकन की राशि हैं।
const dataArray = [
0x0bad0010, 0x60a70020, 0xbeef0030, 0xdead0040, 0xca110050, 0x0e660060,
0xface0070, 0xbad00080, 0x060d0091,
]
प्रत्येक प्रविष्टि को एक एकल 256-बिट पूर्णांक में एन्कोड करने से उदाहरण के लिए, JSON का उपयोग करने की तुलना में कम पठनीय कोड बनता है। हालांकि, इसका मतलब अनुबंध में डेटा पुनर्प्राप्त करने के लिए काफी कम प्रसंस्करण है, इसलिए गैस की लागत बहुत कम है। आप JSON ऑन-चेन पढ़ सकते हैं (opens in a new tab), यह केवल एक बुरा विचार है यदि टाला जा सके।
// हैश मानों की सरणी, BigInts के रूप में
const hashArray = dataArray
इस मामले में हमारा डेटा शुरू करने के लिए 256-बिट मान है, इसलिए किसी प्रसंस्करण की आवश्यकता नहीं है। यदि हम स्ट्रिंग्स जैसी अधिक जटिल डेटा संरचना का उपयोग करते हैं, तो हमें यह सुनिश्चित करने की आवश्यकता है कि हम हैश की एक सरणी प्राप्त करने के लिए पहले डेटा को हैश करें। ध्यान दें कि यह इसलिए भी है क्योंकि हमें इस बात की परवाह नहीं है कि यूज़र दूसरे यूज़रों की जानकारी जानते हैं। अन्यथा हमें हैश करना पड़ता ताकि यूज़र 1 को यूज़र 0 के लिए मान का पता न चले, यूज़र 2 को यूज़र 3 के लिए मान का पता न चले, इत्यादि।
// स्ट्रिंग जिसे हैश फ़ंक्शन उम्मीद करता है और
// BigInt जिसे हम बाकी हर जगह उपयोग करते हैं, के बीच कन्वर्ट करें।
const hash = (x) =>
BigInt(ethers.utils.keccak256("0x" + x.toString(16).padStart(64, 0)))
ethers हैश फ़ंक्शन को 0x60A7 जैसी हेक्साडेसिमल संख्या के साथ एक जावास्क्रिप्ट स्ट्रिंग प्राप्त करने की उम्मीद है, और यह उसी संरचना के साथ एक और स्ट्रिंग के साथ प्रतिक्रिया करता है। हालांकि, बाकी कोड के लिए BigInt का उपयोग करना आसान है, इसलिए हम एक हेक्साडेसिमल स्ट्रिंग में कनवर्ट करते हैं और फिर वापस।
// एक जोड़ी का सममित हैश ताकि हमें इस बात की परवाह न हो कि क्रम उलटा है।
const pairHash = (a, b) => hash(hash(a) ^ hash(b))
यह फ़ंक्शन सममित है (a xor (opens in a new tab) b का हैश)। इसका मतलब है कि जब हम मर्कल प्रमाण की जाँच करते हैं तो हमें इस बात की चिंता करने की आवश्यकता नहीं है कि प्रमाण से मान को परिकलित मान से पहले रखना है या बाद में। मर्कल प्रमाण की जाँच ऑन-चेन की जाती है, इसलिए हमें वहाँ जितना कम करना पड़े, उतना अच्छा है।
चेतावनी:
क्रिप्टोग्राफी जितनी दिखती है उससे कहीं ज्यादा कठिन है।
इस लेख के शुरुआती संस्करण में हैश फ़ंक्शन hash(a^b) था।
यह एक खराब विचार था क्योंकि इसका मतलब था कि यदि आप a और b के वैध मान जानते हैं तो आप किसी भी वांछित a' मान को साबित करने के लिए b' = a^b^a' का उपयोग कर सकते हैं।
इस फ़ंक्शन के साथ आपको b' की गणना करनी होगी ताकि hash(a') ^ hash(b') एक ज्ञात मान (रूट के रास्ते पर अगली शाखा) के बराबर हो, जो बहुत अधिक कठिन है।
// यह दर्शाने वाला मान कि एक निश्चित शाखा खाली है, उसमें
// कोई मान नहीं है
const empty = 0n
जब मानों की संख्या दो की पूर्णांक घात नहीं होती है तो हमें खाली शाखाओं को संभालना पड़ता है। यह प्रोग्राम इसे करने का तरीका यह है कि यह एक प्लेस होल्डर के रूप में शून्य रखता है।
// एक हैश ऐरे के ट्री में, क्रम में प्रत्येक जोड़ी का हैश लेकर एक स्तर ऊपर की
// गणना करें
const oneLevelUp = (inputArray) => {
var result = []
var inp = [...inputArray] // इनपुट पर लिखने से बचने के लिए // यदि आवश्यक हो तो एक खाली मान जोड़ें (हमें सभी पत्तियों को // जोड़ा बनाना होगा)
if (inp.length % 2 === 1) inp.push(empty)
for (var i = 0; i < inp.length; i += 2)
result.push(pairHash(inp[i], inp[i + 1]))
return result
} // oneLevelUp
यह फ़ंक्शन वर्तमान परत पर मानों के जोड़े को हैश करके मर्कल ट्री में एक स्तर "चढ़ता" है। ध्यान दें कि यह सबसे कुशल कार्यान्वयन नहीं है, हम इनपुट की प्रतिलिपि बनाने से बच सकते थे और लूप में उपयुक्त होने पर बस hashEmpty जोड़ सकते थे, लेकिन यह कोड पठनीयता के लिए अनुकूलित है।
const getMerkleRoot = (inputArray) => {
var result
result = [...inputArray] // पेड़ पर तब तक चढ़ें जब तक कि केवल एक मान न रह जाए, जो कि // रूट है। // // यदि किसी परत में विषम संख्या में प्रविष्टियाँ हैं तो // oneLevelUp में कोड एक खाली मान जोड़ता है, इसलिए यदि हमारे पास, उदाहरण के लिए, // 10 पत्तियाँ हैं तो दूसरी परत में 5 शाखाएँ, तीसरी में 3 // शाखाएँ, चौथी में 2 और रूट पाँचवाँ होगा
while (result.length > 1) result = oneLevelUp(result)
return result[0]
}
रूट प्राप्त करने के लिए, तब तक चढ़ें जब तक केवल एक मान न रह जाए।
मर्कल प्रमाण बनाना
एक मर्कल प्रमाण वे मान हैं जिन्हें मर्कल रूट को वापस पाने के लिए साबित किए जा रहे मान के साथ एक साथ हैश किया जाता है। साबित किया जाने वाला मान अक्सर अन्य डेटा से उपलब्ध होता है, इसलिए मैं इसे कोड के हिस्से के रूप में प्रदान करने के बजाय अलग से प्रदान करना पसंद करता हूँ।
// एक मर्कल प्रमाण में हैश करने के लिए प्रविष्टियों की
// सूची का मान होता है। क्योंकि हम एक सममित हैश फ़ंक्शन का उपयोग करते हैं, इसलिए हमें
// प्रमाण को सत्यापित करने के लिए आइटम के स्थान की आवश्यकता नहीं है, केवल इसे बनाने के लिए
const getMerkleProof = (inputArray, n) => {
var result = [], currentLayer = [...inputArray], currentN = n
// जब तक हम शीर्ष पर नहीं पहुँच जाते
while (currentLayer.length > 1) {
// कोई विषम लंबाई वाली परतें नहीं
if (currentLayer.length % 2)
currentLayer.push(empty)
result.push(currentN % 2
// यदि currentN विषम है, तो प्रमाण में उसके पहले वाले मान को जोड़ें
? currentLayer[currentN-1]
// यदि यह सम है, तो उसके बाद का मान जोड़ें
: currentLayer[currentN+1])
हम (v[0],v[1]), (v[2],v[3]) आदि को हैश करते हैं। इसलिए सम मानों के लिए हमें अगला वाला चाहिए, विषम मानों के लिए पिछला वाला।
// अगली परत पर जाएँ
currentN = Math.floor(currentN/2)
currentLayer = oneLevelUp(currentLayer)
} // while currentLayer.length > 1
return result
} // getMerkleProof
ऑन-चेन कोड
अंत में हमारे पास वह कोड है जो प्रमाण की जाँच करता है। ऑन-चेन कोड सॉलिडिटी (opens in a new tab) में लिखा गया है। अनुकूलन यहाँ बहुत अधिक महत्वपूर्ण है क्योंकि गैस अपेक्षाकृत महंगी है।
//SPDX-License-Identifier: Public Domain
pragma solidity ^0.8.0;
import "hardhat/console.sol";
मैंने इसे हार्डहैट विकास परिवेश (opens in a new tab) का उपयोग करके लिखा है, जो हमें विकास के दौरान सॉलिडिटी से कंसोल आउटपुट (opens in a new tab) प्राप्त करने की अनुमति देता है।
contract MerkleProof {
uint merkleRoot;
function getRoot() public view returns (uint) {
return merkleRoot;
}
// अत्यंत असुरक्षित, प्रोडक्शन कोड में इस तक पहुँच
// फ़ंक्शन को सख्ती से सीमित किया जाना चाहिए, शायद किसी
// स्वामी के लिए
function setRoot(uint _merkleRoot) external {
merkleRoot = _merkleRoot;
} // setRoot
मर्कल रूट के लिए सेट और गेट फ़ंक्शन। उत्पादन प्रणाली में सभी को मर्कल रूट अपडेट करने देना एक अत्यंत बुरा विचार है। मैं इसे यहाँ नमूना कोड के लिए सरलता के लिए करता हूँ। इसे किसी ऐसे सिस्टम पर न करें जहाँ डेटा अखंडता वास्तव में मायने रखती है।
function hash(uint _a) internal pure returns(uint) {
return uint(keccak256(abi.encode(_a)));
}
function pairHash(uint _a, uint _b) internal pure returns(uint) {
return hash(hash(_a) ^ hash(_b));
}
यह फ़ंक्शन एक जोड़ी हैश उत्पन्न करता है। यह hash और pairHash के लिए जावास्क्रिप्ट कोड का केवल सॉलिडिटी अनुवाद है।
नोट: यह पठनीयता के लिए अनुकूलन का एक और मामला है। फ़ंक्शन परिभाषा (opens in a new tab) के आधार पर, डेटा को bytes32 (opens in a new tab) मान के रूप में संग्रहीत करना और रूपांतरणों से बचना संभव हो सकता है।
// एक मर्कल प्रमाण सत्यापित करें
function verifyProof(uint _value, uint[] calldata _proof)
public view returns (bool) {
uint temp = _value;
uint i;
for(i=0; i<_proof.length; i++) {
temp = pairHash(temp, _proof[i]);
}
return temp == merkleRoot;
}
} // MarkleProof
गणितीय अंकन में मर्कल प्रमाण सत्यापन इस तरह दिखता है: H(proof_n, H(proof_n-1, H(proof_n-2, ... H(proof_1, H(proof_0, value))...)))। यह कोड इसे लागू करता है।
मर्कल प्रमाण और रोलअप का मेल नहीं होता
मर्कल प्रमाण रोलअप के साथ अच्छी तरह से काम नहीं करते हैं। इसका कारण यह है कि रोलअप सभी ट्रांजेक्शन डेटा L1 पर लिखते हैं, लेकिन L2 पर प्रोसेस करते हैं। एक ट्रांजेक्शन के साथ मर्कल प्रमाण भेजने की लागत प्रति परत औसतन 638 गैस है (वर्तमान में कॉल डेटा में एक बाइट की लागत 16 गैस है यदि यह शून्य नहीं है, और 4 यदि यह शून्य है)। यदि हमारे पास 1024 शब्दों का डेटा है, तो एक मर्कल प्रमाण के लिए दस परतों की आवश्यकता होती है, या कुल 6380 गैस की।
ऑप्टिमिज़्म (opens in a new tab) को उदाहरण के तौर पर देखें, L1 गैस लिखने पर लगभग 100 gwei और L2 गैस पर 0.001 gwei की लागत आती है (यह सामान्य मूल्य है, यह संकुलन के साथ बढ़ सकता है)। तो एक L1 गैस की कीमत पर हम L2 प्रसंस्करण पर एक लाख गैस खर्च कर सकते हैं। यह मानते हुए कि हम स्टोरेज को ओवरराइट नहीं करते हैं, इसका मतलब है कि हम एक L1 गैस की कीमत के लिए L2 पर स्टोरेज में लगभग पांच शब्द लिख सकते हैं। एकल मर्कल प्रमाण के लिए हम पूरे 1024 शब्दों को स्टोरेज में लिख सकते हैं (यह मानते हुए कि उन्हें शुरू में ऑन-चेन परिकलित किया जा सकता है, बजाय इसके कि एक ट्रांजेक्शन में प्रदान किया जाए) और फिर भी अधिकांश गैस बची रहेगी।
निष्कर्ष
वास्तविक जीवन में आप शायद कभी भी अपने दम पर मर्कल ट्री लागू नहीं करेंगे। ऐसी जानी-मानी और ऑडिट की गई लाइब्रेरी हैं जिनका आप उपयोग कर सकते हैं और सामान्य तौर पर यह सबसे अच्छा है कि आप अपने दम पर क्रिप्टोग्राफिक प्रिमिटिव लागू न करें। लेकिन मुझे उम्मीद है कि अब आप मर्कल प्रमाणों को बेहतर ढंग से समझते हैं और यह तय कर सकते हैं कि उनका उपयोग कब करने लायक है।
ध्यान दें कि जबकि मर्कल प्रमाण अखंडता को संरक्षित करते हैं, वे उपलब्धता को संरक्षित नहीं करते हैं। यह जानना कि कोई और आपकी संपत्ति नहीं ले सकता है, यह एक छोटी सी सांत्वना है यदि डेटा स्टोरेज पहुँच को अस्वीकार करने का फैसला करता है और आप उन तक पहुँचने के लिए मर्कल ट्री का निर्माण भी नहीं कर सकते हैं। इसलिए मर्कल ट्री का सबसे अच्छा उपयोग किसी प्रकार के विकेंद्रीकृत स्टोरेज, जैसे कि आईपीएफएस के साथ किया जाता है।
मेरे और काम के लिए यहाँ देखें (opens in a new tab)।
पेज का अंतिम अपडेट: 3 मार्च 2026


