سادہ سلسلہ بندی
سادہ سلسلہ بندی (SSZ) وہ سلسلہ بندی کا طریقہ ہے جو بیکن چین پر استعمال ہوتا ہے۔ یہ عمل درآمد کی تہہ پر استعمال ہونے والی RLP سلسلہ بندی کی جگہ لیتا ہے، سوائے پیئر دریافت پروٹوکول کے، اتفاق رائے کی تہہ میں ہر جگہ۔ RLP سلسلہ بندی کے بارے میں مزید جاننے کے لیے، ریکرسیو-لینتھ پریفکس (RLP) دیکھیں۔ SSZ کو اس طرح ڈیزائن کیا گیا ہے کہ یہ فیصلہ کن (deterministic) ہو اور مؤثر طریقے سے مرکلائز (Merkleize) بھی کر سکے۔ SSZ کے بارے میں سوچا جا سکتا ہے کہ اس کے دو اجزاء ہیں: ایک سلسلہ بندی کی اسکیم اور ایک مرکلائزیشن اسکیم جو سلسلہ بند ڈیٹا اسٹرکچر کے ساتھ مؤثر طریقے سے کام کرنے کے لیے بنائی گئی ہے۔
SSZ کیسے کام کرتا ہے؟
سلسلہ بندی
SSZ ایک سلسلہ بندی کی اسکیم ہے جو خود وضاحتی نہیں ہے - بلکہ یہ ایک ایسی اسکیم پر انحصار کرتی ہے جسے پہلے سے معلوم ہونا چاہیے۔ SSZ سلسلہ بندی کا مقصد صوابدیدی پیچیدگی کی اشیاء کو بائٹس کی تاروں (strings of bytes) کے طور پر پیش کرنا ہے۔ "بنیادی اقسام" کے لیے یہ ایک بہت ہی سادہ عمل ہے۔ عنصر کو محض ہیکسا ڈیسیمل بائٹس میں تبدیل کر دیا جاتا ہے۔ بنیادی اقسام میں شامل ہیں:
- غیر دستخط شدہ انٹیجرز (unsigned integers)
- بولینز (Booleans)
پیچیدہ "مرکب" (composite) اقسام کے لیے، سلسلہ بندی زیادہ پیچیدہ ہوتی ہے کیونکہ مرکب قسم میں متعدد عناصر ہوتے ہیں جن کی اقسام یا سائز مختلف ہو سکتے ہیں، یا دونوں۔ جہاں ان تمام اشیاء کی لمبائی مقررہ ہوتی ہے (یعنی عناصر کا سائز ان کی اصل اقدار سے قطع نظر ہمیشہ مستقل رہے گا) وہاں سلسلہ بندی محض مرکب قسم کے ہر عنصر کو لٹل-اینڈین (little-endian) بائٹ اسٹرنگز میں ترتیب دے کر تبدیل کرنا ہے۔ ان بائٹ اسٹرنگز کو آپس میں جوڑ دیا جاتا ہے۔ سلسلہ بند آبجیکٹ میں مقررہ لمبائی والے عناصر کی بائٹ لسٹ کی نمائندگی اسی ترتیب میں ہوتی ہے جس ترتیب میں وہ غیر سلسلہ بند (deserialized) آبجیکٹ میں ظاہر ہوتے ہیں۔
متغیر لمبائی (variable lengths) والی اقسام کے لیے، اصل ڈیٹا کو سلسلہ بند آبجیکٹ میں اس عنصر کی پوزیشن پر ایک "آف سیٹ" (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 value
وضاحت کے لیے لائنوں پر تقسیم کیا گیا ہے:
[
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 کی تفصیلات (spec) (opens in a new tab) میں دستیاب ہیں۔
غیر سلسلہ بندی
اس آبجیکٹ کو غیر سلسلہ بند کرنے کے لیے اسکیما (schema) کی ضرورت ہوتی ہے۔ اسکیما سلسلہ بند ڈیٹا کی درست ترتیب کی وضاحت کرتا ہے تاکہ ہر مخصوص عنصر کو بائٹس کے ایک بلاب سے کسی بامعنی آبجیکٹ میں غیر سلسلہ بند کیا جا سکے جس میں عناصر کی درست قسم، قدر، سائز اور پوزیشن ہو۔ یہ اسکیما ہی ہے جو ڈی سیریلائزر (deserializer) کو بتاتا ہے کہ کون سی اقدار اصل اقدار ہیں اور کون سی آف سیٹس (offsets) ہیں۔ جب کسی آبجیکٹ کی سلسلہ بندی کی جاتی ہے تو تمام فیلڈ کے نام غائب ہو جاتے ہیں، لیکن اسکیما کے مطابق غیر سلسلہ بندی پر انہیں دوبارہ بحال کر دیا جاتا ہے۔
مرکلائزیشن (Merkleization)
اس SSZ سلسلہ بند آبجیکٹ کو پھر مرکلائز کیا جا سکتا ہے - یعنی اسی ڈیٹا کی مرکل ٹری (Merkle-tree) نمائندگی میں تبدیل کیا جا سکتا ہے۔ سب سے پہلے، سلسلہ بند آبجیکٹ میں 32-byte چنکس (chunks) کی تعداد کا تعین کیا جاتا ہے۔ یہ ٹری کے "پتے" (leaves) ہیں۔ پتوں کی کل تعداد 2 کی طاقت (power of 2) ہونی چاہیے تاکہ پتوں کو ایک ساتھ ہیش کرنے سے بالآخر ایک واحد ہیش-ٹری-روٹ (hash-tree-root) تیار ہو۔ اگر قدرتی طور پر ایسا نہیں ہے، تو صفر کے 32 بائٹس پر مشتمل اضافی پتے شامل کیے جاتے ہیں۔ خاکہ کے لحاظ سے:
hash tree root
/ \
/ \
/ \
/ \
hash of leaves hash of leaves
1 and 2 3 and 4
/ \ / \
/ \ / \
/ \ / \
leaf1 leaf2 leaf3 leaf4
ایسے معاملات بھی ہیں جہاں ٹری کے پتے قدرتی طور پر اس طرح یکساں طور پر تقسیم نہیں ہوتے جیسے وہ مندرجہ بالا مثال میں کرتے ہیں۔ مثال کے طور پر، پتا 4 ایک ایسا کنٹینر ہو سکتا ہے جس میں متعدد عناصر ہوں جن کے لیے مرکل ٹری میں اضافی "گہرائی" (depth) شامل کرنے کی ضرورت ہو، جس سے ایک ناہموار ٹری بنتا ہے۔
ان ٹری عناصر کو پتا X، نوڈ X وغیرہ کہنے کے بجائے، ہم انہیں عمومی اشاریے (generalized indices) دے سکتے ہیں، جو روٹ = 1 سے شروع ہوتے ہیں اور ہر سطح کے ساتھ بائیں سے دائیں گنتے ہیں۔ یہ وہ عمومی اشاریہ ہے جس کی اوپر وضاحت کی گئی ہے۔ سلسلہ بند فہرست میں ہر عنصر کا ایک عمومی اشاریہ 2**depth + idx کے برابر ہوتا ہے جہاں idx سلسلہ بند آبجیکٹ میں اس کی صفر-اشاریہ (zero-indexed) پوزیشن ہے اور گہرائی مرکل ٹری میں سطحوں کی تعداد ہے، جس کا تعین عناصر (پتوں) کی تعداد کے بیس-ٹو لاگرتھم (base-two logarithm) کے طور پر کیا جا سکتا ہے۔
عمومی اشاریے (Generalized indices)
ایک عمومی اشاریہ ایک انٹیجر ہے جو بائنری مرکل ٹری میں ایک نوڈ کی نمائندگی کرتا ہے جہاں ہر نوڈ کا ایک عمومی اشاریہ 2 ** depth + index in row ہوتا ہے۔
1 --depth = 0 2**0 + 0 = 1
2 3 --depth = 1 2**1 + 0 = 2, 2**1+1 = 3
4 5 6 7 --depth = 2 2**2 + 0 = 4, 2**2 + 1 = 5...
یہ نمائندگی مرکل ٹری میں ڈیٹا کے ہر ٹکڑے کے لیے ایک نوڈ اشاریہ فراہم کرتی ہے۔
ملٹی پروفز (Multiproofs)
کسی مخصوص عنصر کی نمائندگی کرنے والے عمومی اشاریوں کی فہرست فراہم کرنے سے ہم اسے ہیش-ٹری-روٹ کے خلاف تصدیق کر سکتے ہیں۔ یہ روٹ حقیقت کا ہمارا تسلیم شدہ ورژن ہے۔ ہمیں فراہم کردہ کسی بھی ڈیٹا کی اس حقیقت کے خلاف تصدیق کی جا سکتی ہے اسے مرکل ٹری میں صحیح جگہ پر داخل کر کے (اس کے عمومی اشاریہ کے ذریعے طے کیا جاتا ہے) اور یہ مشاہدہ کر کے کہ روٹ مستقل رہتا ہے۔ تفصیلات (spec) میں یہاں (opens in a new tab) ایسے فنکشنز موجود ہیں جو دکھاتے ہیں کہ عمومی اشاریوں کے ایک خاص سیٹ کے مندرجات کی تصدیق کے لیے درکار نوڈز کے کم از کم سیٹ کا حساب کیسے لگایا جائے۔
مثال کے طور پر، نیچے دیے گئے ٹری میں اشاریہ 9 میں ڈیٹا کی تصدیق کرنے کے لیے، ہمیں اشاریہ 8, 9, 5, 3, 1 پر ڈیٹا کے ہیش کی ضرورت ہے۔ (8,9) کا ہیش، ہیش (4) کے برابر ہونا چاہیے، جو 5 کے ساتھ ہیش ہو کر 2 پیدا کرتا ہے، جو 3 کے ساتھ ہیش ہو کر ٹری روٹ 1 پیدا کرتا ہے۔ اگر 9 کے لیے غلط ڈیٹا فراہم کیا گیا تھا، تو روٹ بدل جائے گا - ہم اس کا پتہ لگا لیں گے اور برانچ کی تصدیق کرنے میں ناکام رہیں گے۔
* = ثبوت (proof) بنانے کے لیے درکار ڈیٹا
1*
2 3*
4 5* 6 7
8* 9* 10 11 12 13 14 15