ஆஃப்லைன் தரவு ஒருமைப்பாட்டிற்கான மெர்க்கல் சான்றுகள்
அறிமுகம்
எத்தேரியம் சேமிப்பகத்தில் அனைத்தையும் சேமிக்கவே நாம் விரும்புவோம், இது ஆயிரக்கணக்கான கணினிகளில் சேமிக்கப்படுகிறது மற்றும் மிக அதிக கிடைக்கும் தன்மையையும் (தரவை தணிக்கை செய்ய முடியாது) ஒருமைப்பாட்டையும் (தரவை அங்கீகரிக்கப்படாத முறையில் மாற்ற முடியாது) கொண்டுள்ளது, ஆனால் 32-பைட் சொல்லைச் சேமிக்க பொதுவாக 20,000 கேஸ் (gas) செலவாகும். நான் இதை எழுதும் போது, அந்த செலவு $6.60 க்கு சமம். ஒரு பைட்டுக்கு 21 சென்ட்கள் என்பது பல பயன்பாடுகளுக்கு மிகவும் விலை உயர்ந்தது.
இந்தச் சிக்கலைத் தீர்க்க, எத்தேரியம் சுற்றுச்சூழல் அமைப்பு பரவலாக்கப்பட்ட முறையில் தரவைச் சேமிக்க பல மாற்று வழிகளை உருவாக்கியுள்ளது. பொதுவாக அவை கிடைக்கும் தன்மை மற்றும் விலை ஆகியவற்றுக்கு இடையேயான சமரசத்தை உள்ளடக்கியது. இருப்பினும், ஒருமைப்பாடு பொதுவாக உறுதி செய்யப்படுகிறது.
இந்த கட்டுரையில், மெர்க்கல் சான்றுகளைப் (Merkle proofs) (opens in a new tab) பயன்படுத்தி, பிளாக்செயினில் தரவைச் சேமிக்காமல் தரவு ஒருமைப்பாட்டை எப்படி உறுதி செய்வது என்பதை நீங்கள் கற்றுக் கொள்வீர்கள்.
இது எப்படி வேலை செய்கிறது?
கோட்பாட்டளவில், நாம் தரவின் ஹாஷை (hash) ஆன்செயினில் சேமிக்கலாம், மேலும் தேவைப்படும் பரிவர்த்தனைகளில் அனைத்து தரவையும் அனுப்பலாம். இருப்பினும், இது இன்னும் மிகவும் விலை உயர்ந்தது. ஒரு பரிவர்த்தனைக்கு ஒரு பைட் தரவுக்கு சுமார் 16 கேஸ் செலவாகும், தற்போது இது அரை சென்ட் அல்லது ஒரு கிலோபைட்டுக்கு சுமார் $5 ஆகும். ஒரு மெகாபைட்டுக்கு $5000 என்பது, தரவை ஹாஷ் செய்வதற்கான கூடுதல் செலவு இல்லாவிட்டாலும், பல பயன்பாடுகளுக்கு மிகவும் விலை உயர்ந்தது.
தரவின் வெவ்வேறு துணைக்குழுக்களை மீண்டும் மீண்டும் ஹாஷ் செய்வதே இதற்கான தீர்வாகும், எனவே நீங்கள் அனுப்பத் தேவையில்லாத தரவுகளுக்கு ஒரு ஹாஷை மட்டும் அனுப்பலாம். மெர்க்கல் ட்ரீ (Merkle tree) எனப்படும் மரத் தரவு அமைப்பைப் பயன்படுத்தி இதைச் செய்கிறீர்கள், இதில் ஒவ்வொரு முனையும் (node) அதன் கீழே உள்ள முனைகளின் ஹாஷ் ஆகும்:
ரூட் ஹாஷ் (root hash) மட்டுமே ஆன்செயினில் சேமிக்கப்பட வேண்டிய பகுதியாகும். ஒரு குறிப்பிட்ட மதிப்பை நிரூபிக்க, ரூட்டைப் பெற அதனுடன் இணைக்கப்பட வேண்டிய அனைத்து ஹாஷ்களையும் நீங்கள் வழங்குகிறீர்கள். எடுத்துக்காட்டாக, C ஐ நிரூபிக்க நீங்கள் D, H(A-B) மற்றும் H(E-H) ஆகியவற்றை வழங்குகிறீர்கள்.
செயல்படுத்தல்
மாதிரி குறியீடு இங்கே வழங்கப்பட்டுள்ளது (opens in a new tab).
ஆஃப்செயின் குறியீடு
இந்தக் கட்டுரையில் ஆஃப்செயின் கணக்கீடுகளுக்கு JavaScript ஐப் பயன்படுத்துகிறோம். பெரும்பாலான பரவலாக்கப்பட்ட பயன்பாடுகள் அவற்றின் ஆஃப்செயின் கூறுகளை JavaScript இல் கொண்டுள்ளன.
மெர்க்கல் ரூட்டை உருவாக்குதல்
முதலில் நாம் செயினுக்கு மெர்க்கல் ரூட்டை வழங்க வேண்டும்.
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), ஆனால் தவிர்க்க முடிந்தால் அது ஒரு மோசமான யோசனையாகும்.
// BigInt-களாக உள்ள ஹாஷ் மதிப்புகளின் வரிசை
const hashArray = dataArray
இந்த நிலையில், தொடங்குவதற்கு நமது தரவு 256-பிட் மதிப்புகளாகும், எனவே எந்த செயலாக்கமும் தேவையில்லை. சரங்கள் (strings) போன்ற சிக்கலான தரவு அமைப்பைப் பயன்படுத்தினால், ஹாஷ்களின் வரிசையைப் பெற முதலில் தரவை ஹாஷ் செய்வதை உறுதிசெய்ய வேண்டும். பயனர்கள் மற்ற பயனர்களின் தகவல்களைத் தெரிந்துகொள்வதைப் பற்றி நாங்கள் கவலைப்படாததும் இதற்குக் காரணம் என்பதை நினைவில் கொள்க. இல்லையெனில், பயனர் 0 இன் மதிப்பை பயனர் 1 அறியாதபடியும், பயனர் 3 இன் மதிப்பை பயனர் 2 அறியாதபடியும் நாம் ஹாஷ் செய்திருக்க வேண்டும்.
// ஹாஷ் சார்பு எதிர்பார்க்கும் சரத்திற்கும் மற்றும்
// நாம் மற்ற எல்லா இடங்களிலும் பயன்படுத்தும் BigInt-க்கும் இடையே மாற்றவும்.
const hash = (x) =>
BigInt(ethers.utils.keccak256("0x" + x.toString(16).padStart(64, 0)))
ethers ஹாஷ் செயல்பாடு 0x60A7 போன்ற ஹெக்ஸாடெசிமல் எண்ணைக் கொண்ட JavaScript சரத்தைப் பெற எதிர்பார்க்கிறது, மேலும் அதே அமைப்பைக் கொண்ட மற்றொரு சரத்துடன் பதிலளிக்கிறது. இருப்பினும், மீதமுள்ள குறியீட்டிற்கு 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' ஐப் பயன்படுத்தலாம்.
இந்தச் செயல்பாட்டின் மூலம், hash(a') ^ hash(b') என்பது தெரிந்த மதிப்பிற்கு (ரூட்டிற்குச் செல்லும் வழியில் உள்ள அடுத்த கிளை) சமமாக இருக்கும்படி b' ஐ நீங்கள் கணக்கிட வேண்டும், இது மிகவும் கடினமானது.
// ஒரு குறிப்பிட்ட கிளை காலியாக உள்ளது என்பதைக் குறிக்கும் மதிப்பு, இதில்
// எந்த மதிப்பும் இல்லை
const empty = 0n
மதிப்புகளின் எண்ணிக்கை இரண்டின் முழு எண் அடுக்காக இல்லாதபோது, நாம் காலியான கிளைகளைக் கையாள வேண்டும். இந்த நிரல் அதைச் செய்யும் விதம், பூஜ்ஜியத்தை ஒரு இடநிரப்பியாக (place holder) வைப்பதாகும்.
// ஒரு ஹாஷ் வரிசையின் மரத்தில் ஒரு நிலை மேலே கணக்கிட,
// ஒவ்வொரு ஜோடியின் ஹாஷையும் வரிசையாக எடுக்கவும்
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] // ஒரே ஒரு மதிப்பு (அதாவது வேர் - root) இருக்கும் வரை மரத்தின் மேலே செல்லவும். // // ஒரு அடுக்கில் ஒற்றைப்படை எண்ணிக்கையிலான உள்ளீடுகள் இருந்தால், // 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
ஆன்செயின் குறியீடு
இறுதியாக சான்றைச் சரிபார்க்கும் குறியீடு நம்மிடம் உள்ளது. ஆன்செயின் குறியீடு Solidity (opens in a new tab) இல் எழுதப்பட்டுள்ளது. கேஸ் ஒப்பீட்டளவில் விலை உயர்ந்தது என்பதால், உகப்பாக்கம் (Optimization) இங்கே மிகவும் முக்கியமானது.
// SPDX-License-Identifier: Public Domain
pragma solidity ^0.8.0;
import "hardhat/console.sol";
இதை நான் Hardhat மேம்பாட்டு சூழலைப் (opens in a new tab) பயன்படுத்தி எழுதினேன், இது உருவாக்கும் போது Solidity இலிருந்து கன்சோல் வெளியீட்டைப் (opens in a new tab) பெற அனுமதிக்கிறது.
contract MerkleProof {
uint merkleRoot;
function getRoot() public view returns (uint) {
return merkleRoot;
}
// மிகவும் பாதுகாப்பற்றது, தயாரிப்பு குறியீட்டில் (production code)
// இந்தச் சார்புக்கான அணுகல் கண்டிப்பாகக் கட்டுப்படுத்தப்பட வேண்டும், அநேகமாக ஒரு
// உரிமையாளருக்கு (owner) மட்டுமே
function setRoot(uint _merkleRoot) external {
merkleRoot = _merkleRoot;
} // setRoot
மெர்க்கல் ரூட்டிற்கான செட் (Set) மற்றும் கெட் (get) செயல்பாடுகள். தயாரிப்பு அமைப்பில் (production system) மெர்க்கல் ரூட்டைப் புதுப்பிக்க அனைவரையும் அனுமதிப்பது மிகவும் மோசமான யோசனையாகும். மாதிரி குறியீட்டிற்கான எளிமைக்காக நான் இதை இங்கே செய்கிறேன். தரவு ஒருமைப்பாடு உண்மையில் முக்கியமான ஒரு அமைப்பில் இதைச் செய்ய வேண்டாம்.
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 க்கான JavaScript குறியீட்டின் Solidity மொழிபெயர்ப்பு மட்டுமே.
குறிப்பு: இது வாசிப்புத்திறனுக்கான உகப்பாக்கத்தின் மற்றொரு நிகழ்வாகும். செயல்பாட்டு வரையறையின் (opens in a new tab) அடிப்படையில், தரவை bytes32 (opens in a new tab) மதிப்பாகச் சேமித்து மாற்றங்களைத் தவிர்க்க முடியும்.
// ஒரு மெர்க்கல் ப்ரூஃபை (Merkle proof) சரிபார்க்கவும்
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))...))). இந்தக் குறியீடு அதைச் செயல்படுத்துகிறது.
மெர்க்கல் சான்றுகளும் ரோலப்களும் ஒன்றிணைவதில்லை
மெர்க்கல் சான்றுகள் ரோலப்களுடன் (rollups) நன்றாக வேலை செய்யாது. காரணம், ரோலப்கள் அனைத்து பரிவர்த்தனை தரவையும் L1 இல் எழுதுகின்றன, ஆனால் L2 இல் செயலாக்குகின்றன. ஒரு பரிவர்த்தனையுடன் மெர்க்கல் சான்றை அனுப்புவதற்கான செலவு ஒரு அடுக்கிற்கு சராசரியாக 638 கேஸ் ஆகும் (தற்போது கால் டேட்டாவில் (call data) உள்ள ஒரு பைட் பூஜ்ஜியமாக இல்லாவிட்டால் 16 கேஸ், மற்றும் பூஜ்ஜியமாக இருந்தால் 4 கேஸ் செலவாகும்). நம்மிடம் 1024 சொற்கள் தரவு இருந்தால், ஒரு மெர்க்கல் சான்றுக்கு பத்து அடுக்குகள் அல்லது மொத்தம் 6380 கேஸ் தேவைப்படும்.
எடுத்துக்காட்டாக Optimism (opens in a new tab) ஐப் பார்த்தால், L1 கேஸ் எழுதுவதற்கு சுமார் 100 gwei செலவாகும் மற்றும் L2 கேஸ் 0.001 gwei செலவாகும் (அது சாதாரண விலை, நெரிசலுடன் அது உயரலாம்). எனவே ஒரு L1 கேஸின் விலைக்கு நாம் L2 செயலாக்கத்தில் ஒரு லட்சம் கேஸைச் செலவிடலாம். சேமிப்பகத்தை நாம் மேலெழுதவில்லை (overwrite) என்று வைத்துக்கொண்டால், ஒரு L1 கேஸின் விலைக்கு L2 இல் உள்ள சேமிப்பகத்தில் சுமார் ஐந்து சொற்களை எழுதலாம் என்று அர்த்தம். ஒரு மெர்க்கல் சான்றுக்கு நாம் முழு 1024 சொற்களையும் சேமிப்பகத்தில் எழுதலாம் (பரிவர்த்தனையில் வழங்கப்படுவதற்குப் பதிலாக, தொடங்குவதற்கு அவற்றை ஆன்செயினில் கணக்கிட முடியும் என்று வைத்துக்கொள்வோம்) மற்றும் இன்னும் பெரும்பாலான கேஸ் மீதமிருக்கும்.
முடிவுரை
நிஜ வாழ்க்கையில் நீங்கள் ஒருபோதும் மெர்க்கல் ட்ரீகளை நீங்களாகவே செயல்படுத்த மாட்டீர்கள். நீங்கள் பயன்படுத்தக்கூடிய நன்கு அறியப்பட்ட மற்றும் தணிக்கை செய்யப்பட்ட நூலகங்கள் (libraries) உள்ளன, பொதுவாகச் சொல்வதானால், கிரிப்டோகிராஃபிக் ப்ரிமிட்டிவ்களை (cryptographic primitives) நீங்களாகவே செயல்படுத்தாமல் இருப்பது நல்லது. ஆனால் இப்போது நீங்கள் மெர்க்கல் சான்றுகளைப் பற்றி நன்றாகப் புரிந்துகொண்டிருப்பீர்கள் என்றும், அவற்றைப் பயன்படுத்துவது எப்போது மதிப்புமிக்கது என்பதை உங்களால் தீர்மானிக்க முடியும் என்றும் நம்புகிறேன்.
மெர்க்கல் சான்றுகள் ஒருமைப்பாட்டைப் பாதுகாத்தாலும், அவை கிடைக்கும் தன்மையைப் பாதுகாப்பதில்லை என்பதை நினைவில் கொள்க. தரவுச் சேமிப்பகம் அணுகலை அனுமதிக்க வேண்டாம் என்று முடிவு செய்தால், அவற்றை அணுகுவதற்கு உங்களால் மெர்க்கல் ட்ரீயை உருவாக்க முடியாவிட்டால், வேறு யாரும் உங்கள் சொத்துக்களை எடுக்க முடியாது என்பதை அறிவது ஒரு சிறிய ஆறுதல் மட்டுமே. எனவே IPFS போன்ற சில வகையான பரவலாக்கப்பட்ட சேமிப்பகத்துடன் மெர்க்கல் ட்ரீகளைப் பயன்படுத்துவது சிறந்தது.
எனது மேலும் பல படைப்புகளுக்கு இங்கே பார்க்கவும் (opens in a new tab).
பக்கம் கடைசியாகப் புதுப்பிக்கப்பட்டது: 3 மார்ச், 2026


