सरल क्रमांकन
सरल क्रमांकन (SSZ) बीकन चेन पर उपयोग की जाने वाली क्रमांकन विधि है। यह पीयर खोज प्रोटोकॉल को छोड़कर सर्वसम्मति परत में हर जगह निष्पादन परत पर उपयोग किए जाने वाले RLP क्रमांकन को प्रतिस्थापित करता है। RLP क्रमांकन के बारे में अधिक जानने के लिए, रिकर्सिव-लेंथ प्रीफिक्स (RLP) देखें। SSZ को नियतात्मक (deterministic) होने और कुशलतापूर्वक मर्कलाइज़ (Merkleize) करने के लिए डिज़ाइन किया गया है। SSZ को दो घटकों वाला माना जा सकता है: एक क्रमांकन योजना और एक मर्कलाइज़ेशन योजना जिसे क्रमांकित डेटा संरचना के साथ कुशलतापूर्वक काम करने के लिए डिज़ाइन किया गया है।
SSZ कैसे काम करता है?
क्रमांकन
SSZ एक क्रमांकन योजना है जो स्व-वर्णनात्मक (self-describing) नहीं है - बल्कि यह एक स्कीमा पर निर्भर करती है जिसे पहले से ज्ञात होना चाहिए। SSZ क्रमांकन का लक्ष्य मनमानी जटिलता वाली वस्तुओं को बाइट्स की स्ट्रिंग के रूप में दर्शाना है। "बुनियादी प्रकारों" (basic types) के लिए यह एक बहुत ही सरल प्रक्रिया है। तत्व को बस हेक्साडेसिमल बाइट्स में बदल दिया जाता है। बुनियादी प्रकारों में शामिल हैं:
- अनसाइन्ड इंटिजर्स (unsigned integers)
- बूलियन (Booleans)
जटिल "समग्र" (composite) प्रकारों के लिए, क्रमांकन अधिक जटिल होता है क्योंकि समग्र प्रकार में कई तत्व होते हैं जिनके प्रकार या आकार अलग-अलग हो सकते हैं, या दोनों। जहां इन सभी वस्तुओं की लंबाई निश्चित होती है (यानी, तत्वों का आकार उनके वास्तविक मूल्यों की परवाह किए बिना हमेशा स्थिर रहने वाला है), क्रमांकन केवल समग्र प्रकार के प्रत्येक तत्व को लिटिल-एंडियन (little-endian) बाइटस्ट्रिंग में क्रमबद्ध करने का एक रूपांतरण है। इन बाइटस्ट्रिंग्स को एक साथ जोड़ा जाता है। क्रमांकित वस्तु में निश्चित-लंबाई वाले तत्वों का बाइटलिस्ट प्रतिनिधित्व उसी क्रम में होता है जिस क्रम में वे अक्रमांकित (deserialized) वस्तु में दिखाई देते हैं।
परिवर्तनीय लंबाई वाले प्रकारों के लिए, वास्तविक डेटा को क्रमांकित वस्तु में उस तत्व की स्थिति में "ऑफ़सेट" (offset) मान से बदल दिया जाता है। वास्तविक डेटा को क्रमांकित वस्तु के अंत में एक हीप (heap) में जोड़ दिया जाता है। ऑफ़सेट मान हीप में वास्तविक डेटा की शुरुआत के लिए सूचकांक है, जो प्रासंगिक बाइट्स के लिए एक पॉइंटर के रूप में कार्य करता है।
नीचे दिया गया उदाहरण स्पष्ट करता है कि निश्चित और परिवर्तनीय-लंबाई वाले दोनों तत्वों वाले कंटेनर के लिए ऑफ़सेटिंग कैसे काम करती है:
struct Dummy {
number1: u64,
number2: u64,
vector: Vec<u8>,
number3: u64
}
dummy = Dummy{
number1: 37,
number2: 55,
vector: vec![1,2,3,4],
number3: 22,
}
serialized = ssz.serialize(dummy)
serialized की निम्नलिखित संरचना होगी (यहां केवल 4 बिट्स तक पैड किया गया है, वास्तविकता में 32 बिट्स तक पैड किया जाता है, और स्पष्टता के लिए int प्रतिनिधित्व को रखा गया है):
[37, 0, 0, 0, 55, 0, 0, 0, 16, 0, 0, 0, 22, 0, 0, 0, 1, 2, 3, 4]
------------ ----------- ----------- ----------- ----------
| | | | |
number1 number2 vector के number 3 vector का
लिए offset मान
स्पष्टता के लिए पंक्तियों में विभाजित किया गया है:
[
37, 0, 0, 0, # `number1` की लिटिल-एंडियन एन्कोडिंग।
55, 0, 0, 0, # `number2` की लिटिल-एंडियन एन्कोडिंग।
16, 0, 0, 0, # "ऑफ़सेट" जो इंगित करता है कि `vector` का मान कहाँ से शुरू होता है (लिटिल-एंडियन 16)।
22, 0, 0, 0, # `number3` की लिटिल-एंडियन एन्कोडिंग।
1, 2, 3, 4, # `vector` में वास्तविक मान।
]
यह अभी भी एक सरलीकरण है - ऊपर दिए गए आरेखों में पूर्णांक और शून्य वास्तव में बाइटलिस्ट के रूप में संग्रहीत किए जाएंगे, इस प्रकार:
[
10100101000000000000000000000000 # `number1` की लिटिल-एंडियन एन्कोडिंग
10110111000000000000000000000000 # `number2` की लिटिल-एंडियन एन्कोडिंग।
10010000000000000000000000000000 # "ऑफ़सेट" जो इंगित करता है कि `vector` का मान कहाँ से शुरू होता है (लिटिल-एंडियन 16)।
10010110000000000000000000000000 # `number3` की लिटिल-एंडियन एन्कोडिंग।
10000001100000101000001110000100 # `bytes` फ़ील्ड का वास्तविक मान।
]
इसलिए परिवर्तनीय-लंबाई वाले प्रकारों के वास्तविक मान क्रमांकित वस्तु के अंत में एक हीप में संग्रहीत किए जाते हैं, और उनके ऑफ़सेट फ़ील्ड की क्रमबद्ध सूची में उनके सही स्थानों पर संग्रहीत किए जाते हैं।
कुछ विशेष मामले भी हैं जिनके लिए विशिष्ट उपचार की आवश्यकता होती है, जैसे कि BitList प्रकार जिसके लिए क्रमांकन के दौरान एक लंबाई सीमा (length cap) जोड़ने और अक्रमांकन के दौरान हटाने की आवश्यकता होती है। पूर्ण विवरण SSZ स्पेक (opens in a new tab) में उपलब्ध हैं।
अक्रमांकन
इस वस्तु को अक्रमांकित करने के लिए स्कीमा की आवश्यकता होती है। स्कीमा क्रमांकित डेटा के सटीक लेआउट को परिभाषित करता है ताकि प्रत्येक विशिष्ट तत्व को बाइट्स के एक ब्लॉब से किसी सार्थक वस्तु में अक्रमांकित किया जा सके, जिसमें तत्वों का सही प्रकार, मान, आकार और स्थिति हो। यह स्कीमा ही है जो अक्रमांकक को बताता है कि कौन से मान वास्तविक मान हैं और कौन से ऑफ़सेट हैं। जब किसी वस्तु को क्रमांकित किया जाता है तो सभी फ़ील्ड नाम गायब हो जाते हैं, लेकिन स्कीमा के अनुसार अक्रमांकन पर उन्हें फिर से स्थापित कर दिया जाता है।
मर्कलाइज़ेशन (Merkleization)
इस SSZ क्रमांकित वस्तु को फिर मर्कलाइज़ किया जा सकता है - यानी उसी डेटा के मर्कल ट्री प्रतिनिधित्व में बदल दिया जाता है। सबसे पहले, क्रमांकित वस्तु में 32-बाइट चंक्स की संख्या निर्धारित की जाती है। ये ट्री की "पत्तियां" (leaves) हैं। पत्तियों की कुल संख्या 2 की घात (power of 2) होनी चाहिए ताकि पत्तियों को एक साथ हैशिंग करने पर अंततः एक एकल हैश-ट्री-रूट (hash-tree-root) प्राप्त हो। यदि स्वाभाविक रूप से ऐसा नहीं है, तो शून्य के 32 बाइट्स वाली अतिरिक्त पत्तियां जोड़ी जाती हैं। आरेखीय रूप से:
हैश ट्री रूट
/ \
/ \
/ \
/ \
पत्तियों का हैश पत्तियों का हैश
1 और 2 3 और 4
/ \ / \
/ \ / \
/ \ / \
लीफ1 लीफ2 लीफ3 लीफ4
ऐसे मामले भी हैं जहां ट्री की पत्तियां स्वाभाविक रूप से उस तरह समान रूप से वितरित नहीं होती हैं जैसे वे ऊपर दिए गए उदाहरण में होती हैं। उदाहरण के लिए, लीफ 4 कई तत्वों वाला एक कंटेनर हो सकता है जिसके लिए मर्कल ट्री में अतिरिक्त "गहराई" (depth) जोड़ने की आवश्यकता होती है, जिससे एक असमान ट्री बनता है।
इन ट्री तत्वों को लीफ X, नोड X आदि के रूप में संदर्भित करने के बजाय, हम उन्हें सामान्यीकृत सूचकांक (generalized indices) दे सकते हैं, जो रूट = 1 से शुरू होते हैं और प्रत्येक स्तर पर बाएं से दाएं गिने जाते हैं। यह ऊपर समझाया गया सामान्यीकृत सूचकांक है। क्रमांकित सूची में प्रत्येक तत्व का एक सामान्यीकृत सूचकांक 2**depth + idx के बराबर होता है, जहां idx क्रमांकित वस्तु में इसकी शून्य-अनुक्रमित (zero-indexed) स्थिति है और गहराई मर्कल ट्री में स्तरों की संख्या है, जिसे तत्वों (पत्तियों) की संख्या के आधार-दो लघुगणक (base-two logarithm) के रूप में निर्धारित किया जा सकता है।
सामान्यीकृत सूचकांक (Generalized indices)
एक सामान्यीकृत सूचकांक एक पूर्णांक है जो बाइनरी मर्कल ट्री में एक नोड का प्रतिनिधित्व करता है जहां प्रत्येक नोड का एक सामान्यीकृत सूचकांक 2 ** depth + index in row होता है।
1 --गहराई = 0 2**0 + 0 = 1
2 3 --गहराई = 1 2**1 + 0 = 2, 2**1+1 = 3
4 5 6 7 --गहराई = 2 2**2 + 0 = 4, 2**2 + 1 = 5...
यह प्रतिनिधित्व मर्कल ट्री में डेटा के प्रत्येक टुकड़े के लिए एक नोड सूचकांक प्रदान करता है।
मल्टीप्रूफ़्स (Multiproofs)
किसी विशिष्ट तत्व का प्रतिनिधित्व करने वाले सामान्यीकृत सूचकांकों की सूची प्रदान करने से हम इसे हैश-ट्री-रूट के विरुद्ध सत्यापित कर सकते हैं। यह रूट वास्तविकता का हमारा स्वीकृत संस्करण है। हमें प्रदान किए गए किसी भी डेटा को मर्कल ट्री में सही स्थान पर (इसके सामान्यीकृत सूचकांक द्वारा निर्धारित) डालकर और यह देखकर कि रूट स्थिर रहता है, उस वास्तविकता के विरुद्ध सत्यापित किया जा सकता है। स्पेक में यहाँ (opens in a new tab) ऐसे फ़ंक्शन हैं जो दिखाते हैं कि सामान्यीकृत सूचकांकों के एक विशेष सेट की सामग्री को सत्यापित करने के लिए आवश्यक नोड्स के न्यूनतम सेट की गणना कैसे करें।
उदाहरण के लिए, नीचे दिए गए ट्री में सूचकांक 9 में डेटा को सत्यापित करने के लिए, हमें सूचकांक 8, 9, 5, 3, 1 पर डेटा के हैश की आवश्यकता है। (8,9) का हैश, हैश (4) के बराबर होना चाहिए, जो 5 के साथ हैश होकर 2 उत्पन्न करता है, जो 3 के साथ हैश होकर ट्री रूट 1 उत्पन्न करता है। यदि 9 के लिए गलत डेटा प्रदान किया गया था, तो रूट बदल जाएगा - हम इसका पता लगा लेंगे और शाखा को सत्यापित करने में विफल रहेंगे।
* = प्रमाण उत्पन्न करने के लिए आवश्यक डेटा
1*
2 3*
4 5* 6 7
8* 9* 10 11 12 13 14 15