استخدم هذه الصفحة للتكامل مع الجسر: لبناء إيداع على سلسلة المصدر، ولتقديم شهادة (attestation) على Rand، ولحرق أصل مُجسَّر وإعادته إلى الخارج، ولقراءة الحالة العامة للجسر. أُنشئت السلسلة 20، وهي شبكة testnet الحالية (قائمة منذ 2026-10-01 06:17 UTC، العقدة الكاملة v0.6.8)، بقسم bridge: ثمانية حرّاس تبدأ من فهرس مجموعة الحرّاس 1، وثمانية حرّاس ما بعد الكم، ومفتاح إيقاف مؤقت، وجهات إصدار (emitters) لـ Ethereum وBSC وTron وSolana (الثلاث الأولى هي نقاط النهاية التي أُعيد نشرها في 2026-09-30)، ومجموعة rules-v2 التي يبلغ سقف السك العام فيها 4,000 zUSD لكل 24 ساعة متحركة، وحدًّا أدنى لمنع إعادة تشغيل الرسائل الواردة، ومجموعتين جديدتين في v0.6.8: الرسوم بنسبة 10 bps على كل عملية سك وكل عملية حرق، وتُقتطع بعملة zUSD، وقواعد التدوير التي تُلزم أي تدوير لمجموعة ما بعد الكم أو لمفتاح الإيقاف المؤقت بإثبات حيازة المفاتيح الجديدة وبانتظار يوم كامل. وعلى خلاف السلسلة 14، تُدرج وثيقة التكوين (genesis) لهذه السلسلة zUSD بجميع الضمانات السبعة، وتحمل معها الأصول المحفوظة التي انتقلت من السلسلة 14 عبر كل إصدار لاحق (المنقول من السلسلة 14). وتعرض rand_getAssets والمسار /bridge/assets في المستكشف قيمة locked الحالية لكل ضمان.
التدفق
source chain (Ethereum / BSC / Tron / Solana) Rand
┌────────────────────────────────┐ ┌────────────────────────────────┐
│ lock tokens, emit │ guardians observe, │ BridgeAttest transaction │
│ (amount, token, to, fee) │ sign mu = keccak²(body) │ admission → one deposit note │
└────────────────────────────────┘ threshold n·2/3 + 1 │ computed by the chain │
▲ + Dilithium2 co-signers │ │
release after guardian guardian committee │ BridgeBurn transaction │
signatures on the burn ◄───── secp256k1 keys ◄───────── │ one bundle: slots 0–1 burn the │
message read the burn log │ asset, 2–3 pay the RAND fee; │
│ emits a sequenced burn message │
└────────────────────────────────┘
للداخل: تُقفل القيمة على سلسلة المصدر، ويشهد الحرّاس، ويقدّم أي طرف الشهادة إلى Rand، فيُلحق السجل ملاحظة إيداع واحدة للمستلم — ملاحظة تكون كلمة asset فيها هي فهرس الرمز المُجسَّر في السجل. وللخارج: تحرق معاملة على Rand ملاحظات ذلك الأصل وتصدر رسالة؛ ويشهد الحرّاس على تلك الرسالة؛ ثم يُفرج العقد على سلسلة المصدر عن الأصل. لا تتحقق Rand أبدًا من ترويسة كتلة أو إثبات حالة على سلسلة المصدر: توقيعات لجنة الحرّاس هي نموذج الثقة بأكمله.
نموذج الثقة ومعرّفات السلاسل
معرّفات السلاسل المحلية للجسر، المشتركة مع العقود (bridge-codec):
| السلسلة | المعرّف |
|---|---|
| Rand | 1 |
| Ethereum | 2 |
| BSC | 3 |
| Tron | 4 |
| Solana | 5 |
الحرّاس لجنة من مفاتيح secp256k1؛ وهوية الحارس هي آخر 20 بايتًا من keccak256(uncompressed_pubkey[1..])، أي اشتقاق العناوين في Ethereum، فيعمل المفتاح الواحد على عقود EVM وبرنامج Solana وعلى Rand. النصاب القانوني لمجموعة بحجم n هو n·2/3 + 1. ويجب أن يكون كل توقيع بصيغة low-s (s ≤ n/2) سدًّا لباب قابلية التعديل (malleability). أما الملخّص الذي يوقّعه الحرّاس فهو mu = keccak256(keccak256(body_bytes)). وتدوير مجموعة الحرّاس هو شهادة صادرة عن جهة الإصدار الخاصة بالحوكمة (chain 1, keccak256("rand-bridge-governance"))، ويجب أن تحمل new_index == current + 1، وأن توقّعها المجموعة الحالية لا مجموعة سابقة؛ وتحتفظ المجموعة المنتهية بمهلة سماح قدرها 86,400 ثانية للتحويلات الجارية. (هذه هي مجموعة ECDSA. أما مجموعة ما بعد الكم ومفتاح الإيقاف المؤقت فيُدوَّران بإجراءاتهما الخاصة، وهي على السلسلة 20 تنتظر تأخيرًا مدته يوم: أدناه.) وإذا تواطأ n·2/3 + 1 من الحرّاس أمكنهم سك قيمة اعتباطية؛ ويحدّ تحصين الجسر من مقدار ذلك وسرعته، لكنه لا يزيله.
ربط المعاملة. لا تحمل معاملة Rand أي توقيع: يُصرَّح بالحرق بإثبات STARK الخاص بحزمتها. ومنذ السلسلة 14 يُنشأ إثبات كل حزمة على Transaction::binding ويُتحقق منه مقابله — وهو تجزئة للمعاملة كاملة (معرّف السلسلة، والحزمة الواحدة بمظاريفها الأربعة، والإجراء) مع تفريغ بايتات الإثبات وحدها. ومنذ السلسلة 17 تحمل الحزمة إثباتين، هما إثبات الحزمة وإثبات تفويض الإنفاق: يفرّغ الربط (binding) كليهما، ويُبقي auth_commit الخاص بالحزمة، ويُتحقق من كليهما مقابله. وكل نسخة من حرق معلّق غُيّر فيها to أو relayer_fee أو (to_chain, token)، أو من شهادة غُيّر فيها time أو المظروف، يفشل إثباتها (TxError::InvalidBundleProof). وعلى السلسلة 20 يحمل الربط كذلك تجزئة genesis (في genesis تكون binding_domain: 1: أي rand-tx-bind-2 على تجزئة genesis ومعرّف السلسلة والحزمة والإجراء)، فلا يُقبل كذلك إثبات صُنع لسلسلة أخرى تشترك معها في معرّف السلسلة. ويجب على المحافظ والمُرحِّلات (relayers) تشغيل نسخة مبنية للسلسلة التي ترسل إليها: v0.6.8 على السلسلة 20 (وv0.6.7 على السلسلتين 18 و19)؛ أما rand الأقدم فيُنتج إثباتات وتوقيعات ترفضها السلسلة 20.
خدمتا الحارس والمُرحِّل ليستا جزءًا من العقدة الكاملة؛ فالعقدة الكاملة تحمل المرمِّز (codec) المشترك والنصف المستقبِل.
صيغة السلك
كل عدد صحيح بترتيب big-endian؛ ونسخة السلك هي 1.
مظروف الشهادة
| الإزاحة | الطول | الحقل |
|---|---|---|
| 0 | 1 | version = 1 |
| 1 | 4 | guardian_set_index |
| 5 | 1 | n_sigs |
| 6 | 66·n_sigs | التوقيعات: index (1)، r (32)، s (32)، v (1) |
6 + 66·n_sigs | … | body |
ترويسة الجسم — 51 بايتًا، ثم الحمولة
| الإزاحة | الطول | الحقل |
|---|---|---|
| 0 | 4 | timestamp |
| 4 | 4 | nonce |
| 8 | 2 | emitter_chain |
| 10 | 32 | emitter_address |
| 42 | 8 | sequence |
| 50 | 1 | consistency_level |
| 51 | … | payload |
حمولة Transfer — المعرّف 1، 133 بايتًا
| الإزاحة | الطول | الحقل |
|---|---|---|
| 0 | 1 | id = 1 |
| 1 | 32 | amount (u256؛ تستخدم Rand الـ 128 بتًا الدنيا، ويجب أن يتسع الإيداع أيضًا في u64 الخاص بالملاحظة) |
| 33 | 32 | token_address |
| 65 | 2 | token_chain |
| 67 | 32 | to — تجزئة المستلم، انظر أدناه |
| 99 | 2 | to_chain |
| 101 | 32 | fee (u256): حصة المُرحِّل من amount |
حمولة GuardianSetUpgrade — المعرّف 2: id (1)، new_index (4)، n (1)، ثم n من المفاتيح ذات 20 بايتًا.
عناوين Ethereum وBSC وTron هي 20 بايتًا تُحشى من اليسار بـ 12 بايتًا صفرية في الخانات ذات 32 بايتًا؛ أما عنوان Solana فيملأ الخانة. ويجب أن تأتي Transfer من جهة الإصدار المسجّلة للسلسلة emitter_chain، ويجب أن يساوي token_chain السلسلة المُصدِرة. والحد الأقصى للشهادة 16 KiB. ولم يغيّر أيٌّ من تحصين الجسر صيغة السلك ولا المرمِّز ولا نقاط النهاية المنشورة على سلاسل المصدر: فالتوقيعات المشتركة لما بعد الكم ورسائل الحوكمة تنتقل في معاملة Rand لا في الشهادة.
المستلم ذو 32 بايتًا
العنوان المحمي (shielded) طوله 1,216 بايتًا، ولا يتسع السلك إلا لـ 32. فيحمل الحقل to تجزئةً له:
to = blake3("rand-shielded-recipient" || pk_bytes || kem_ek) // ShieldedAddress::recipient_hash
يذكر المودِع التجزئة على سلسلة المصدر، ويوقّع عليها الحرّاس، ويضع مقدِّم الشهادة العنوان الكامل في الإجراء BridgeAttest، ويعيد السجل حساب التجزئة ويرفض أي عدم تطابق — وإلا استطاع المقدِّم أن يختار من يستلم إيداع غيره. ويجب أن تقبل الواجهات الأمامية لسلسلة المصدر هذه الصيغة؛ وتطبعها المحفظة. وقد جعلت السلسلة 11 الخانة معرّف المستقبِل مباشرة لفترة وجيزة؛ ثم أُلغيت صيغة العنوان تلك مع السلسلة 12 (الحسابات والعناوين).
رمز واحد وضمانات متعددة: zUSD
لم يعد سجل الأصول ملكًا للجسر. فقد انتقل إلى سجل رموز RPL على مستوى السجل، المشترك بين الرموز الأصلية والمُجسَّرة، ويلتزم جذر حالة السلسلة به باسم tokens_root. والرمز المُجسَّر هو مدخل في السجل سلطة السك فيه هي Bridge { backings }، ولم يعد يقابل زوجًا واحدًا (chain, token). وأول هذه الرموز zUSD، المضمون بـ USDT وUSDC المقفلة على أربع سلاسل — تحمل Ethereum وBinance Smart Chain وSolana العملتين معًا، وTron USDT وحدها (أوقفت Circle إصدار USDC على Tron):
chain token (32-byte wire form) coin decimals
2 000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7 USDT 6
2 000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 USDC 6
3 00000000000000000000000055d398326f99059ff775485246999027b3197955 USDT 18
3 0000000000000000000000008ac76a51cc950d9822d68b83fe1ad97b32cd580d USDC 18
4 000000000000000000000000a614f803b6fd780986a42c78ec9c7f77e6ded13c USDT 6
5 ce010e60afedb22717bd63192f54145a3f965a33bb82d2c7029eb2ce1e208264 USDT 6
5 c6fa7af3bedbad3a3d65f36aabc97431b1bbe4c2d2f6e0e47ca60203452f5d61 USDC 6
على السلسلة 20 يحمل zUSD الفهرس 1، وrpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx، والاسم “Shielded USD”، و8 خانات عشرية، ومدرجًا في ملف genesis كما في السلاسل من 15 إلى 19، بالاسم والرمز والملح (salt) نفسها التي كانت له على السلسلة 14، فانتقل معرّف أصله (32e5ab28c782c663e14da2650a3feb12f16a12db85599f4f62dc169d26f37b1f) ومعرّفه rpl1… دون تغيير (المستكشف: GET /tokens/1). وعلى السلسلة 14 سُجّل عند الارتفاع 256 بمعاملة register_bridged_token.
Backing { chain: u16, token: [u8; 32], decimals: u8, locked: u64, minted_today: u64, mint_day: u32 }
لا يضمن الزوج (chain, token) أكثر من رمز واحد في السجل كله (TokenError::BackingTaken)؛ وللرمز من 1 إلى 32 ضمانًا؛ وdecimals هو عدد خانات عملة المصدر نفسها، من 0 إلى 18، لا الخانات الثماني للرمز المُجسَّر. والضمان المجمَّع مخاطرة معلنة: فالعملات السبع قابلة للتبادل مع zUSD ذهابًا وإيابًا بالقيمة الاسمية، فيُضعف انفكاك سعر أيٍّ منها قيمة zUSD لدى كل حامليه، ولا يوجد سقف على جانب Rand لحصة أي ضمان.
zUSD سلة. يتحمل الحامل مخاطر كل غطاء مُدرج، لا مخاطر العملة التي أودعها: فكل وحدة zUSD مطالبة على المجمّع، وعلى أي غطاء يكفي locked فيه. لا شيء يسعّر الغطاء. فقفل وحدة واحدة من أي عملة مُدرجة يسك 1 zUSD، ويجوز للحرق أن يسمّي أي غطاء يغطيه locked، ومن ثَمّ يمكن إيداع عملة فقدت ربطها بالسعر بقيمتها الاسمية وحرق zUSD مقابل عملة حافظت على ربطها، إلى أن تُستنزف الأغطية السليمة. والخسارة محدودة بنوافذ السك المتحركة وبالحراسة المقفلة فعلًا: ففي السلسلة 20 لا يمكن سك أكثر من 4,000 zUSD عبر جميع الأغطية، و100,000 لكل غطاء، في أي 24 ساعة (rules v2)، ولا يمكن أن يخرج عبر غطاء سليم أكثر مما يحتويه. فالنافذة تحدّ من المعدل لا من الإجمالي: إنها تشتري الوقت لإيقاف السك مؤقتًا، وهو ما يوقف الإيداعات الجديدة من العملة الضعيفة بينما تبقى عمليات الحرق مفتوحة. لا يوجد حد استرداد لكل غطاء ولا إيقاف مؤقت لكل غطاء. وفي السلسلة 14، قبل rules v2، كان الحد الوحيد هو المُوقِف الخاص بنقاط المصدر الطرفية وسقوف المعدل لكل رمز.
الثابت: total_supply == Σ backings.locked. تضيف TokenRegistry::lock (وهي BridgeAttest) المبلغ المصدَّق عليه إلى locked الخاص بالغطاء وإلى total_supply الخاص بالرمز في استدعاء واحد؛ وتطرح TokenRegistry::release (وهي BridgeBurn) المبلغ المحرَّر من كليهما. لا شيء آخر يكتب أيًّا من الحقلين، وورقة الرمز تلتزم بـlocked لكل غطاء داخل tokens_root، فإذا اختلفت عقدتان في أحدهما تباعدتا عند جذر الحالة. لا تأخذ نقاط المصدر الطرفية رسمًا على القفل، وتخصم كامل المبلغ المصدَّق عليه عند التحرير، فيساوي locked لكل غطاء حراسة تلك النقطة الطرفية، باستثناء الرسائل قيد النقل. ولا تخل الرسوم في السلسلة 20 بذلك: فالرسم ملاحظة zUSD تبقى مُصدَرة ومغطاة، فيقفل الإيداع المبلغ الإجمالي ولا يحرر الحرق إلا amount − fee. وفي السلسلة 20 ضُبط اقتطاع النقاط الطرفية الخاص بها، وقدره 10 bps من USDT عند التحرير، على 0 عند القطع؛ ويُؤخذ الرسم الآن على Rand.
المبالغ وحدات من 8 خانات عشرية على الشبكة، أيًّا كانت خانات العملة المصدر العشرية: فإيداع 1 USDT من Ethereum (6 خانات عشرية) يُقفل على هيئة 100_000_000 وحدة. والفهرس 0 وحده، أي RAND، له تسع خانات عشرية في هذه السلسلة.
يجب إدراج الرمز قبل أول إيداع له. لم يعد هناك تسجيل عند أول ظهور: فالإفادة التي تسمّي (chain, token) لم يدرجه أحد تُرفض بالخطأ BridgeError::UnlistedToken (token … of chain … is not listed on this chain). وتبقى الأموال في حراسة نقطة المصدر الطرفية إلى أن يدرج نصاب من الحراس ما بعد الكم العملة (الإدراج بعد التكوين).
asset_id = blake3("rand-bridge-asset" || token_chain (2 bytes BE) || token_address (32 bytes))
لا تزال rand_bridgeAssetId(token_chain, token_address) تحسب هذا على أي سلسلة، وإليه يُحلّ (token_chain, token_address) في حمولة Transfer، لكنه لم يعد مفتاحًا في السجل. أما ما تحمله الملاحظة والمحفظة فهو فهرس الرمز، وهو u32 كثيف: الفهرس 0 هو RAND ولا يكون في السجل أبدًا. وفي rand_getAssets صف لكل غطاء، وصفوف الرمز الواحد كلها تحت الفهرس نفسه:
[{ "index": 1, "chain": 2, "token": "000000000000000000000000dac17f…", "asset_id": "…", "decimals": 6,
"locked": "0", "mint_cap_per_day": "10000000000000", "minted_today": "0", "mint_day": 20716 }]
locked وmint_cap_per_day وminted_today سلاسل عشرية بوحدات من 8 خانات عشرية، كسائر مبالغ u64 التي تقدمها RPC منذ السلسلة 14؛ أما index وchain وdecimals وmint_day فتبقى أرقامًا. وتعيد الطرائق على مستوى الرمز — rand_getTokens وrand_getToken وrand_getTokenSupply — الرمز المجسَّر صفًّا واحدًا يحمل جميع أغطيته (رموز RPL). أما GET /bridge/tokens في المستكشف فهو القائمة الثابتة المسموح بها للعملات التي يقبلها الجسر.
الوارد: BridgeAttest
Action::BridgeAttest { attestation: Vec<u8>, recipient: ShieldedAddress, r: Word8, time: u32,
asset: u32, envelope: Envelope, pq_signatures: Vec<PqSignature> }
PqSignature { index: u8, signature: Vec<u8> } // Dilithium2, 2,420 bytes
تحمله معاملة محمية عادية تدفع حزمتها الواحدة ذات الأصل المخفي الأساس البالغ 0.001 RAND ولا شيء غيره: فالمُرحِّل يدفع عن مودِع لا يملك RAND بعد. وتحسب السلسلة، لا المُرسِل، ملاحظة الإيداع { pk: recipient.pk, from: 0, amount, asset: <index>, time, r } من المبلغ الذي وقّع عليه الحراس، فلا يستطيع المُرحِّل تضخيم عملية السك أو إعادة توجيهها؛ والتزامها هو الورقة الوحيدة التي لا تحملها المعاملة على الشبكة. وفي السلسلة 20 يكون amount الملاحظة هو الإجمالي ناقص رسم السك، وتضيف السلسلة ملاحظة ثانية، هي الرسم، إلى الخزينة (رسوم السك والحرق).
التعمية مشتقة لا مختارة. يجب أن يكون r مساويًا
r = blake3("rand-deposit-r-1" || mu) // read as eight little-endian words; mu = keccak256(keccak256(body))
للخلاصة التي وقّع عليها نصاب الحراس؛ وأي قيمة أخرى تعني BridgeError::WrongDepositBlinding، وتُرفض قبل أي عمل على التوقيعات. يبقى الحقل على الشبكة — إذ تقرؤه المحفظة عند إعادة بناء الحقول العلنية — لكنه مقيَّد فحسب، وتشمل القاعدة إفادة التدوير أيضًا. وقبلها كان بوسع كل من رأى معاملة مُرحِّل معلقة أن يرسل الإفادة نفسها بتعمية من اختياره: فيحصل المستلم على ملاحظة مع ذلك، لكن معاملة المُرحِّل تموت بعد أن دفع ثمن إثبات. ومع تثبيت r، يسمّي مرسلان للإفادة الواحدة عند time واحد ملاحظة واحدة، ويُدخل المجمّع تعارضًا بينهما على الالتزام.
time وasset هما توقعان من المودِع، يُجريهما كي يمكن ختم المغلف قبل وجود المعاملة. ويُقيَّد time بالنافذة نفسها التي تُقيَّد بها الحزمة (1,024 كتلة في السلسلة 20، من proof_window_blocks في التكوين؛ و256 قبلها)، ولا يُشتق عمدًا: إذ لا قاعدة تعطي time يتفق عليه كل المرسلين، فقد يختار مُستبِق time آخر ضمن النافذة — أي ملاحظة مختلفة للمستلم والمبلغ والأصل نفسها، تجدها محفظة المستلم بالطريقة نفسها — فيتصادم الإرسالان على خلاصة الإفادة بدلًا من ذلك. ويجب أن يساوي asset الفهرس الذي منحه السجل فعلًا لـ(chain, token)، وإلا TxError::AttestAssetMismatch.
يُحمل fee في حمولة Transfer ولا يُدفع لأحد: إذ لا هوية للمرسل تُدفع إليها، فالإيداع هو المبلغ الإجمالي، ناقص رسم السك الخاص بالسلسلة وحده حيث يحدده التكوين (السلسلة 20). والحماية من الإعادة تتم بالخلاصة: كل mu مستهلك يدخل في مجموعة spent الخاصة بالجسر، ويرفض مجمّع المعاملات مُرحِّلًا ثانيًا يسابق الإفادة نفسها. ويُقيَّد الإيداع كذلك بسقف السك للغطاء، وفي السلسلة 20 بالسقف العام أيضًا، ويُرفض أثناء إيقاف السك مؤقتًا (B1، rules v2). ويحتسب السقفان المبلغ الإجمالي. وفي السلسلة التي يضع تكوينها حدًّا أدنى للإعادة، ومنها السلسلة 20، تُرفض المعاملة التي sequence فيها دون حد سلسلتها المصدر بالخطأ BelowReplayFloor قبل فحص أي توقيع (ما انتقل من السلسلة 14).
rand bridge-mint <attestation hex or @file> --pq <JSON or @file> [--to rand1…] [--fee <RAND>] [--no-wait]
rand bridge-rotate <rotation hex or @file> --pq <JSON or @file> [--fee <RAND>] [--no-wait]
--pq مطلوب: نصاب التوقيع المشترك لـDilithium2 من الحراس على هيئة [{"index":0,"signature":"<4840 hex>"},…]. وقبل أي إثبات تفك المحفظة ترميز الإفادة، وتتحقق من النصاب مقابل pq_guardians في العقدة ومعرّف السلسلة، وتتحقق من أن المستلم يُجزَّأ إلى المستلم المصدَّق عليه، وتقرأ الفهرس المُدرج؛ ثم تشتق r، وتختم مغلف المستلم (بالمبلغ الصافي في السلسلة ذات الرسوم)، وتدفع بحزمة واحدة من RAND الخاص بها. ويرسل bridge-rotate تدوير مجموعة الحراس (الحمولة 2)، وهو لا يودع شيئًا، ويرفض كل ما لا يتقدم بمجموعة Rand خطوة واحدة. ولأن كل حقل من ملاحظة الإيداع علني في تلك المعاملة الواحدة، تعيد محفظة المستلم بناء ملاحظات الإيداع الموجهة إليها بالمرور على معاملات bridge_attest المثبتة، دون فتح أي مغلف — فقد يختم المُرحِّل العدائي هراءً لكنه لا يستطيع إخفاء الملاحظة.
الصادر: BridgeBurn
Transaction {
chain_id,
bundle: <hidden-asset bundle: slots 0–1 spend the token, slots 2–3 pay the RAND fee;
burn_asset = asset, burn_a = amount, burn_r = 0, fee >= BRIDGE_BURN_FEE>,
action: BridgeBurn { asset, amount, relayer_fee, to_chain, token, to }
}
منذ حزمة الأصل المخفي في السلسلة 14 يكون الحرق حزمة واحدة (كان حزمتين قبلها): تثبت الخانتان 0–1 أن الحارق كان يملك ملاحظات من الأصل مجموعها لا يقل عن amount، حيث burn_a هي القيمة الخارجة من المجمّع، وتدفع الخانتان 2–3 الرسم بـRAND. والرسم هو BRIDGE_BURN_FEE، أي 0.01 RAND، وهو يغطي أساس الحزمة. أما relayer_fee فجزء من amount (fee ≤ amount) يقتطعه عقد التحرير في السلسلة الوجهة، وليس رسمًا إضافيًا، فتساوي burn_a المبلغ amount تمامًا. وفي السلسلة 20 تحتفظ السلسلة أيضًا برسم حرق قدره 10 bps من amount على هيئة ملاحظة zUSD للخزينة، ويكون المحرَّر amount − fee؛ ويجب أن يتسع المحرَّر لحصة المُرحِّل (رسوم السك والحرق).
الحرق يسمّي عملته. يجب أن يكون (to_chain, token) غطاءً للأصل المحروق (وإلا TokenError::NotABacking)، وتُفحص قاعدتان في خطوة الإجراء، قبل إثبات الحزمة:
- وحدة التحرير. الغطاء الذي له
d < 8من الخانات العشرية في المصدر يحرر بوحدات من10^(8−d)(100 للعملة ذات 6 خانات عشرية؛ و1 للعملة ذات 18). ويجب أن يكون كل من المبلغ المحرَّر وrelayer_feeمضاعفًا صحيحًا لها، وإلاTokenError::NotReleasable(… is not a multiple of the release unit …): إذ إن كسرًا دون أصغر وحدة في العملة المصدر سيبقى عالقًا في الحراسة. - حراسة الغطاء نفسه. يجب أن يكون المبلغ المحرَّر
≤ lockedلذلك الغطاء، وإلاTokenError::InsufficientBacking(only … is locked in that backing, not the … asked for). فقد يغطي إجمالي عرض الرمز التحرير، لكن حراسة هذا الغطاء وحده هي المطلوبة.
والمبلغ المحرَّر هو amount في السلسلة التي لا رسوم فيها، وamount − fee في السلسلة 20.
يجب أن يكون لـto الشكل الصحيح (20 بايتًا مبطنة بالأصفار في السلاسل 2 و3 و4؛ ولا يكون صفرًا كله أبدًا). ويسجل دفتر الأستاذ BridgeBurnRecord تحت burn_sequence يتزايد تزايدًا صارمًا، مع تجزئة المعاملة في خانة المرسل، ويقرأ الحراس سجل الحرق:
→ {"method":"rand_getBridgeBurn","params":[13]}
← { "sequence": 13, "amount": "99900000", "fee": "99900", "release_amount": "99800100",
"body_hex": "…", "digest": "a4bc1c7d…63f4", "tx": "ceb9e3c2…0be7", "height": 2899 }
body_hex هي الرسالة الصادرة تمامًا كما يجب على الحراس (guardians) تجزئتها وتوقيعها، ومبلغ Transfer فيها هو release_amount. السجل أعلاه هو أول عملية حرق على chain 20: جرى حرق 0.999 zUSD باتجاه BSC USDT عند الارتفاع 2,899، ذهب منها 0.000999 إلى الخزينة وصُرف 0.998001.
rand bridge-burn <ASSET> <AMOUNT> <TO_CHAIN> <TOKEN> <TO> [--relayer-fee N] [--fee <RAND>] [--no-wait] [--yes]
rand bridge-message <SEQUENCE> # one outbound message, verbatim, for a guardian to sign
تتحقق المحفظة أولًا، من قراءة واحدة لحالة الجسر وسجل الرموز وقبل أي إثبات، من أن للسلسلة جسرًا، وأن ASSET موجود في السجل، وأن TOKEN على TO_CHAIN يسنده ويحتوي على AMOUNT على الأقل من المبلغ المقفل، وأن AMOUNT ورسم المُرحِّل (relayer) كلاهما عدد صحيح من وحدات الصرف. وعلى سلسلة ذات رسوم تطبع بعد ذلك رسم الجسر والمبلغ الذي سيُصرف، وتسأل قبل الإثبات (--yes يتجاوز السؤال). ثم تُثبت عملية الحرق حزمة واحدة: نحو 100 ثانية على حاسوب محمول ونحو 1.5 MB من الإثبات، وعلى chain 20، كما في كل حزمة منذ chain 17، يرافقها إثبات تفويض الإنفاق (بضع ثوانٍ، نحو 1.37 MB).
الرسوم على السك والحرق
جديد في fullnode v0.6.8، وقد حددتها على chain 20 مجموعة التكوين الأولي (genesis)
"bridge": { …, "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" } }
تأخذ السلسلة رسمًا على كل عملية سك وكل عملية حرق عبر الجسر، بالرمز المُعبَّر عنه عبر الجسر، على Rand، في صورة مذكرة محمية (shielded note) إلى عنوان الخزينة recipient. لا يتجاوز كل معدل 100 bps (1 %)، وعلى chain 20 كلاهما 10 bps، أي 0.1 %. الرسم هو ⌊amount · bps / 10⁴⌋ مقرَّبًا إلى الأسفل إلى وحدة صرف كاملة من الأصل الساند (10^(8 − source decimals): أي 100 وحدة لـ USDT ذي 6 منازل عشرية، و1 لعملة ذات 18 منزلة)، فيكون الرسم والباقي كلاهما وحدات كاملة، والرسم الذي يُقرَّب إلى الصفر لا ينشئ مذكرة (ledger/bridge_notes.rs، bridge_fee).
الإيداع (BridgeAttest) بإجمالي g | الحرق (BridgeBurn) بمبلغ amount | |
|---|---|---|
| الرسم | ⌊g · mint_bps / 10⁴⌋، إلى الأسفل حتى وحدة صرف | ⌊amount · burn_bps / 10⁴⌋، إلى الأسفل حتى وحدة صرف |
| جانب المستخدم | مذكرة إيداع بقيمة g − fee | حرق amount (burn_a == amount، دون باقٍ من الضيف)؛ ويُقِرّ المتن الصادر بأن release_amount = amount − fee، وتصرف نقطة النهاية هذا المبلغ بالضبط |
| مذكرة الخزينة | fee، والتعمية blake3("rand-bridge-fee-r-1" ‖ mu)، عند time الإيداع وأصله | fee، والتعمية blake3("rand-bridge-burn-fee-r-1" ‖ txid)، عند time الحزمة |
locked وtotal_supply | يرتفعان بمقدار g | ينخفضان بمقدار release_amount |
| يُفحص مقابل | حدود السك لكل أصل ساند والحد العام، على g | InsufficientBacking وNotReleasable على release_amount؛ ويجب أن تقع حصة المُرحِّل داخله (FeeExceedsAmount) |
المذكرتان كلتاهما مُصدَرتان ومسنودتان، لذا تظل المعادلة total_supply == Σ locked == custody قائمة: مذكرة الرسم هي zUSD كأي مذكرة أخرى، وتبقى قيمتها مقفلة على سلسلة المصدر إلى أن يحرقها أحد. أما BRIDGE_BURN_FEE، وهو 0.01 RAND تدفعها عملية الحرق في خانات RAND الخاصة بها، فلم يتغير وهو منفصل.
أول رحلة ذهاب وإياب على chain 20، كما تُقرأ من العقدة: إيداع 1 zUSD من BSC عند الارتفاع 1,664 (4f6e6c54…1a64) أعطى المودِع 0.999 zUSD وأعطى الخزينة 0.001؛ وحرقُ 0.999 zUSD عائدًا إلى BSC USDT عند الارتفاع 2,899 (التسلسل 13) أبقى 0.000999 zUSD وصرف 0.998001. وبعده (قراءة 2026-10-01) كان الأصل الساند BSC USDT يحتفظ بـ 22.001999 zUSD، وكان المعروض من الرمز، 99.001999 zUSD، مساويًا لمجموع أصوله الساندة.
كيف تجد الخزينة مذكراتها. لا غلاف لمذكرة الرسم: كل كلمة فيها علنية (المبلغ من المعاملة ومعدل التكوين الأولي، والأصل وtime من المعاملة، والتعمية مشتقة، والمالك هو recipient المحدد في التكوين الأولي). تعيد المحفظة التي عنوانها recipient بناء مذكرات الرسوم في المرور العلني نفسه الذي يعيد بناء مذكرات الإيداع، فتقرأ rand_getBridgeState.fees ومنازل الأصول الساندة العشرية مرة واحدة في كل مزامنة. والعقدة التي تكذب بشأن الرسوم لا تستطيع إلا أن تجعل مذكرة لا يُعثر عليها، ولا أن تقيِّد مذكرة. وتقيّم محفظة المودِع إيداعها بالطريقة نفسها: الإجمالي ناقص الرسم. لا يفعل ذلك إلا محفظة v0.6.8؛ أما الأقدم فلا تستطيع الإرسال على chain 20 أصلًا.
وبلا المجموعة، أي في chains من 14 إلى 19، لا رسم: يقيِّد الإيداع الإجمالي، وتصرف عملية الحرق amount. وحتى chain 20 كان رسم الجسر الوحيد هو اقتطاع نقطتي المصدر الخاص بهما بنسبة 10 bps من USDT عند الصرف، على السلسلة المضيفة؛ وقد ضُبط على 0 عند قطع chain 20. المجموعة مُلتزَم بها في تجزئة التكوين الأولي ومدمجة في جذر الجسر تحت rand-bridge-fees-1. ويتضمن docs/bridge.md §25 في fullnode الترميز.
تحصين الجسر
يحرس كلٌّ من locked وInsufficientBacking لكل أصل ساند الاسترداد، ولا يحدّان السك: فالإقرار الحاصل على نصاب حراس صالح يسك ما يقوله. وتسد ثلاث طبقات هذه الثغرة وتتيح رابعة إدراج رمز دون قطع سلسلة. لا شيء منها يغيّر نقاط النهاية على سلسلة المصدر ولا صيغة الإقرار السلكية ولا bridge-codec.
B1: حد سك لكل أصل ساند، وإيقاف مؤقت للسك
يُطبَّق tokens.mint_cap_per_day في التكوين الأولي لكل أصل ساند: تضبطه chain 20 على 10000000000000، أي 100,000 zUSD لكل أصل ساند في اليوم، كما فعلت كل سلسلة منذ 14. وبلا القواعد v2 (chain 14) يكون اليوم timestamp_ms / 86_400_000 من الطابع الزمني للكتلة؛ وفي ظل القواعد v2 (chains من 15 إلى 20) يُقاس الحد نفسه على نافذة متحركة طولها cap_window_secs من زمن الكتلة، ويكون الحد العام على مستوى السجل — 4,000 zUSD لكل 86,400 s على chain 20 — هو الأضيق (القواعد v2). والإيداع الذي يتجاوز الحد يُرفض بـ TokenError::MintCapExceeded قبل عمل توقيعات الحراس، ويصبح مقبولًا متى اتسعت النافذة من جديد.
bridge.pause_key في التكوين الأولي مفتاح Dilithium2 واحد، متميز عن كل حارس ما بعد كمّي. وفيه إجراءان بلا حزمة وبلا رسوم:
PauseMints { nonce, signature }— يوقعه مفتاح الإيقاف وحده. ولا يستطيع إلا الإيقاف.UnpauseMints { nonce, pq_signatures }— يحتاج نصابًا من الحراس ما بعد الكمّيين، ولا يكفيه مفتاح الإيقاف أبدًا.
كلاهما يحمل pause_nonce ويزيده، فلا يُعاد تشغيل أيٍّ منهما. وما دام السك موقوفًا يُرفض كل BridgeAttest تحويلي بـ BridgeError::MintsPaused؛ وتبقى عمليات الحرق وتدوير مجموعات الحراس مفتوحة، فلا يحبس الإيقاف الاسترداد أبدًا. وفي ظل القواعد v2 (chains من 15 إلى 20) يُرفض الإدراج أيضًا أثناء الإيقاف. والمقصود أن يُحفظ مفتاح الإيقاف على جهاز غير الذي تُحفظ عليه مفاتيح الحراس.
rand bridge-pause --sig <HEX or @file> [--no-wait] # no key file and no RAND needed
rand bridge-unpause --pq <JSON or @file> [--no-wait]
ما يغطيه الإيقاف: خداع برامج الحراس أو اختراقها لتشارك في توقيع سك مزوَّر بينما لا يفعل ذلك حائزو النصاب ما بعد الكمّي. وهو ليس دفاعًا ضد سرقة ذلك النصاب، الذي يستطيع رفع الإيقاف، وإدراج ما يصل إلى 32 أصلًا ساندًا لرمز واحد، لكلٍّ حدُّه اليومي، وفي ظل القواعد v2 استبدال المجموعة ما بعد الكمّية ومفتاح الإيقاف.
B2: طوابع زمنية محدودة للكتل
الطابع الزمني للكتلة في سلسلة مجسَّرة يحدد انتهاء صلاحية مجموعة الحراس ويوم حد السك، فلا يجوز أن يستطيع القائد (leader) القفز به. على سلسلة ذات جسر:
- قاعدة صلاحية، تُفحص عند إعادة التشغيل أيضًا:
timestamp_ms ≤ parent.timestamp_ms + 60_000، وإلاBlockError::TimestampLeap. ومع قاعدة الرجوع القائمة (BlockError::TimestampRewind) لا يتحرك زمن الكتلة إلا إلى الأمام، وبما لا يزيد على دقيقة في الكتلة. - قاعدة تصويت، وليست قاعدة إعادة تشغيل أبدًا: يمتنع المُصادِق (validator) عن التصويت لكتلة تتقدم على ساعته بأكثر من 15,000 ms.
لذلك يحتاج كل مُصادِق على سلسلة مجسَّرة إلى NTP: فمن ينحرف أكثر من 15 ثانية هو قائد معيب ومصوِّت معيب. وبينما تلحق سلسلة بالركب بعد توقف، قد يتقدم زمن الكتلة حتى 60 s في الكتلة الواحدة، فيمكن أن ينقلب يوم حد السك أسرع من يوم حقيقي — بحد أقصى حد يوم إضافي واحد لكل يوم كامل من التأخر المتراكم.
B3: توقيع مشترك Dilithium2 على كل عملية سك
يجب أن يحمل كل BridgeAttest، إيداعًا كان أو تدويرًا، إلى جانب نصاب ECDSA الخاص به نصابًا من توقيعات Dilithium2 على
M = "rand-bridge-pq-cosign-1" || rand_chain_id (u64 BE) || mu // 63 bytes
mu هو الملخص الذي يوقعه نصاب ECDSA؛ ومعرّف السلسلة يمنع توقيعًا مشتركًا على testnet من أن يصح على سلسلة Rand أخرى. وتُحمل التواقيع في pq_signatures الخاص بالإجراء، داخل ربط المعاملة، فلا يمكن نزعها أو استبدالها في نسخة. وتُفحص الأرخص قبل الأغلى: quorum(n) ≤ len ≤ n (PqNoQuorum)، والفهارس في تزايد صارم (PqIndexOrder)، وكل فهرس < n (PqIndexOutOfRange)، وكل توقيع طوله 2,420 بايت بالضبط (PqBadSignatureLength)، وأخيرًا أن كلًّا منها يتحقق تحت pq_guardians[index] (PqBadSignature). ويُحسب النصابان باستقلال. ويتوافق bridge.pq_guardians في التكوين الأولي فهرسيًا مع مجموعة ECDSA في التكوين الأولي (المجموعة 1 على chain 20). والتدوير من الحمولة 2 يحرك مجموعة ECDSA فقط؛ وبلا القواعد v2 تظل المجموعة ما بعد الكمّية ثابتة طوال عمر السلسلة، وفي ظل القواعد v2 لا يستبدلها إلا RotatePqGuardians (على chain 20 بصيغته V2، مع إثبات حيازة وتأخير يوم واحد). وفي chain 20 ثمانية مفاتيح، هي المفاتيح الثمانية نفسها في chains من 15 إلى 19؛ ويحتاج السك إلى ستة منها (كان في chain 14 ستة ويحتاج خمسة).
ما زال هذا التوقيع المشترك يقتصر على ربط معرّف السلسلة (chain id) وحده، وهو كذلك في السلسلة 20: فعلى خلاف رسائل الحوكمة الواردة أدناه، لم يُنقل خلف تجزئة التكوين الأولي (genesis hash)، لأن عمليات الحرّاس (guardian daemons) التي تُنتجه موجودة في مستودع آخر ولم تتغيّر. ونسخة من سلسلة بالمعرّف نفسه، إن نسخت كذلك مجموعة الحرّاس، ستقبل التواقيع المشتركة لإيداعٍ صُنعت للسلسلة الأصلية؛ فحزمة الرسوم الخاصة بمعاملة الإيداع مرتبطة بتجزئة التكوين الأولي في السلسلة 20، أما الإقرار (attestation) المضمَّن فيها فليس كذلك.
B4: إدراج رمز مُجسَّر بعد التكوين الأولي
لا يمكن لعنوان عادي أن ينشئ رمزًا بسلطة Bridge. هناك إجراءان، كلٌّ منهما على حزمة رسوم RAND عادية وتحت نصاب التوقيعات ما بعد الكمّية:
RegisterBridgedToken { name, symbol, salt, chain, token, decimals, nonce, pq_signatures }— رمز مُجسَّر جديد عند الفهرس التالي، بثمانية منازل عشرية على Rand، وتحت سقف السك في التكوين الأولي، مع أول غطاء له. الرسوم: أساس الحزمة مضافًا إليهregistration_feeالخاص بالسجل (1 RAND في السلسلة 20، أي 1.001 RAND؛ وتحرق السلسلة 20 رسم التسجيل بدلًا من دفعه إلى المقترِح).ListBacking { token_index, chain, token, decimals, nonce, pq_signatures }— غطاء إضافي لرمز مُجسَّر مسجَّل، يبدأ عندlocked = 0. الرسوم: أساس الحزمة.
يحمل كلاهما list_nonce ويزيده (وإلا BadListNonce { expected, got })، ويجريان الفحوص نفسها: للسلسلة مُصدِر (emitter) مسجَّل (BridgeError::NoEmitter)، والزوج (chain, token) لا يغطّي شيئًا بعد، وعدد الأغطية أقل من 32، وdecimals ≤ 18.
rand token register-bridged --name … --symbol … --salt <hex32> --chain N --token <hex32> --decimals N --pq <JSON or @file>
rand token list-backing --asset <index> --chain N --token <hex32> --decimals N --pq <JSON or @file>
قاعدتان للترتيب. يوقّع الحرّاس التسجيل أولًا، وينتظرون إتمامه، ويقرؤون الفهرس المُسنَد، ثم يوقّعون بعد ذلك فقط الإدراجات التي تسمّيه — فتسجيل رمز أصلي (native) لا يحتاج إلى إذن، وقد يجد الإدراج الموقَّع مسبقًا أن فهرسه قد أُخذ. ويُدرَج الرمز على Rand قبل أن يفعّله setToken على نقطة المصدر: ففي الترتيب المعاكس قد يُقفَل إيداع قبل أن يكون لدى Rand غطاء يُقيَّد به، وسيُرفض إقراره بالخطأ UnlistedToken إلى أن تلحق Rand بالركب.
رسائل الحوكمة
بايتات ثابتة، وليست bincode أبدًا؛ وكل الأعداد الصحيحة بترتيب big-endian:
M_pause = "rand-bridge-pause-1" || chain_id u64 || nonce u64
M_unpause = "rand-bridge-pq-unpause-1" || chain_id u64 || nonce u64
M_list = "rand-bridge-pq-list-1" || chain_id u64 || nonce u64 || token_index u32 || chain u16 || token [32] || decimals u8
M_register = "rand-bridge-pq-register-1" || chain_id u64 || nonce u64 || u8 len || name || u8 len || symbol || salt [32] || chain u16 || token [32] || decimals u8
M_rotate_pq = "rand-bridge-pq-rotate-pq-1" || chain_id u64 || rotation_nonce u64 || count u32 || key [1312] … // rules v2
M_rotate_pause = "rand-bridge-pq-rotate-pause-1" || chain_id u64 || rotation_nonce u64 || key [1312] // rules v2
تحمل M_pause/M_unpause القيمة pause_nonce، وتحمل M_list/M_register القيمة list_nonce، وتحمل M_rotate_pq/M_rotate_pause القيمة rotation_nonce. والقيمة count هي عدد المفاتيح لا طولًا بالبايت؛ وكل مفتاح هو 1,312 بايتًا خامًا من المفتاح العام لـ Dilithium2، بالترتيب الذي ستكون عليه المجموعة الجديدة.
في السلسلة 20 ترتبط كل رسالة بتجزئة التكوين الأولي (genesis binding_domain: 1): يصير رقم الإصدار في الوسم 2، وتقع تجزئة التكوين الأولي البالغة 32 بايتًا بين الوسم ومعرّف السلسلة، ويبقى الذيل كما هو — M'_pause = "rand-bridge-pause-2" || genesis [32] || chain_id u64 || nonce u64، وكذلك rand-bridge-pq-unpause-2 وrand-bridge-pq-list-2 وrand-bridge-pq-register-2 وrand-bridge-pq-rotate-pq-2 وrand-bridge-pq-rotate-pause-2 (bridge/gov.rs). فمفتاح الإيقاف المؤقت أو النصاب الذي يوقّع لسلسلة واحدة لا يوقّع بعدها لأي سلسلة أخرى تشاركها معرّف السلسلة ومفاتيحها. ويُرفض في السلسلة 20 ملف موقَّع بصيغة …-1 (BadPauseSignature، PqBadSignature). أما السلاسل من 14 إلى 19 فتستخدم تخطيطات …-1 الواردة أعلاه، بايتًا ببايت.
القواعد v2: التدوير، والسقوف المتحركة، ومنع الإدراج أثناء الإيقاف المؤقت
مجموعة اختيارية داخل قسم bridge في التكوين الأولي. لم تكن موجودة في السلسلة 14؛ وكل سلسلة منذ 15 تتضمنها، والسلسلة 20 بالقيم نفسها:
"bridge": { …, "rules_v2": { "global_mint_cap_per_window": 400000000000, "cap_window_secs": 86400 } }
- تدويران تحت نصاب التوقيعات ما بعد الكمّية. يستبدل
RotatePqGuardians { new_pq_guardians, nonce, pq_signatures }مجموعة التوقيعات ما بعد الكمّية كلها؛ ويستبدلRotatePauseKey { new_pause_key, nonce, pq_signatures }مفتاح الإيقاف المؤقت. كلاهما بلا حزمة وبلا رسوم مثلPauseMints، ويوقّعهما كليهما المجموعة الحالية ما بعد الكمّية، ويحمل كلٌّ منهماrotation_nonceويزيده، فيخوّل نصاب موقَّع واحد تدويرًا واحدًا فقط. ويجب أن يساوي طولُ المجموعة الجديدة طولَ مجموعة ECDSA الحالية، ومفاتيحها بطول 1,312 بايتًا، بلا تكرار ولا أن يكون أحدها مفتاح الإيقاف المؤقت. ولا توجد مهلة سماح: فالمجموعة أو المفتاح المستبدَل لا يوقّع شيئًا بعد ذلك. وفي السلسلة 20 تُرفض إجراءات v1 هذه، ويجب أن يثبت التدوير حيازة المفاتيح الجديدة وأن ينتظر يومًا (أدناه). - سقوف النافذة المتحركة. يُقاس
mint_cap_per_dayلكل غطاء على نافذة متحركة مدتهاcap_window_secsبدلًا من اليوم التقويمي، ويغطي سقف ثانٍ على مستوى السجل،global_mint_cap_per_window، كل أغطية كل الرموز معًا (TokenError::GlobalMintCapExceeded). وهو في السلسلة 20 يساوي 400,000,000,000 وحدة، أي 4,000 zUSD لكل 86,400 s. وبموجب قاعدة اليوم، كان السقف عند 23:59:59 ثم عند 00:00:01 ضعف السقف في ثانيتين؛ أما بموجب النافذة فتُرفض الثانية. - لا إدراج أثناء الإيقاف المؤقت. يُرفض
RegisterBridgedTokenوListBackingبالخطأMintsPausedما دامmint_paused. وتبقى عمليات الحرق والتدويران كلاهما مفتوحة.
التدوير: إثبات الحيازة ومهلة يوم واحد
تركت القواعد v2 ثلاثة أمور مفتوحة: لم يكن ما يثبت أن أحدًا يحوز المفاتيح الجديدة (فخطأ مطبعي، أو مفتاح لا يحوزه أحد، كان سيُعطّل نصاب التوقيعات ما بعد الكمّية إلى الأبد)، ونفذ التدوير في الكتلة التي وصل فيها، ولم يكن ما يوقف تدويرًا عدائيًا. وتعالجها مجموعة ثانية اختيارية إلى جانب rules_v2 (وتتطلبها)، جديدة في v0.6.8 (التدقيق v6، BRG-14). والسلسلة 20 تفعّلها:
"bridge": { …, "rotation": { "delay_secs": 86400, "needs_possession": true } }
- الحيازة. يجب أن يكون التدوير
RotatePqGuardiansV2 { new_pq_guardians, possession, nonce, pq_signatures }أوRotatePauseKeyV2 { new_pause_key, possession, nonce, pq_signatures }: وpossession[i]هو توقيع Dilithium2 الخاص بالمفتاح الجديدiعلى الرسالة ذاتها التي يوقّعها النصاب (M_rotate_pqأوM_rotate_pause، بصيغة…-2في السلسلة 20)، توقيع واحد لكل مفتاح بترتيب المفاتيح، بطول 2,420 بايتًا لكل منها. وتُرفض إجراءات v1 بالخطأPossessionRequired. ويُرفض العدد أو الطول الخاطئ قبل التحقق من النصاب، وتوقيع الحيازة السيئ (BadPossession { index }) بعده. - مهلة. يُسجَّل التدوير المقبول على أنه معلّق، مع
effective_at_secs = block time + delay_secs، ويسري في نهاية أول كتلة يبلغه طابعها الزمني. وفي السلسلة 20 تساوي المهلة 86,400 s، أي يومًا. وإلى ذلك الحين توقّع المجموعة القديمة مع كل سك ويوقف مفتاح الإيقاف المؤقت القديم. ويستهلك قبول التدويرrotation_nonceواحدًا ويستهلك تفعيله آخر، فيغادر إقرار السك المجمَّع تحت المجموعة القديمة المجمَّعَ حين تتغير المجموعة. ويجوز أن يكون معلّقًا تدوير واحد من كل نوع في آن واحد (RotationPending { effective_at_secs }). - الإلغاء. يُسقط
CancelRotation { kind, nonce, signature }(بلا حزمة وبلا رسوم) التدوير المعلّق من النوعkind(0 = مجموعة التوقيعات ما بعد الكمّية، 1 = مفتاح الإيقاف المؤقت). ويوقّعه مفتاح الإيقاف المؤقت الحالي، وهو المفتاح الوحيد المحفوظ بمعزل عن النصاب، على"rand-bridge-cancel-rotation-2" || genesis [32] || chain_id u64 || rotation_nonce u64 || kind u8في السلسلة 20، ويستهلك الـ nonce. وعليه فلا يستطيع نصاب مُستولى عليه تدوير المجموعة في أقل من يوم، ويستطيع حامل مفتاح الإيقاف المؤقت إيقافه خلال ذلك اليوم؛ أما مفتاح الإيقاف المؤقت القادم فلا يستطيع إلغاء وصوله بنفسه.
الموقِّع هو rand-node bridge-gov (rotate-pq وrotate-pause وcancel-rotation، لكلٍّ منها خطوات message وsign وsubmit؛ ويعيد submit التحقق من كل سطر حيازة قبل الإرسال). ويقدّم rand_getBridgeState الحقلين rotation_rules وpending_rotations؛ وكلاهما null في سلسلة بلا هذه المجموعة (السلاسل من 14 إلى 19). ولا يتأثر تدوير مجموعة ECDSA للحرّاس (الحمولة 2): فله قواعده ومهلة سماحه الخاصة. ولم يُجرَ أي تدوير على السلسلة 20.
ما انتقل من السلسلة 14
قُطعت السلسلة 15 من موضع الجسر في السلسلة 14 (المُصدِرون أنفسهم ونقاط المصدر نفسها)، وقُطعت السلاسل 16 و17 و18 كلٌّ من سابقتها بالطريقة نفسها. وقُطعت السلسلة 19 من موضع السلسلة 18 مع تغيير واحد: مُصدِرو Ethereum وBSC وTron فيها هم نقاط النهاية التي أُعيد نشرها في 2026-09-30 (0x7af6b17047c1db6cb54347fdea45cf9179075bfa للسلسلتين 2 و3، و0x6410797df959987a5baf65b5fab97edeb34d5163 للسلسلة 4). ولم تثق بها السلسلة 18 قط، فلم يُسَك منها شيء هناك. وقُطعت السلسلة 20 من لقطة للسلسلة 19 عند الارتفاع 7,258، مع إيقاف الجسر أولًا، وتُبقي مُصدِري السلسلة 19 وحرّاسها ومفاتيحها ما بعد الكمّية ومفتاح الإيقاف المؤقت دون تغيير؛ وما تضيفه هو مجموعتا الرسوم والتدوير. ولم يتغير مُصدِر Solana ولا المُصدِر على جهة Rand منذ السلسلة 14. وما يحمله التكوين الأولي للسلسلة 20:
- الحيازة. 99 zUSD، على هيئة ملاحظتَي zUSD من التكوين (10 و89 zUSD)، مقابل 22 USDT مقفلة على Ethereum و22 USDT على BSC و30 USDT على Tron و25 USDT على Solana، و0 على كل غطاء من USDC: هذه قيمة
lockedفي السلسلة 19 عند اللقطة، وقد طابقت حيازة نقاط النهاية. تساويtotal_supply == Σ lockedحيازةَ نقاط النهاية عند الكتلة 0. التاريخ: كانت الـ10 zUSD المتبقية على السلسلة 14 ملاحظة تكوين واحدة على السلاسل من 15 إلى 19؛ وفي تكوين السلسلة 18 كانت مقفلة على هيئة 9 USDT على Tron وواحدة على Solana، وفي تكوين السلسلة 19 على هيئة 10 على Solana بعد إعادة توازن جرت على السلسلة 18. أما الـ89 الأخرى فهي صافي إيداعات السلسلة 19 بعد خصم عمليات الحرق فيها. - فهرس مجموعة الحراس 1، وهو الفهرس الذي تقف عنده نقاط النهاية المصدرية أصلًا؛ ويجب أن يحمل التدوير التالي لـ ECDSA الفهرس 2. أما المفاتيح الثمانية ما بعد الكمّية ومفتاح الإيقاف المؤقت فهي مفاتيح السلسلة 15 دون تغيير.
burn_sequence13: انتهت عمليات الحرق في السلسلة 14 عند 6، وبدأ تكوين السلاسل من 15 إلى 18 عند 7، وأجرت السلسلة 18 عملية حرق واحدة (7)، وبدأت السلسلة 19 عند 8. أجرت السلسلة 19 خمس عمليات حرق بالتسلسلات من 8 إلى 12، فتبدأ السلسلة 20 عند 13، فلا ترى أي نقطة نهاية أو خدمة خلفية مفهرسة بالتسلسل أيَّ تسلسل مرتين. وقد أخذت أول عملية حرق في السلسلة 20 التسلسل 13.- منع إعادة التشغيل. ما زال سكّ (mint) من سلسلة سابقة يجتاز نصاب ECDSA، لكن توقيعه المشترك ما بعد الكمّي يربط معرّف تلك السلسلة، ولذلك يُرفض على السلسلة 20. لم يكن في تكوين السلسلة 15 حدٌّ أدنى لإعادة التشغيل الوارد (كانت
min_inbound_sequenceتساويnull)، فكان الاعتماد هناك على التوقيع المشترك وعلى أن الحراس لا يوقّعون توقيعًا مشتركًا على قفل قديم لمعرّف السلسلة الجديدة. ومنذ السلسلة 16 يضع التكوين حدًّا كهذا: لكل سلسلة مصدرية، أدنىsequenceيجوز أن يحمله إثبات تحويل صادر عن مُصدِر تلك السلسلة. وحدّ السلسلة 20 هو{ "2": 3, "3": 3, "4": 3, "5": 7 }: أي التسلسل الصادر التالي لكل نقطة نهاية عند أخذ لقطة السلسلة 19، وكل قفل سابق قد سُكّ على السلسلة 19. (كان حد السلسلة 18 هو{ "2": 2, "3": 2, "4": 2, "5": 2 }؛ وحد السلسلة 19 هو{ "2": 1, "3": 1, "4": 1, "5": 4 }، حيث كان التسلسل 0 على كل نقطة نهاية أُعيد نشرها هو قفل المشغّل نفسه، وهو قفل يجب ألا يُسكّ أبدًا.) يُرفض أي تحويل دون الحد بالخطأBelowReplayFloorمهما كان النصاب والتوقيع المشترك اللذان يحملهما. ولا يُحكم على تدوير مجموعات الحراس بهذا الحد. الحد ثابت عند التكوين، ومُلتزَم به في تجزئة التكوين، ومضمَّن في جذر الجسر.
ترتيب الإطلاق على السلسلة 14 (تاريخي)
كيف بدأ الجسر العمل على السلسلة 14، وهي شبكة testnet سابقة، في سبتمبر 2026:
- إنشاء السلسلة بقسم
bridge(يشملpq_guardiansوpause_key) وقسمtokensيضبطregistration_feeوmint_cap_per_dayدون إدراج أي رمز. تم في 2026-09-20. - تفعيل الصنبور (faucet) كي تتمكن محفظة المُنشِر من الحصول على RAND. تم.
- يسجّل المُنشِر zUSD مع أول غطاء له بتوقيع النصاب على
M_registerعندlist_nonce = 0. تم عند الارتفاع 256. - يقرأ الحراس الفهرس ثم يوقّعون رسائل
ListBackingالست المتبقية، وتُرسل واحدة تلو الأخرى. تم: يقرأlist_nonceالقيمة 7. - لكل غطاء: يُدرج على Rand قبل
setTokenعلى نقطة النهاية المصدرية. - تدوير مجموعة الحراس (المجموعة 0 ← المجموعة 1، بتوقيع 5 من 6 من المجموعة الحالية) على نقاط النهاية المصدرية الأربع، ثم الإثبات نفسه على Rand. قرأت Rand المجموعة 0 طوال عمر السلسلة 14؛ وتبدأ السلسلة 15 عند المجموعة 1.
- رحلة ذهاب وإياب حقيقية لكل سلسلة مصدرية بمبالغ صغيرة — إيداع عبر الجسر، وسكّ zUSD، وتحويل، وحرق عائد — ومطابقة
lockedلكل غطاء (rand_getTokenSupply) مع حيازة نقطة النهاية.
الحالة العامة
rand_getBridgeState:
{
"enabled": true,
"emitter": "01…", "emitters": { "2": "02…" },
"guardian_set_index": 0, "guardians": ["aabb…"],
"pq_guardians": ["…"],
"mint_paused": false, "pause_nonce": 0, "list_nonce": 0, "pause_key": "…",
"registration_fee": "1000000000",
"burn_sequence": 1,
"rotation_nonce": 0,
"rules_v2": { "global_mint_cap_per_window": "400000000000", "cap_window_secs": 86400,
"global_minted_in_window": "…", "global_mint_headroom": "…" },
"min_inbound_sequence": null,
"rotation_rules": null, "pending_rotations": null,
"fees": null,
"assets": [ … ]
}
مفاتيح pq_guardians هي مفاتيح Dilithium2 العامة، حجم كل منها 1,312 بايت بصيغة سداسية عشرية. لم يعد هناك next_index: يُدرج الرمز المجسَّر، تحت فهرس حدّده تسجيله، قبل أن يمكن إيداعه، فلا يبقى فهرس يُتنبأ به. أما rotation_nonce فهو ما يجب أن يحمله M_rotate_pq/M_rotate_pause/M_cancel التالي؛ وقيمة rules_v2 هي null على سلسلة بلا هذه المجموعة (السلسلة 14)، وقيمة min_inbound_sequence هي null على سلسلة بلا حد أدنى لإعادة التشغيل (السلسلة 15 وما قبلها)؛ وعلى السلسلة 20 هي { "2": 3, "3": 3, "4": 3, "5": 7 }. وقيم rotation_rules وpending_rotations وfees هي null على سلسلة بلا هذه المجموعات (السلاسل من 14 إلى 19). يبيّن المثال أعلاه الشكل لا قيم السلسلة 20: فقد بدأت السلسلة 20 عند guardian_set_index 1 وburn_sequence 13، وتقدّم "rotation_rules": { "delay_secs": 86400, "needs_possession": true } و"pending_rotations": [] و"fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" }. وتجيب السلسلة التي لا تحتوي قسم bridge بـ {"enabled": false}.
لا أرصدة: القيمة المجسَّرة ملاحظات (notes)، وتقرأ المحفظة ما تحتفظ به بالأمر rand asset-balance [INDEX] من ملاحظاتها هي. يلتزم جذر الجسر بالمُصدِرين، ومجموعات الحراس، والمُلخّصات المنفَقة، وتسلسل الحرق، ومجموعة الحراس ما بعد الكمّية، ومفتاح الإيقاف المؤقت وعلامة الإيقاف المؤقت ونونسات الحوكمة (اثنتان، وrotation_nonce في ظل القواعد v2)، والحد الأدنى لإعادة التشغيل حيث يضع التكوين حدًّا، وعلى السلسلة 20 قواعد التدوير مع أي تدوير معلّق ومجموعة الرسوم؛ ويُدمج في جذر الحالة فقط على سلسلة فيها جسر. أما سجل الرموز، مع locked لكل غطاء، فيُلتزم به منفصلًا في tokens_root. ويقدّم GET /bridge وGET /bridge/assets في المستكشف الحالة نفسها، والثاني مع إجماليات الإيداع والحرق لكل غطاء كسلاسل نصية بثمانية منازل عشرية؛ ويقدّم GET /tokens وGET /tokens/:id السجل.
ما هو علني وما هو مخفي
| ماذا | من يراه |
|---|---|
| مبلغ الإيداع وفهرس الأصل وعنوان المستلم | الجميع، في تلك المعاملة وحدها |
| أي ملاحظة ينفقها المستلم لاحقًا | لا أحد |
| مبلغ الحرق والأصل والعملة والوجهة ورسوم المُرحِّل | الجميع — فالحراس يحتاجون إليها |
| مبلغ ملاحظة رسوم الجسر وأصلها ومالكها، وهو الخزينة (السلسلة 20) | الجميع: تُعاد بناؤها من حقول علنية |
| أي ملاحظات موّلت عملية حرق | لا أحد |
| الأرصدة المجسَّرة | حاملها فقط، أو حامل مفتاح العرض |
مجموعات الحراس والمُصدِرون والسجل مع locked لكل غطاء وسجل الحرق | الجميع |
الثغرات المعروفة
من قائمة fullnode نفسها: لا يُدفع لأي مُرحِّل على Rand؛ وظرف الإيداع غير مرتبط بشيء، فيستطيع مُرحِّل خبيث ختم بيانات عشوائية، ولهذا تعيد المحفظة بناء ملاحظات الإيداع من الحقول العلنية بدلًا من ذلك؛ ولم تُعَد بعدُ بناء متجهات الاختبار المشتركة على مستوى دفتر الحسابات فوق مجمّع الملاحظات؛ ولا يزال bridge-burn لا يعلم بسياسة نقطة النهاية الوجهة نفسها إلا بعد دفع تكلفة إثبات؛ وتُسمح الطوابع الزمنية للكتل المساوية لطابع الكتلة الأم على سلسلة مجسَّرة، فيمكن لقادة متواطئين إبقاء مجموعة حراس مُستبدَلة ضمن فترة السماح؛ وسجل الحرق في الذاكرة غير محدود؛ ولا يوجد عميل خفيف ولا تحقق على السلسلة من حالة السلسلة المصدرية، وليس مخططًا لهما.