آف لائن ڈیٹا کی سالمیت کے لیے مرکل (Merkle) پروفز
تعارف
مثالی طور پر ہم ہر چیز کو ایتھیریم اسٹوریج میں اسٹور کرنا چاہیں گے، جو ہزاروں کمپیوٹرز پر اسٹور ہوتا ہے اور اس کی دستیابی انتہائی زیادہ ہوتی ہے (ڈیٹا کو سنسر نہیں کیا جا سکتا) اور سالمیت (ڈیٹا کو غیر مجاز طریقے سے تبدیل نہیں کیا جا سکتا)، لیکن 32 بائٹ کا لفظ اسٹور کرنے پر عام طور پر 20,000 گیس خرچ ہوتی ہے۔ جب میں یہ لکھ رہا ہوں، تو یہ لاگت $6.60 کے برابر ہے۔ 21 سینٹ فی بائٹ کے حساب سے یہ بہت سے استعمالات کے لیے بہت مہنگا ہے۔
اس مسئلے کو حل کرنے کے لیے ایتھیریم ایکو سسٹم نے ڈیٹا کو ڈی سینٹرلائزڈ انداز میں اسٹور کرنے کے کئی متبادل طریقے تیار کیے ہیں۔ عام طور پر ان میں دستیابی اور قیمت کے درمیان سمجھوتہ شامل ہوتا ہے۔ تاہم، سالمیت کی عام طور پر یقین دہانی کرائی جاتی ہے۔
اس مضمون میں آپ سیکھیں گے کہ مرکل پروفز (opens in a new tab) کا استعمال کرتے ہوئے، بلاک چین پر ڈیٹا اسٹور کیے بغیر ڈیٹا کی سالمیت کو کیسے یقینی بنایا جائے۔
یہ کیسے کام کرتا ہے؟
نظریاتی طور پر ہم صرف ڈیٹا کا ہیش آن چین اسٹور کر سکتے ہیں، اور وہ تمام ڈیٹا ان ٹرانزیکشنز میں بھیج سکتے ہیں جنہیں اس کی ضرورت ہوتی ہے۔ تاہم، یہ اب بھی بہت مہنگا ہے۔ ٹرانزیکشن میں ایک بائٹ ڈیٹا کی لاگت تقریباً 16 گیس ہوتی ہے، جو فی الحال تقریباً آدھا سینٹ، یا تقریباً $5 فی کلو بائٹ ہے۔ $5000 فی میگا بائٹ کے حساب سے، یہ اب بھی بہت سے استعمالات کے لیے بہت مہنگا ہے، یہاں تک کہ ڈیٹا کو ہیش کرنے کی اضافی لاگت کے بغیر بھی۔
اس کا حل یہ ہے کہ ڈیٹا کے مختلف ذیلی سیٹس کو بار بار ہیش کیا جائے، تاکہ جس ڈیٹا کو آپ کو بھیجنے کی ضرورت نہیں ہے اس کے لیے آپ صرف ایک ہیش بھیج سکیں۔ آپ یہ ایک مرکل ٹری (Merkle tree) کا استعمال کرتے ہوئے کرتے ہیں، جو ایک ٹری ڈیٹا اسٹرکچر ہے جہاں ہر نوڈ اپنے نیچے والے نوڈز کا ہیش ہوتا ہے:
روٹ ہیش وہ واحد حصہ ہے جسے آن چین اسٹور کرنے کی ضرورت ہوتی ہے۔ کسی خاص ویلیو کو ثابت کرنے کے لیے، آپ وہ تمام ہیشز فراہم کرتے ہیں جنہیں روٹ حاصل کرنے کے لیے اس کے ساتھ ملانے کی ضرورت ہوتی ہے۔ مثال کے طور پر، C کو ثابت کرنے کے لیے آپ D، H(A-B)، اور H(E-H) فراہم کرتے ہیں۔
نفاذ
نمونہ کوڈ یہاں فراہم کیا گیا ہے (opens in a new tab)۔
آف چین کوڈ
اس مضمون میں ہم آف چین کمپیوٹیشنز کے لیے JavaScript کا استعمال کرتے ہیں۔ زیادہ تر ڈی سینٹرلائزڈ ایپلی کیشنز کا آف چین جزو JavaScript میں ہوتا ہے۔
مرکل روٹ بنانا
سب سے پہلے ہمیں چین کو مرکل روٹ فراہم کرنے کی ضرورت ہے۔
1const ethers = require("ethers")ہم ethers پیکیج سے ہیش فنکشن استعمال کرتے ہیں (opens in a new tab)۔
1// وہ خام ڈیٹا جس کی سالمیت کی ہمیں تصدیق کرنی ہے۔ پہلے دو بائٹس2// ایک صارف شناخت کنندہ ہیں، اور آخری دو بائٹس ان ٹوکنز کی مقدار ہیں جو3// اس وقت صارف کی ملکیت میں ہیں۔4const dataArray = [5 0x0bad0010, 0x60a70020, 0xbeef0030, 0xdead0040, 0xca110050, 0x0e660060,6 0xface0070, 0xbad00080, 0x060d0091,7]ہر اندراج کو ایک واحد 256-بٹ انٹیجر میں انکوڈ کرنے کے نتیجے میں، مثال کے طور پر، JSON استعمال کرنے کی نسبت کم پڑھنے کے قابل کوڈ ملتا ہے۔ تاہم، اس کا مطلب ہے کہ کنٹریکٹ میں ڈیٹا بازیافت کرنے کے لیے نمایاں طور پر کم پروسیسنگ درکار ہے، اس لیے گیس کی لاگت بہت کم ہوتی ہے۔ آپ آن چین JSON پڑھ سکتے ہیں (opens in a new tab)، اگر اس سے بچا جا سکے تو یہ صرف ایک برا خیال ہے۔
1// ہیش ویلیوز کی ایرے، BigInts کے طور پر2const hashArray = dataArrayاس صورت میں ہمارا ڈیٹا شروع سے ہی 256-بٹ ویلیوز پر مشتمل ہے، اس لیے کسی پروسیسنگ کی ضرورت نہیں ہے۔ اگر ہم زیادہ پیچیدہ ڈیٹا اسٹرکچر استعمال کرتے ہیں، جیسے کہ اسٹرنگز، تو ہمیں یہ یقینی بنانا ہوگا کہ ہم ہیشز کی ایک سرنی (array) حاصل کرنے کے لیے پہلے ڈیٹا کو ہیش کریں۔ نوٹ کریں کہ یہ اس لیے بھی ہے کہ ہمیں اس بات کی پرواہ نہیں ہے کہ آیا صارفین دوسرے صارفین کی معلومات جانتے ہیں۔ بصورت دیگر ہمیں ہیش کرنا پڑتا تاکہ صارف 1 کو صارف 0 کی ویلیو معلوم نہ ہو، صارف 2 کو صارف 3 کی ویلیو معلوم نہ ہو، وغیرہ۔
1// اس سٹرنگ جس کی ہیش فنکشن توقع کرتا ہے اور اس2// BigInt کے درمیان تبدیل کریں جسے ہم ہر دوسری جگہ استعمال کرتے ہیں۔3const hash = (x) =>4 BigInt(ethers.utils.keccak256("0x" + x.toString(16).padStart(64, 0)))ethers ہیش فنکشن کو ہیکسا ڈیسیمل نمبر کے ساتھ ایک JavaScript اسٹرنگ ملنے کی توقع ہوتی ہے، جیسے کہ 0x60A7، اور یہ اسی ساخت کے ساتھ ایک اور اسٹرنگ کے ساتھ جواب دیتا ہے۔ تاہم، باقی کوڈ کے لیے BigInt استعمال کرنا آسان ہے، اس لیے ہم اسے ہیکسا ڈیسیمل اسٹرنگ میں تبدیل کرتے ہیں اور پھر واپس لاتے ہیں۔
1// ایک جوڑے کا سڈول (symmetrical) ہیش تاکہ اگر ترتیب الٹ بھی جائے تو ہمیں کوئی فرق نہ پڑے۔2const pairHash = (a, b) => hash(hash(a) ^ hash(b))یہ فنکشن سڈول (symmetrical) ہے (a xor (opens in a new tab) b کا ہیش)۔ اس کا مطلب یہ ہے کہ جب ہم مرکل پروف چیک کرتے ہیں تو ہمیں اس بات کی فکر کرنے کی ضرورت نہیں ہوتی کہ پروف سے حاصل کردہ ویلیو کو کیلکولیٹڈ ویلیو سے پہلے رکھنا ہے یا بعد میں۔ مرکل پروف کی جانچ آن چین کی جاتی ہے، اس لیے ہمیں وہاں جتنا کم کام کرنا پڑے اتنا ہی بہتر ہے۔
انتباہ:
کرپٹوگرافی دیکھنے میں جتنی آسان لگتی ہے اس سے کہیں زیادہ مشکل ہے۔
اس مضمون کے ابتدائی ورژن میں ہیش فنکشن hash(a^b) تھا۔
یہ ایک برا خیال تھا کیونکہ اس کا مطلب یہ تھا کہ اگر آپ کو a اور b کی جائز ویلیوز معلوم ہوں تو آپ کسی بھی مطلوبہ a' ویلیو کو ثابت کرنے کے لیے b' = a^b^a' استعمال کر سکتے ہیں۔
اس فنکشن کے ساتھ آپ کو b' کا حساب اس طرح لگانا ہوگا کہ hash(a') ^ hash(b') ایک معلوم ویلیو (روٹ کے راستے پر اگلی برانچ) کے برابر ہو، جو کہ بہت زیادہ مشکل ہے۔
1// وہ ویلیو جو یہ ظاہر کرتی ہے کہ ایک مخصوص برانچ خالی ہے، اس میں2// کوئی ویلیو نہیں ہے3const empty = 0nجب ویلیوز کی تعداد دو کی انٹیجر پاور نہیں ہوتی ہے تو ہمیں خالی برانچز کو سنبھالنے کی ضرورت ہوتی ہے۔ یہ پروگرام جس طرح سے یہ کرتا ہے وہ یہ ہے کہ پلیس ہولڈر کے طور پر صفر رکھ دیتا ہے۔
1// ہیش ایرے کے ٹری میں ایک لیول اوپر کا حساب لگائیں، جس کے لیے2// ہر جوڑے کا ترتیب وار ہیش لیا جاتا ہے3const oneLevelUp = (inputArray) => {4 var result = []5 var inp = [...inputArray] // ان پٹ کو اوور رائٹ کرنے سے بچنے کے لیے // اگر ضروری ہو تو ایک خالی ویلیو شامل کریں (ہمیں تمام لیوز کو // جوڑے کی شکل میں رکھنے کی ضرورت ہے)6
7 if (inp.length % 2 === 1) inp.push(empty)8
9 for (var i = 0; i < inp.length; i += 2)10 result.push(pairHash(inp[i], inp[i + 1]))11
12 return result13} // oneLevelUpیہ فنکشن موجودہ لیئر پر ویلیوز کے جوڑوں کو ہیش کر کے مرکل ٹری میں ایک لیول "اوپر چڑھتا" ہے۔ نوٹ کریں کہ یہ سب سے زیادہ موثر نفاذ نہیں ہے، ہم ان پٹ کو کاپی کرنے سے بچ سکتے تھے اور لوپ میں مناسب ہونے پر صرف hashEmpty شامل کر سکتے تھے، لیکن یہ کوڈ پڑھنے کی اہلیت کے لیے آپٹمائز کیا گیا ہے۔
1const getMerkleRoot = (inputArray) => {2 var result3
4 result = [...inputArray] // ٹری میں اوپر جائیں جب تک کہ صرف ایک ویلیو نہ بچ جائے، جو کہ // روٹ ہے۔ // // اگر کسی لیئر میں اندراجات کی تعداد طاق (odd) ہے تو // oneLevelUp کا کوڈ ایک خالی ویلیو شامل کر دیتا ہے، لہذا اگر ہمارے پاس، مثال کے طور پر، // 10 لیوز ہیں تو دوسری لیئر میں 5 برانچز ہوں گی، تیسری میں 3 // برانچز، چوتھی میں 2 اور روٹ پانچویں ہوگی5
6 while (result.length > 1) result = oneLevelUp(result)7
8 return result[0]9}روٹ حاصل کرنے کے لیے، اس وقت تک اوپر چڑھتے رہیں جب تک کہ صرف ایک ویلیو باقی نہ رہ جائے۔
مرکل پروف بنانا
مرکل پروف وہ ویلیوز ہیں جنہیں ثابت کی جانے والی ویلیو کے ساتھ ہیش کیا جاتا ہے تاکہ مرکل روٹ واپس حاصل کیا جا سکے۔ ثابت کرنے والی ویلیو اکثر دوسرے ڈیٹا سے دستیاب ہوتی ہے، اس لیے میں اسے کوڈ کے حصے کے بجائے الگ سے فراہم کرنے کو ترجیح دیتا ہوں۔
1// ایک مرکل پروف ان اندراجات کی فہرست کی ویلیو پر مشتمل ہوتا ہے جن کے ساتھ2// ہیش کرنا ہے۔ چونکہ ہم ایک سڈول ہیش فنکشن استعمال کرتے ہیں، اس لیے ہمیں3// پروف کی تصدیق کے لیے آئٹم کی لوکیشن کی ضرورت نہیں ہوتی، صرف اسے بنانے کے لیے ہوتی ہے4const getMerkleProof = (inputArray, n) => {5 var result = [], currentLayer = [...inputArray], currentN = n6
7 // جب تک ہم سب سے اوپر نہ پہنچ جائیں8 while (currentLayer.length > 1) {9 // کوئی طاق (odd) لمبائی والی لیئرز نہیں10 if (currentLayer.length % 2)11 currentLayer.push(empty)12
13 result.push(currentN % 214 // اگر currentN طاق (odd) ہے، تو اس سے پہلے والی ویلیو کے ساتھ پروف میں شامل کریں15 ? currentLayer[currentN-1]16 // اگر یہ جفت (even) ہے، تو اس کے بعد والی ویلیو شامل کریں17 : currentLayer[currentN+1])18
ہم (v[0],v[1])، (v[2],v[3])، وغیرہ کو ہیش کرتے ہیں۔ لہذا جفت (even) ویلیوز کے لیے ہمیں اگلی ویلیو کی ضرورت ہوتی ہے، طاق (odd) ویلیوز کے لیے پچھلی ویلیو کی۔
1 // اگلی اوپر والی لیئر پر جائیں2 currentN = Math.floor(currentN/2)3 currentLayer = oneLevelUp(currentLayer)4 } // while currentLayer.length > 15
6 return result7} // getMerkleProofآن چین کوڈ
آخر کار ہمارے پاس وہ کوڈ ہے جو پروف کو چیک کرتا ہے۔ آن چین کوڈ Solidity (opens in a new tab) میں لکھا گیا ہے۔ یہاں آپٹمائزیشن بہت زیادہ اہم ہے کیونکہ گیس نسبتاً مہنگی ہے۔
1// SPDX-License-Identifier: Public Domain2pragma solidity ^0.8.0;3
4import "hardhat/console.sol";میں نے اسے Hardhat ڈیولپمنٹ انوائرنمنٹ (opens in a new tab) کا استعمال کرتے ہوئے لکھا ہے، جو ہمیں ڈیولپمنٹ کے دوران Solidity سے کنسول آؤٹ پٹ (opens in a new tab) حاصل کرنے کی اجازت دیتا ہے۔
1
2contract MerkleProof {3 uint merkleRoot;4
5 function getRoot() public view returns (uint) {6 return merkleRoot;7 }8
9 // انتہائی غیر محفوظ، پروڈکشن کوڈ میں رسائی10 // اس فنکشن تک سختی سے محدود ہونی چاہیے، غالباً کسی11 // مالک تک12 function setRoot(uint _merkleRoot) external {13 merkleRoot = _merkleRoot;14 } // setRootمرکل روٹ کے لیے set اور get فنکشنز۔ پروڈکشن سسٹم میں ہر کسی کو مرکل روٹ اپ ڈیٹ کرنے کی اجازت دینا ایک انتہائی برا خیال ہے۔ میں یہاں نمونہ کوڈ کی سادگی کی خاطر ایسا کرتا ہوں۔ ایسے سسٹم پر ایسا نہ کریں جہاں ڈیٹا کی سالمیت واقعی اہمیت رکھتی ہو۔
1 function hash(uint _a) internal pure returns(uint) {2 return uint(keccak256(abi.encode(_a)));3 }4
5 function pairHash(uint _a, uint _b) internal pure returns(uint) {6 return hash(hash(_a) ^ hash(_b));7 }یہ فنکشن ایک پیئر ہیش (pair hash) تیار کرتا ہے۔ یہ صرف hash اور pairHash کے لیے JavaScript کوڈ کا Solidity ترجمہ ہے۔
نوٹ: یہ پڑھنے کی اہلیت کے لیے آپٹمائزیشن کا ایک اور معاملہ ہے۔ فنکشن کی تعریف (opens in a new tab) کی بنیاد پر، ڈیٹا کو bytes32 (opens in a new tab) ویلیو کے طور پر اسٹور کرنا اور تبادلوں سے بچنا ممکن ہو سکتا ہے۔
1 // ایک مرکل پروف (Merkle proof) کی تصدیق کریں2 function verifyProof(uint _value, uint[] calldata _proof)3 public view returns (bool) {4 uint temp = _value;5 uint i;6
7 for(i=0; i<_proof.length; i++) {8 temp = pairHash(temp, _proof[i]);9 }10
11 return temp == merkleRoot;12 }13
14} // MarkleProofریاضیاتی اشارے (mathematical notation) میں مرکل پروف کی تصدیق اس طرح نظر آتی ہے: H(proof_n, H(proof_n-1, H(proof_n-2, ... H(proof_1, H(proof_0, value))...)))۔ یہ کوڈ اسے نافذ کرتا ہے۔
مرکل پروفز اور رول اپس آپس میں نہیں ملتے
مرکل پروفز رول اپس (rollups) کے ساتھ اچھی طرح کام نہیں کرتے ہیں۔ اس کی وجہ یہ ہے کہ رول اپس تمام ٹرانزیکشن ڈیٹا L1 پر لکھتے ہیں، لیکن L2 پر پروسیس کرتے ہیں۔ ٹرانزیکشن کے ساتھ مرکل پروف بھیجنے کی لاگت اوسطاً 638 گیس فی لیئر ہوتی ہے (فی الحال کال ڈیٹا میں ایک بائٹ کی لاگت 16 گیس ہوتی ہے اگر یہ صفر نہ ہو، اور 4 اگر یہ صفر ہو)۔ اگر ہمارے پاس ڈیٹا کے 1024 الفاظ ہیں، تو ایک مرکل پروف کے لیے دس لیئرز، یا کل 6380 گیس درکار ہوتی ہے۔
مثال کے طور پر Optimism (opens in a new tab) کو دیکھیں، L1 گیس لکھنے کی لاگت تقریباً 100 gwei اور L2 گیس کی لاگت 0.001 gwei ہے (یہ عام قیمت ہے، یہ ہجوم کے ساتھ بڑھ سکتی ہے)۔ لہذا ایک L1 گیس کی قیمت کے لیے ہم L2 پروسیسنگ پر ایک لاکھ گیس خرچ کر سکتے ہیں۔ یہ فرض کرتے ہوئے کہ ہم اسٹوریج کو اوور رائٹ نہیں کرتے ہیں، اس کا مطلب یہ ہے کہ ہم ایک L1 گیس کی قیمت پر L2 پر اسٹوریج میں تقریباً پانچ الفاظ لکھ سکتے ہیں۔ ایک واحد مرکل پروف کے لیے ہم پورے 1024 الفاظ اسٹوریج میں لکھ سکتے ہیں (یہ فرض کرتے ہوئے کہ ان کا حساب شروع سے ہی آن چین لگایا جا سکتا ہے، بجائے اس کے کہ ٹرانزیکشن میں فراہم کیا جائے) اور پھر بھی زیادہ تر گیس بچ جائے گی۔
نتیجہ
حقیقی زندگی میں آپ شاید کبھی بھی خود سے مرکل ٹریز نافذ نہ کریں۔ ایسی مشہور اور آڈٹ شدہ لائبریریاں موجود ہیں جنہیں آپ استعمال کر سکتے ہیں اور عام طور پر یہ بہتر ہے کہ کرپٹوگرافک پرائمیٹوز کو خود سے نافذ نہ کریں۔ لیکن مجھے امید ہے کہ اب آپ مرکل پروفز کو بہتر طور پر سمجھ گئے ہوں گے اور فیصلہ کر سکتے ہیں کہ انہیں کب استعمال کرنا فائدہ مند ہے۔
نوٹ کریں کہ اگرچہ مرکل پروفز سالمیت کو محفوظ رکھتے ہیں، لیکن وہ دستیابی کو محفوظ نہیں رکھتے۔ یہ جاننا کہ کوئی اور آپ کے اثاثے نہیں لے سکتا، ایک چھوٹی سی تسلی ہے اگر ڈیٹا اسٹوریج رسائی کی اجازت نہ دینے کا فیصلہ کرے اور آپ ان تک رسائی کے لیے مرکل ٹری بھی نہ بنا سکیں۔ لہذا مرکل ٹریز کو کسی قسم کے ڈی سینٹرلائزڈ اسٹوریج، جیسے کہ IPFS کے ساتھ استعمال کرنا بہترین ہے۔
میرے مزید کام کے لیے یہاں دیکھیں (opens in a new tab)۔
صفحہ کی آخری اپ ڈیٹ: ۳ مارچ، ۲۰۲۶


