Rand Protocol
مستندات
راهنماها

پل

پل نگهبان‌محور آن‌گونه که ساخته شده است: قفل در زنجیرهٔ مبدأ، گواهی نگهبان همراه با امضای مشترک Dilithium2، یک BridgeAttest که یک note را واریز می‌کند، و یک BridgeBurn تک‌بسته‌ای که برداشت را انجام می‌دهد؛ همراه با قالب انتقال داده، هفت پشتوانهٔ zUSD، کارمزدهای zUSD در mint و burn، سقف‌های mint و توقف، چرخش‌های rules-v2 با اثبات تصاحب و تأخیر آن‌ها، و آنچه اکنون روی chain 20 فعال است.

از این صفحه برای یکپارچه‌سازی با پل استفاده کنید: برای ساختن یک واریز در زنجیره‌ی مبدأ، ثبت یک گواهی در Rand، سوزاندن یک دارایی پل‌شده برای بازگرداندن آن به بیرون، و خواندن وضعیت عمومی پل. زنجیره‌ی ۲۰، testnet فعلی (فعال از 2026-10-01 06:17 UTC، fullnode نسخه‌ی v0.6.8)، با بخش bridge ساخته شد: هشت نگهبان با شروع از شاخص مجموعه‌ی نگهبانان 1، هشت نگهبان پساکوانتومی، یک کلید توقف، صادرکننده‌ها برای Ethereum، BSC، Tron و Solana (نقاط پایانیِ سه‌تای نخست در 2026-09-30 دوباره مستقر شدند)، گروه rules-v2 که سقف سراسریِ ضرب آن 4,000 zUSD در هر 24 ساعتِ لغزان است، یک کف بازپخش ورودی، و دو گروه جدید در v0.6.8: کارمزدها به میزان 10 bps بر هر ضرب و هر سوزاندن که به zUSD دریافت می‌شود، و قواعد چرخش که چرخش مجموعه‌ی پساکوانتومی یا کلید توقف را ملزم می‌کند مالکیت کلیدهای جدید را اثبات کند و یک روز صبر کند. برخلاف زنجیره‌ی ۱۴، جنسیس آن zUSD را با هر هفت پشتوانه فهرست می‌کند و وثیقه‌ای را که در هر برش از زنجیره‌ی ۱۴ تا امروز منتقل شده است با خود دارد (منتقل‌شده از زنجیره‌ی ۱۴). rand_getAssets و /bridge/assets در explorer مقدار 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):

زنجیرهشناسه
Rand1
Ethereum2
BSC3
Tron4
Solana5

نگهبانان کمیته‌ای از کلیدهای secp256k1 هستند؛ هویت هر نگهبان ۲۰ بایت آخرِ 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 است. مجموعه‌ی پساکوانتومی و کلید توقف با اقدام‌های خودشان چرخش می‌کنند که در زنجیره‌ی ۲۰ یک تأخیر یک‌روزه را پشت سر می‌گذارند: در ادامه.) اگر n·2/3 + 1 نگهبان تبانی کنند می‌توانند ارزشی دلخواه ضرب کنند؛ تقویت امنیت پل مقدار و سرعت آن را محدود می‌کند، اما این امکان را از میان نمی‌برد.

اتصال تراکنش. تراکنش Rand هیچ امضایی ندارد: سوزاندن با اثبات STARK بسته‌ی آن مجاز می‌شود. از زنجیره‌ی ۱۴ به بعد، اثبات هر بسته روی Transaction::binding ساخته و در برابر آن تأیید می‌شود — هشی از کل تراکنش (شناسه‌ی زنجیره، تنها بسته با چهار پاکت آن، و اقدام) که فقط بایت‌های اثبات در آن خالی شده است. از زنجیره‌ی ۱۷ به بعد هر بسته دو اثبات دارد، اثبات بسته و اثبات مجوز خرج: این اتصال هر دو را خالی می‌کند، auth_commit بسته را نگه می‌دارد و هر دو در برابر آن تأیید می‌شوند. نسخه‌ی کپی‌شده‌ی یک سوزاندنِ در انتظار که to، relayer_fee یا (to_chain, token) در آن تغییر کرده باشد، یا یک attest که time یا پاکت آن تغییر کرده باشد، اثبات را رد می‌کند (TxError::InvalidBundleProof). در زنجیره‌ی ۲۰ این اتصال هش جنسیس را نیز در بر دارد (binding_domain: 1 در جنسیس: rand-tx-bind-2 روی هش جنسیس، شناسه‌ی زنجیره، بسته و اقدام)، بنابراین اثباتی که برای زنجیره‌ی دیگری با همان شناسه‌ی زنجیره ساخته شده باشد نیز تأیید نمی‌شود. کیف‌پول‌ها و رله‌کننده‌ها باید باینری ساخته‌شده برای زنجیره‌ای را اجرا کنند که به آن ارسال می‌کنند: v0.6.8 در زنجیره‌ی ۲۰ (v0.6.7 در زنجیره‌های ۱۸ و ۱۹)؛ یک rand قدیمی‌تر اثبات‌ها و امضاهایی می‌سازد که زنجیره‌ی ۲۰ نمی‌پذیرد.

دیمون‌های نگهبان و رله‌کننده بخشی از full node نیستند؛ full node کدک مشترک و نیمه‌ی دریافت‌کننده را در خود دارد.

قالب سیمی

هر عدد صحیح big-endian است؛ نسخه‌ی سیمی 1 است.

پاکت گواهی

موقعیتطولفیلد
01version = 1
14guardian_set_index
51n_sigs
666·n_sigsامضاها: index (1)، r (32)، s (32)، v (1)
6 + 66·n_sigs…body

سرآیند بدنه — 51 بایت، سپس بار داده

موقعیتطولفیلد
04timestamp
44nonce
82emitter_chain
1032emitter_address
428sequence
501consistency_level
51…payload

بار داده‌ی Transfer — شناسه‌ی 1، 133 بایت

موقعیتطولفیلد
01id = 1
132amount (u256؛ Rand از 128 بیت پایین استفاده می‌کند، و یک واریز باید در u64 یک یادداشت هم جا شود)
3332token_address
652token_chain
6732to — هش گیرنده، در ادامه ببینید
992to_chain
10132fee (u256): سهم رله‌کننده از amount

بار داده‌ی GuardianSetUpgrade — شناسه‌ی 2: id (1)، new_index (4)، n (1)، سپس n کلید ۲۰ بایتی.

آدرس‌های Ethereum، BSC و Tron ۲۰ بایت هستند که در شکاف‌های ۳۲ بایتی از چپ با ۱۲ بایت صفر پر می‌شوند؛ آدرس Solana تمام شکاف را پر می‌کند. یک Transfer باید از صادرکننده‌ی ثبت‌شده‌ی زنجیره برای emitter_chain بیاید، و token_chain باید با زنجیره‌ی صادرکننده برابر باشد. یک گواهی حداکثر 16 KiB است. هیچ‌یک از اقدام‌های تقویت امنیت پل قالب سیمی، کدک یا نقاط پایانیِ مستقرشده در زنجیره‌های مبدأ را تغییر نداد: هم‌امضاهای پساکوانتومی و پیام‌های حاکمیتی در تراکنش Rand حمل می‌شوند، نه در گواهی.

گیرنده‌ی ۳۲ بایتی

یک آدرس محافظت‌شده 1,216 بایت است و سیم فقط جای 32 بایت دارد. فیلد to هشی از آن را حمل می‌کند:

to = blake3("rand-shielded-recipient" || pk_bytes || kem_ek)     // ShieldedAddress::recipient_hash

واریزکننده هش را در زنجیره‌ی مبدأ مشخص می‌کند، نگهبانان آن را امضا می‌کنند، ارسال‌کننده آدرس کامل را در اقدام BridgeAttest می‌گذارد، و دفتر کل هش را دوباره محاسبه می‌کند و ناسازگاری را رد می‌کند — وگرنه ارسال‌کننده می‌توانست تعیین کند چه کسی واریزِ دیگری را دریافت کند. رابط‌های کاربری زنجیره‌ی مبدأ باید این شکل را بپذیرند؛ کیف‌پول آن را چاپ می‌کند. زنجیره‌ی ۱۱ مدت کوتاهی شکاف را مستقیماً شناسه‌ی گیرنده قرار داد؛ آن شکل آدرس با زنجیره‌ی ۱۲ برگردانده شد (حساب‌ها و آدرس‌ها).

یک توکن، چند پشتوانه: 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

در زنجیره‌ی ۲۰ zUSD شاخص 1 است، rpl1xtj6k2x8strx8c2d5fjs50ltztck5ykms4ve7nmzmstf6fhn0v0spelqtx، با نام “Shielded USD” و 8 رقم اعشار، در فایل جنسیس فهرست شده است، مانند زنجیره‌های ۱۵ تا ۱۹، با همان نام، نماد و salt که در زنجیره‌ی ۱۴ داشت، بنابراین شناسه‌ی دارایی آن (32e5ab28c782c663e14da2650a3feb12f16a12db85599f4f62dc169d26f37b1f) و شناسه‌ی rpl1… آن بدون تغییر منتقل شد (explorer: GET /tokens/1). در زنجیره‌ی ۱۴ در ارتفاع 256 با تراکنش register_bridged_token ثبت شده بود.

Backing { chain: u16, token: [u8; 32], decimals: u8, locked: u64, minted_today: u64, mint_day: u32 }

یک جفت (chain, token) در کل ثبت‌نامْ پشتوانه‌ی حداکثر یک توکن است (TokenError::BackingTaken)؛ یک توکن ۱ تا ۳۲ پشتوانه دارد؛ decimals تعداد ارقام اعشارِ خودِ سکه‌ی مبدأ است، از 0 تا 18، نه 8 رقمِ توکن پل‌شده. پشتوانه‌ی ادغام‌شده یک ریسک اعلام‌شده است: هفت سکه با نرخ برابر به zUSD و از آن قابل تبدیل‌اند، بنابراین جدا شدن قیمت (depeg) هر یک از آن‌ها سهم همه‌ی دارندگان zUSD را رقیق می‌کند، و هیچ سقفی در سمت Rand برای سهم یک پشتوانه وجود ندارد.

zUSD یک سبد است. دارنده ریسک هر پشتوانهٔ فهرست‌شده را بر دوش می‌گیرد، نه فقط ریسک ارزی که واریز کرده است: هر zUSD ادعایی است بر استخر، و بر هر پشتوانه‌ای که locked کافی داشته باشد. هیچ سازوکاری برای قیمت‌گذاری پشتوانه‌ها وجود ندارد. قفل‌کردن ۱ واحد از هر ارز فهرست‌شده، ۱ zUSD ضرب می‌کند و یک سوزاندن می‌تواند هر پشتوانه‌ای را نام ببرد که locked آن پوشش‌دهندهٔ مبلغ باشد؛ بنابراین ارزی که پیوند قیمتی خود را از دست داده می‌تواند به نرخ برابر واریز شود و zUSD به‌سوی ارزی که پیوند خود را حفظ کرده سوزانده شود، تا زمانی که پشتوانه‌های سالم تخلیه شوند. زیان با پنجره‌های غلتانِ ضرب و با نگهداریِ واقعاً قفل‌شده محدود می‌شود: در chain 20 در هر ۲۴ ساعت حداکثر ۴٬۰۰۰ zUSD در مجموع همهٔ پشتوانه‌ها و ۱۰۰٬۰۰۰ برای هر پشتوانه قابل ضرب است (rules v2)، و از طریق یک پشتوانهٔ سالم بیش از آنچه آن پشتوانه در اختیار دارد خارج نمی‌شود. پنجره نرخ را محدود می‌کند، نه مجموع را: زمان لازم برای توقف ضرب را فراهم می‌کند، که واریزهای جدید ارز ضعیف را متوقف می‌کند، در حالی که سوزاندن‌ها باز می‌مانند. هیچ سقف بازخرید برای هر پشتوانه و هیچ توقف جداگانه برای هر پشتوانه وجود ندارد. در chain 14، پیش از rules v2، توقف‌دهنده و سقف‌های نرخ هر توکن در نقاط پایانی مبدأ تنها محدودیت بودند.

ناوردا: total_supply == Σ backings.locked. TokenRegistry::lock (یک BridgeAttest) مبلغ تصدیق‌شده را در یک فراخوانی هم به locked پشتوانه و هم به total_supply توکن می‌افزاید؛ TokenRegistry::release (یک BridgeBurn) مبلغ آزادشده را از هر دو کم می‌کند. هیچ چیز دیگری هیچ‌یک از این دو فیلد را نمی‌نویسد، و برگ توکن locked هر پشتوانه را در tokens_root متعهد می‌کند، پس دو گره‌ای که دربارهٔ یکی از آن‌ها اختلاف داشته باشند در ریشهٔ حالت از هم جدا می‌شوند. نقاط پایانی مبدأ هنگام قفل‌کردن کارمزدی نمی‌گیرند و در آزادسازی کل مبلغ تصدیق‌شده را کسر می‌کنند، پس locked هر پشتوانه، با احتساب پیام‌های در راه، برابر با نگهداریِ آن نقطهٔ پایانی است. کارمزدها در chain 20 این ناوردا را نمی‌شکنند: کارمزد یک note از نوع zUSD است که صادرشده و پشتوانه‌دار می‌ماند، پس واریز مبلغ ناخالص را قفل می‌کند و سوزاندن فقط amount − fee را آزاد می‌کند. در chain 20 کسر ۱۰ bps نقطهٔ پایانی روی USDT هنگام آزادسازی در لحظهٔ برش روی ۰ تنظیم شد؛ کارمزد اکنون روی Rand گرفته می‌شود.

مبالغ روی شبکه واحدهای ۸ رقم اعشاری هستند، صرف‌نظر از ارقام اعشاری خود ارز مبدأ: واریز ۱ USDT از Ethereum (۶ رقم اعشار) به‌صورت 100_000_000 واحد قفل می‌شود. فقط اندیس ۰، یعنی RAND، نه رقم اعشار این زنجیره را دارد.

یک توکن باید پیش از نخستین واریزش فهرست شده باشد. دیگر ثبت در نخستین مشاهده وجود ندارد: تصدیقی که (chain, token)ای را نام ببرد که کسی فهرست نکرده باشد با BridgeError::UnlistedToken رد می‌شود (token … of chain … is not listed on this chain). وجوه در نگهداریِ نقطهٔ پایانی مبدأ می‌مانند تا نصابی از نگهبانان پساکوانتومی ارز را فهرست کنند (فهرست‌کردن پس از genesis).

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 به آن حل می‌شود، اما دیگر کلید رجیستری نیست. آنچه یک note و یک کیف پول حمل می‌کند اندیس توکن است، یک u32 فشرده: اندیس ۰ همان 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 رشته‌های دهدهی در واحدهای ۸ رقم اعشاری‌اند، مانند هر مبلغ u64 که RPC از chain 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

توسط یک تراکنش محافظت‌شدهٔ معمولی حمل می‌شود که تنها بستهٔ دارایی پنهان آن فقط پایهٔ ۰٫۰۰۱ RAND را می‌پردازد و بیش از آن نه: رِلهٔ آن برای واریزکننده‌ای می‌پردازد که هنوز RAND ندارد. زنجیره، نه ارسال‌کننده، note واریز { pk: recipient.pk, from: 0, amount, asset: <index>, time, r } را از مبلغی که نگهبانان امضا کرده‌اند محاسبه می‌کند، پس رِله نمی‌تواند ضرب را باد کند یا منحرف سازد؛ تعهد آن تنها برگی است که یک تراکنش روی شبکه حمل نمی‌کند. در chain 20 مقدار amount در note برابر مبلغ ناخالص منهای کارمزد ضرب است، و زنجیره note دومی، یعنی کارمزد، را به خزانه می‌افزاید (کارمزد ضرب و سوزاندن).

پوشاننده انتخاب نمی‌شود، مشتق می‌شود. r باید برابر

r = blake3("rand-deposit-r-1" || mu)        // read as eight little-endian words; mu = keccak256(keccak256(body))

باشد، یعنی همان چکیده‌ای که نصاب نگهبانان امضا کرد؛ هر چیز دیگری BridgeError::WrongDepositBlinding است و پیش از هر کار امضایی رد می‌شود. این فیلد روی شبکه می‌ماند — بازسازی میدان‌های عمومی کیف پول آن را از آنجا می‌خواند — فقط مقید می‌شود، و این قاعده تصدیقِ یک چرخش را نیز دربر می‌گیرد. پیش از آن، هر کس تراکنش در انتظار یک رِله را می‌دید می‌توانست همان تصدیق را با پوشانندهٔ خودش ارسال کند: گیرنده باز هم note می‌گرفت، اما تراکنش رِله پس از آنکه برای یک اثبات هزینه کرده بود از بین می‌رفت. با ثابت‌بودن r، دو ارسال‌کنندهٔ یک تصدیق در یک time یک note را نام می‌برند و استخر آن‌ها را روی تعهد در تعارض قرار می‌دهد.

time و asset دو پیش‌بینی واریزکننده‌اند که انجام می‌شوند تا پاکت بتواند پیش از وجود تراکنش مهروموم شود. time به همان پنجره‌ای محدود است که زمان یک بسته محدود است (۱٬۰۲۴ بلوک در chain 20، از proof_window_blocks در genesis؛ پیش از آن ۲۵۶)، و عمداً مشتق نمی‌شود: هیچ قاعده‌ای timeای نمی‌دهد که همهٔ ارسال‌کنندگان بر آن توافق کنند، پس یک پیشی‌گیرنده هنوز می‌تواند time دیگری در پنجره برگزیند — note متفاوتی با همان گیرنده، مبلغ و دارایی، که کیف پول گیرنده به همان شیوه پیدا می‌کند — و دو ارسال به‌جای آن روی چکیدهٔ تصدیق با هم برخورد می‌کنند. asset باید برابر اندیسی باشد که رجیستری پیشتر به (chain, token) داده است، وگرنه TxError::AttestAssetMismatch.

فیلد fee در بار Transfer حمل می‌شود و به هیچ‌کس پرداخت نمی‌شود: هویتی برای ارسال‌کننده وجود ندارد که به او پرداخت شود، پس واریز مبلغ ناخالص است، فقط منهای کارمزد ضرب خود زنجیره در جایی که genesis آن را تعیین کرده باشد (chain 20). محافظت در برابر بازپخش با چکیده انجام می‌شود: هر mu مصرف‌شده وارد مجموعهٔ spent پل می‌شود، و mempool رِلهٔ دومی را که با همان تصدیق رقابت کند رد می‌کند. واریز همچنین به سقف ضرب پشتوانه محدود است، در chain 20 به سقف سراسری نیز، و تا زمانی که ضرب متوقف است رد می‌شود (B1، rules v2). هر دو سقف مبلغ ناخالص را می‌شمارند. در زنجیره‌ای که genesis آن کفِ بازپخش تعیین می‌کند، از جمله chain 20، انتقالی که sequence آن زیر کف زنجیرهٔ مبدأ باشد پیش از بررسی هر امضایی با BelowReplayFloor رد می‌شود (آنچه از chain 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 چرخش مجموعهٔ نگهبانان را ارسال می‌کند (بار ۲)، که چیزی واریز نمی‌کند، و هر چیزی را که مجموعهٔ Rand را یک گام جلو نبرد رد می‌کند. از آنجا که هر فیلد یک note واریز در همان یک تراکنش عمومی است، کیف پول گیرنده note‌های واریزِ نشانی‌شده به خود را با پیمایش تراکنش‌های bridge_attest ثبت‌شده بازسازی می‌کند، بی‌آنکه پاکتی باز شود — یک رِلهٔ خصم می‌تواند آشغال مهروموم کند اما نمی‌تواند note را پنهان کند.

خروجی: 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 }
}

از بستهٔ دارایی پنهان chain 14 به بعد، یک سوزاندن یک بسته است (پیش‌تر دو بسته بود): شکاف‌های ۰ تا ۱ اثبات می‌کنند که سوزاننده صاحب note‌هایی از آن دارایی بوده که مجموعشان دست‌کم amount است، با burn_a به‌عنوان ارزشی که استخر را ترک می‌کند، و شکاف‌های ۲ تا ۳ کارمزد را به RAND می‌پردازند. کارمزد BRIDGE_BURN_FEE است، یعنی ۰٫۰۱ RAND، که پایهٔ بسته را پوشش می‌دهد. relayer_fee بخشی از amount است (fee ≤ amount) که در زنجیرهٔ مقصد توسط قرارداد آزادسازی جدا می‌شود، نه هزینهٔ اضافی، پس burn_a دقیقاً برابر amount است. در chain 20 زنجیره همچنین کارمزد سوزاندن ۱۰ bps از amount را به‌صورت یک note از نوع zUSD برای خزانه نگه می‌دارد، و آزادسازی amount − fee است؛ سهم رِله باید در همین آزادسازی بگنجد (کارمزد ضرب و سوزاندن).

سوزاندن ارز خود را نام می‌برد. (to_chain, token) باید یکی از پشتوانه‌های دارایی سوزانده‌شده باشد (در غیر این صورت TokenError::NotABacking)، و دو قاعده در گام عمل، پیش از اثبات بسته، بررسی می‌شوند:

  • واحد آزادسازی. پشتوانه‌ای با d < 8 رقم اعشار مبدأ در واحدهای 10^(8−d) آزاد می‌کند (۱۰۰ برای ارز ۶ رقمی؛ ۱ برای ارز ۱۸ رقمی). مبلغ آزادشده و 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 و در chain 20 amount − fee است.

to باید شکل درست داشته باشد (۲۰ بایت با صفر پرشده در زنجیره‌های ۲، ۳ و ۴؛ هرگز تماماً صفر نباشد). دفتر کل یک 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) باید آن را درهم‌سازی (hash) و امضا کنند؛ مقدار Transfer در آن برابر release_amount است. رکوردِ بالا نخستین burn در chain 20 است: ۰٫۹۹۹ zUSD به‌سوی USDT روی BSC سوزانده شد، در ارتفاع 2,899، که ۰٫۰۰۰۹۹۹ آن به خزانه رفت و ۰٫۹۹۸۰۰۱ آزاد شد.

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 پرسش را رد می‌کند). سپس burn یک بسته (bundle) را اثبات می‌کند: حدود ۱۰۰ ثانیه روی یک لپ‌تاپ و حدود ۱٫۵ MB اثبات، و روی chain 20، مانند هر بسته از chain 17 به بعد، یک اثبات مجوز خرج (spend-authorisation) در کنار آن (چند ثانیه، حدود ۱٫۳۷ MB).

کارمزد بر mint و burn

تازه در fullnode v0.6.8 و در chain 20 توسط گروه پیدایش (genesis) تنظیم شده

"bridge": { …, "fees": { "mint_bps": 10, "burn_bps": 10, "recipient": "rand1…" } }

زنجیره بر هر mint پل‌شده و هر burn پل‌شده کارمزد می‌گیرد، در توکنِ پل‌شده، روی Rand، به‌صورت یک note شیلدشده (shielded) به نشانی خزانه recipient. هر نرخ حداکثر 100 bps (۱٪) است؛ روی chain 20 هر دو 10 bps، یعنی ۰٫۱٪ هستند. کارمزد برابر ⌊amount · bps / 10⁴⌋ است که به پایین به یک واحد آزادسازی کامل پشتوانه گرد می‌شود (10^(8 − source decimals): ۱۰۰ واحد برای USDT با ۶ رقم اعشار، ۱ برای یک سکه‌ی ۱۸ رقمی)، بنابراین کارمزد و باقی‌مانده هر دو واحدهای کامل‌اند، و کارمزدی که به صفر گرد شود هیچ note‌ای نمی‌سازد (ledger/bridge_notes.rs، bridge_fee).

واریز (BridgeAttest) به مبلغ ناخالص gburn (BridgeBurn) به مقدار amount
کارمزد⌊g · mint_bps / 10⁴⌋، به پایین تا یک واحد آزادسازی⌊amount · burn_bps / 10⁴⌋، به پایین تا یک واحد آزادسازی
سهم کاربریک note واریز به مبلغ g − feeamount سوزانده می‌شود (burn_a == amount، بدون باقی‌مانده‌ی guest)؛ بدنه‌ی خروجی release_amount = amount − fee را تصدیق می‌کند و نقطه‌ی پایانی دقیقاً همان را آزاد می‌کند
note خزانهfee، با blinding برابر blake3("rand-bridge-fee-r-1" ‖ mu)، در time و دارایی همان واریزfee، با blinding برابر blake3("rand-bridge-burn-fee-r-1" ‖ txid)، در time همان بسته
locked و total_supplyبه اندازه‌ی g افزایش می‌یابندبه اندازه‌ی release_amount کاهش می‌یابند
بررسی در برابرسقف‌های mint به‌ازای هر پشتوانه و سقف سراسری، بر gInsufficientBacking و NotReleasable بر release_amount؛ سهم رله‌گر باید در آن بگنجد (FeeExceedsAmount)

هر دو note صادر و پشتیبانی‌شده‌اند، پس total_supply == Σ locked == custody همچنان برقرار است: note کارمزد مانند هر zUSD دیگر است و ارزشش روی زنجیره‌ی مبدأ قفل می‌ماند تا کسی آن را burn کند. BRIDGE_BURN_FEE، یعنی ۰٫۰۱ RAND که یک burn در اسلات‌های RAND خود می‌پردازد، بدون تغییر و جدا است.

نخستین رفت‌وبرگشت در chain 20، خوانده‌شده از گره: یک واریز ۱ zUSD از BSC در ارتفاع 1,664 (4f6e6c54…1a64) به واریزکننده ۰٫۹۹۹ zUSD و به خزانه ۰٫۰۰۱ داد؛ یک burn به مقدار ۰٫۹۹۹ zUSD به USDT روی BSC در ارتفاع 2,899 (شماره‌ی ترتیب 13) مبلغ ۰٫۰۰۰۹۹۹ zUSD را نگه داشت و ۰٫۹۹۸۰۰۱ را آزاد کرد. پس از آن (خوانده‌شده در 2026-10-01) پشتوانه‌ی USDT روی BSC برابر ۲۲٫۰۰۱۹۹۹ zUSD بود و عرضه‌ی توکن، ۹۹٫۰۰۱۹۹۹ zUSD، با مجموع پشتوانه‌هایش برابر بود.

خزانه note‌های خود را چگونه پیدا می‌کند. note کارمزد پاکت (envelope) ندارد: هر جزء آن عمومی است (مقدار از تراکنش و نرخ پیدایش، دارایی و time از تراکنش، blinding مشتق‌شده، و مالک همان recipient پیدایش). کیف پولی که نشانی‌اش recipient است، note‌های کارمزد را در همان گذر عمومی بازمی‌سازد که note‌های واریز را بازمی‌سازد، و در هر همگام‌سازی یک بار rand_getBridgeState.fees و ارقام اعشار پشتوانه‌ها را می‌خواند. گرهی که درباره‌ی کارمزدها دروغ بگوید فقط می‌تواند باعث شود note‌ای پیدا نشود، هرگز نمی‌تواند note‌ای را اعتبار دهد. کیف پول واریزکننده نیز ارزش واریز خود را به همین شیوه محاسبه می‌کند: ناخالص منهای کارمزد. فقط کیف پول v0.6.8 چنین می‌کند؛ کیف پول قدیمی‌تر به هر حال نمی‌تواند روی chain 20 ارسال کند.

بدون این گروه، یعنی در chain 14 تا 19، کارمزدی وجود ندارد: واریز مبلغ ناخالص را اعتبار می‌دهد و burn مقدار amount را آزاد می‌کند. تا پیش از chain 20 تنها کارمزد پل، کسر 10 bps از USDT توسط نقطه‌های پایانیِ مبدأ هنگام آزادسازی روی زنجیره‌ی میزبان بود؛ این مقدار در برش chain 20 به ۰ تنظیم شد. این گروه در هش پیدایش ثبت شده و تحت rand-bridge-fees-1 در ریشه‌ی پل ادغام می‌شود. docs/bridge.md در fullnode، بخش ۲۵، کدگذاری را دارد.

مقاوم‌سازی پل

مقدار locked به‌ازای هر پشتوانه و InsufficientBacking از بازخرید محافظت می‌کنند. آن‌ها mint را محدود نمی‌کنند: تصدیقی (attestation) با حد نصاب معتبر نگهبانان همان مقداری را mint می‌کند که می‌گوید. سه لایه این شکاف را می‌بندند و لایه‌ی چهارم اجازه می‌دهد توکنی بدون برش زنجیره فهرست شود. هیچ‌کدام نقاط پایانی زنجیره‌ی مبدأ، قالب سیمی تصدیق یا bridge-codec را تغییر نمی‌دهند.

B1: سقف mint به‌ازای هر پشتوانه، و توقف mint

مقدار tokens.mint_cap_per_day در پیدایش به‌ازای هر پشتوانه اعمال می‌شود: chain 20 مقدار 10000000000000 را تعیین می‌کند، یعنی 100,000 zUSD به‌ازای هر پشتوانه در روز، همان‌طور که هر زنجیره از 14 به بعد کرده است. بدون rules v2 (chain 14) یک روز برابر timestamp_ms / 86_400_000 از برچسب زمانی بلوک است؛ تحت rules v2 (زنجیره‌های 15 تا 20) همان سقف روی یک پنجره‌ی غلتان به طول cap_window_secs از زمان بلوک سنجیده می‌شود، و سقف سراسریِ کل فهرست — 4,000 zUSD به‌ازای هر 86,400 ثانیه روی chain 20 — کران تنگ‌تر است (rules v2). واریزی که از سقف بگذرد پیش از کار امضای نگهبانان با TokenError::MintCapExceeded رد می‌شود، و وقتی پنجره دوباره جا داشته باشد پذیرفتنی است.

bridge.pause_key در پیدایش یک کلید Dilithium2 است، متمایز از هر نگهبان پساکوانتومی. دو کنش بدون بسته و بدون کارمزد:

  • PauseMints { nonce, signature } — تنها با کلید توقف امضا می‌شود. فقط می‌تواند متوقف کند.
  • UnpauseMints { nonce, pq_signatures } — به حد نصاب نگهبانان پساکوانتومی نیاز دارد، هرگز به کلید توقف.

هر دو pause_nonce را حمل و افزایش می‌دهند، پس هیچ‌کدام بازپخش نمی‌شوند. تا زمانی که متوقف است هر BridgeAttest انتقالی با BridgeError::MintsPaused رد می‌شود؛ burn و چرخش مجموعه‌ی نگهبانان باز می‌مانند، پس توقف هرگز بازخرید را مسدود نمی‌کند. تحت rules v2 (زنجیره‌های 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]

آنچه توقف پوشش می‌دهد: دیمون‌های نگهبانی که فریب خورده یا نفوذ شده‌اند و یک mint جعلی را هم‌امضا می‌کنند، در حالی که دارندگان حد نصاب پساکوانتومی چنین نیستند. این دفاعی در برابر سرقت همان حد نصاب نیست، که می‌تواند توقف را لغو کند، تا 32 پشتوانه را روی یک توکن فهرست کند، هر یک با سقف روزانه‌ی خودش، و تحت rules v2 مجموعه‌ی پساکوانتومی و کلید توقف را جایگزین کند.

B2: برچسب‌های زمانی کران‌دار بلوک

برچسب زمانی بلوک در زنجیره‌ی پل‌شده انقضای مجموعه‌ی نگهبانان و روزِ سقف mint را تعیین می‌کند، پس نباید یک leader بتواند آن را جهش دهد. روی زنجیره‌ای که پل دارد:

  • یک قاعده‌ی اعتبار، که در بازپخش (replay) هم بررسی می‌شود: timestamp_ms ≤ parent.timestamp_ms + 60_000، وگرنه BlockError::TimestampLeap. با قاعده‌ی موجود بازگشت به عقب (BlockError::TimestampRewind)، زمان بلوک فقط به جلو می‌رود، حداکثر یک دقیقه در هر بلوک.
  • یک قاعده‌ی رأی، هرگز قاعده‌ی بازپخش نیست: یک validator رأی خود را از بلوکی که بیش از 15,000 ms جلوتر از ساعت خودش باشد دریغ می‌کند.

بنابراین هر validator در زنجیره‌ی پل‌شده به NTP نیاز دارد: کسی که بیش از 15 ثانیه اختلاف داشته باشد، هم leader معیوب است و هم رأی‌دهنده‌ی معیوب. وقتی زنجیره پس از توقف عقب‌ماندگی را جبران می‌کند، زمان بلوک می‌تواند در هر بلوک تا 60 ثانیه جلو برود، پس روزِ سقف mint می‌تواند سریع‌تر از یک روز واقعی بچرخد — حداکثر سقف یک روز اضافه به‌ازای هر روز کامل عقب‌ماندگیِ انباشته.

B3: هم‌امضای Dilithium2 بر هر mint

هر BridgeAttest، چه واریز و چه چرخش، باید در کنار حد نصاب ECDSA خود حد نصابی از امضاهای Dilithium2 را بر

M = "rand-bridge-pq-cosign-1" || rand_chain_id (u64 BE) || mu        // 63 bytes

حمل کند. mu همان چکیده‌ای است که حد نصاب ECDSA امضا می‌کند؛ شناسه‌ی زنجیره مانع می‌شود هم‌امضای testnet روی زنجیره‌ی دیگری از Rand معتبر شود. امضاها در pq_signatures کنش، درون الزام تراکنش (transaction binding) می‌آیند، پس روی یک رونوشت نمی‌توان آن‌ها را حذف یا جابه‌جا کرد. ارزان پیش از گران بررسی می‌شود: quorum(n) ≤ len ≤ n (PqNoQuorum)، اندیس‌ها اکیداً صعودی (PqIndexOrder)، هر اندیس < n (PqIndexOutOfRange)، هر امضا دقیقاً 2,420 بایت (PqBadSignatureLength)، و در پایان اینکه هر کدام تحت pq_guardians[index] تأیید شود (PqBadSignature). دو حد نصاب مستقل شمرده می‌شوند. bridge.pq_guardians در پیدایش با مجموعه‌ی ECDSA پیدایش (مجموعه‌ی 1 روی chain 20) هم‌تراز اندیسی است. چرخش payload-2 فقط مجموعه‌ی ECDSA را جابه‌جا می‌کند؛ بدون rules v2 مجموعه‌ی پساکوانتومی در تمام عمر زنجیره ثابت است، و تحت rules v2 فقط RotatePqGuardians آن را جایگزین می‌کند (روی chain 20 فرم V2 آن، با اثبات تصاحب (possession proof) و تأخیر یک‌روزه). chain 20 هشت کلید دارد، همان هشت کلید chains 15 تا 19؛ یک mint به شش تای آن‌ها نیاز دارد (chain 14 شش کلید داشت و به پنج نیاز داشت).

این هم‌امضایی در زنجیرهٔ 20 نیز همچنان فقط شناسهٔ زنجیره را می‌بندد: برخلاف پیام‌های حاکمیتی زیر، پشت هش genesis منتقل نشد، چون دیمن‌های guardian که آن را تولید می‌کنند در مخزن دیگری قرار دارند و تغییری نکرده‌اند. نسخهٔ هم‌شناسه از زنجیره‌ای که مجموعهٔ guardianها را هم کپی کرده باشد، هم‌امضایی‌های یک سپرده را که برای زنجیرهٔ اصلی ساخته شده‌اند می‌پذیرد؛ بستهٔ کارمزد خودِ تراکنش سپرده در زنجیرهٔ 20 به genesis بسته است، اما گواهی درون آن بسته نیست.

B4: فهرست‌کردن یک توکن پل‌شده پس از genesis

یک آدرس معمولی هرگز نمی‌تواند توکنی با مرجع Bridge بسازد. دو کنش وجود دارد، هر یک روی یک بستهٔ کارمزد معمولی RAND و هر یک تحت حدنصاب پساکوانتومی:

  • RegisterBridgedToken { name, symbol, salt, chain, token, decimals, nonce, pq_signatures } — یک توکن پل‌شدهٔ جدید در شاخص بعدی، با 8 رقم اعشار در Rand، تحت سقف ضرب genesis، همراه با نخستین پشتوانه‌اش. کارمزد: پایهٔ بسته به‌علاوهٔ 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>

دو قاعدهٔ ترتیب. guardianها ابتدا ثبت را امضا می‌کنند، منتظر می‌مانند تا commit شود، شاخص تخصیص‌یافته را بازمی‌خوانند و تنها آن‌گاه فهرست‌هایی را امضا می‌کنند که نام آن شاخص را دارند — ثبت یک توکن بومی بدون مجوز است، پس فهرستی که زودتر امضا شود ممکن است شاخصش اشغال شده باشد. و یک کوین پیش از آنکه setToken آن را روی endpoint مبدأ فعال کند در Rand فهرست می‌شود: در حالت عکس، یک سپرده می‌تواند پیش از آن‌که Rand پشتوانه‌ای برای اعتباردهی داشته باشد قفل شود و گواهی آن تا رسیدن Rand با UnlistedToken رد می‌شد.

پیام‌های حاکمیتی

بایت‌های ثابت، هرگز 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 در genesis): رقم نسخهٔ برچسب به 2 می‌رود و هش 32 بایتی genesis میان برچسب و شناسهٔ زنجیره قرار می‌گیرد و بخش انتهایی تغییری نمی‌کند — 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). کلید توقف یا حدنصابی که برای یک زنجیره امضا می‌کند، دیگر برای هیچ زنجیرهٔ دیگری که شناسهٔ زنجیره و کلیدهایش را با آن سهیم است امضا نمی‌کند. فایلی که به شکل …-1 امضا شده باشد در زنجیرهٔ 20 رد می‌شود (BadPauseSignature، PqBadSignature). زنجیره‌های 14 تا 19 از ساختارهای …-1 بالا استفاده می‌کنند، بایت به بایت.

قواعد نسخهٔ 2: چرخش‌ها، سقف‌های غلتان، بدون فهرست‌کردن در حالت توقف

یک گروه اختیاری درون بخش bridge در genesis. زنجیرهٔ 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 تا زمانی که mint_paused برقرار است با MintsPaused رد می‌شوند. سوزاندن‌ها و هر دو چرخش باز می‌مانند.

چرخش: اثبات مالکیت و تأخیر یک‌روزه

قواعد نسخهٔ 2 سه چیز را باز گذاشته بود: هیچ چیز اثبات نمی‌کرد کلیدهای جدید در دست کسی هستند (یک غلط تایپی یا کلیدی که کسی ندارد، حدنصاب پساکوانتومی را برای همیشه از کار می‌انداخت)، چرخش در همان بلوکی که ثبت می‌شد اثر می‌کرد، و هیچ چیز نمی‌توانست مانع یک چرخش خصمانه شود. گروه اختیاری دوم در کنار 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، در زنجیرهٔ 20 به شکل …-2 آن‌ها)، یکی برای هر کلید به ترتیب کلیدها، هر کدام 2,420 بایت. کنش‌های v1 با PossessionRequired رد می‌شوند. تعداد یا طول نادرست پیش از راستی‌آزمایی حدنصاب رد می‌شود؛ امضای مالکیت نادرست (BadPossession { index }) پس از آن.
  • یک تأخیر. چرخش پذیرفته‌شده به‌صورت در انتظار ثبت می‌شود، با effective_at_secs = block time + delay_secs، و در پایان نخستین بلوکی که برچسب زمانی‌اش به آن برسد اثر می‌کند. در زنجیرهٔ 20 این 86,400 s است، یک روز. تا آن زمان مجموعهٔ قدیمی هر ضرب را هم‌امضا می‌کند و کلید توقف قدیمی توقف می‌دهد. پذیرش یک چرخش یک rotation_nonce مصرف می‌کند و فعال‌سازی آن یکی دیگر، پس گواهی ضربی که زیر مجموعهٔ قدیمی در pool جمع شده باشد با تغییر مجموعه از pool خارج می‌شود. از هر نوع چرخش فقط یکی در هر زمان می‌تواند در انتظار باشد (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 را برمی‌گرداند؛ هر دو در زنجیره‌ای بدون این گروه (زنجیره‌های 14 تا 19) null هستند. چرخش مجموعهٔ guardian از نوع ECDSA (payload 2) تحت تأثیر قرار نمی‌گیرد: قواعد و پنجرهٔ مهلت خودش را نگه می‌دارد. هیچ چرخشی در زنجیرهٔ 20 اجرا نشده است.

منتقل‌شده از زنجیرهٔ 14

زنجیرهٔ 15 از موقعیت پل زنجیرهٔ 14 (همان emitterها و endpointهای مبدأ) بریده شد، و زنجیره‌های 16، 17 و 18 هر یک به همین شکل از موقعیت پیشین خود بریده شدند. زنجیرهٔ 19 با یک تغییر از موقعیت زنجیرهٔ 18 بریده شد: emitterهای Ethereum، BSC و Tron آن، endpointهایی هستند که در 2026-09-30 دوباره مستقر شدند (0x7af6b17047c1db6cb54347fdea45cf9179075bfa برای زنجیره‌های 2 و 3، 0x6410797df959987a5baf65b5fab97edeb34d5163 برای زنجیرهٔ 4). زنجیرهٔ 18 هرگز به آن‌ها اعتماد نکرد، پس چیزی از آن‌ها در آنجا ضرب نشد. زنجیرهٔ 20 از یک snapshot از زنجیرهٔ 19 در ارتفاع 7,258 بریده شد، با توقف پیشینِ پل، و emitterها، guardianها، کلیدهای پساکوانتومی و کلید توقف زنجیرهٔ 19 را بدون تغییر نگه می‌دارد؛ آنچه می‌افزاید گروه‌های کارمزدها و چرخش است. emitter مربوط به Solana و emitter سمت Rand از زنجیرهٔ 14 تغییری نکرده‌اند. آنچه genesis زنجیرهٔ 20 حمل می‌کند:

  • نگهداری (Custody). ۹۹ zUSD، در قالب دو note زنجیره‌ی genesis از zUSD (به مقدار ۱۰ و ۸۹ zUSD)، در برابر 22 USDT قفل‌شده روی Ethereum، 22 USDT روی BSC، 30 USDT روی Tron و 25 USDT روی Solana، و 0 روی هر پشتوانه‌ی USDC: مقدار locked در زنجیره‌ی 19 هنگام تهیه‌ی snapshot، که با موجودی نگهداری‌شده در endpointها مطابقت داشت. total_supply == Σ locked با موجودی نگهداری‌شده در endpointها در بلوک 0 برابر است. تاریخچه: ۱۰ zUSD باقی‌مانده روی زنجیره‌ی 14 در زنجیره‌های 15 تا 19 یک note در genesis بودند؛ genesis زنجیره‌ی 18 آن‌ها را به صورت 9 USDT قفل‌شده روی Tron و 1 روی Solana نگه می‌داشت، و genesis زنجیره‌ی 19 به صورت 10 روی Solana، پس از یک بازتوازن در زنجیره‌ی 18. ۸۹ مورد دیگر، واریزهای خالص زنجیره‌ی 19 منهای burnهای آن هستند.
  • اندیس مجموعه‌ی guardian برابر 1 است، همان‌جا که endpointهای مبدأ هم هستند؛ چرخش ECDSA بعدی باید اندیس 2 را حمل کند. هشت کلید پساکوانتومی و کلید توقف (pause key) همان کلیدهای زنجیره‌ی 15 هستند، بدون تغییر.
  • burn_sequence برابر 13: burnهای زنجیره‌ی 14 در 6 پایان یافتند، genesis زنجیره‌های 15 تا 18 از 7 شروع شد، زنجیره‌ی 18 یک burn انجام داد (7) و زنجیره‌ی 19 از 8 شروع کرد. زنجیره‌ی 19 پنج burn انجام داد، با شماره‌های ترتیبی 8 تا 12، پس زنجیره‌ی 20 از 13 شروع می‌کند و هیچ endpoint یا daemon‌ای که با شماره‌ی ترتیبی کلید خورده باشد، یک شماره را دو بار نمی‌بیند. نخستین burn زنجیره‌ی 20 شماره‌ی ترتیبی 13 را گرفت.
  • بازپخش (Replay). mint زنجیره‌ای قدیمی‌تر هنوز از حد نصاب ECDSA عبور می‌کرد، اما امضای همزمان پساکوانتومی آن شناسه‌ی همان زنجیره را به خود پیوند می‌دهد، پس در زنجیره‌ی 20 رد می‌شود. genesis زنجیره‌ی 15 کفِ بازپخش ورودی نداشت (min_inbound_sequence برابر null بود)، پس در آنجا این امر بر امضای همزمان و بر این تکیه داشت که guardianها هرگز یک lock قدیمی را برای شناسه‌ی زنجیره‌ی جدید امضا نکنند. از زنجیره‌ی 16 به بعد genesis یکی تعیین می‌کند: برای هر زنجیره‌ی مبدأ، کمترین sequence که یک گواهی انتقال از emitter آن زنجیره می‌تواند داشته باشد. مقدار زنجیره‌ی 20 { "2": 3, "3": 3, "4": 3, "5": 7 } است: شماره‌ی ترتیبی خروجی بعدی هر endpoint هنگام تهیه‌ی snapshot از زنجیره‌ی 19، چون هر lock قبلی روی زنجیره‌ی 19 mint شده بود. (مقدار زنجیره‌ی 18 { "2": 2, "3": 2, "4": 2, "5": 2 } بود؛ مقدار زنجیره‌ی 19 { "2": 1, "3": 1, "4": 1, "5": 4 } بود، که در آن شماره‌ی ترتیبی 0 در هر endpoint بازمستقرشده، lock خودِ اپراتور بود که هرگز نباید mint شود.) انتقالی که زیر این کف باشد با BelowReplayFloor رد می‌شود، هر حد نصاب و امضای همزمانی هم که داشته باشد. چرخش مجموعه‌ی guardian با آن سنجیده نمی‌شود. این کف در genesis ثابت می‌شود، در هش genesis متعهد می‌شود و در ریشه‌ی bridge پیچیده می‌شود.

ترتیب راه‌اندازی روی زنجیره‌ی 14 (تاریخچه)

چگونگی راه‌اندازی bridge روی زنجیره‌ی 14، یک testnet پیشین، در سپتامبر 2026:

  1. زنجیره را با بخش bridge (شامل pq_guardians و pause_key) و بخش tokens که registration_fee و mint_cap_per_day را تعیین می‌کند و هیچ توکنی را فهرست نمی‌کند، برش بزنید. انجام شد در 2026-09-20.
  2. faucet را فعال کنید تا یک کیف پول deployer بتواند RAND بگیرد. انجام شد.
  3. deployer، zUSD را با نخستین پشتوانه‌اش، با امضای حد نصاب روی M_register در list_nonce = 0، ثبت می‌کند. انجام شد در ارتفاع 256.
  4. guardianها اندیس را بازخوانی می‌کنند و شش پیام باقی‌مانده‌ی ListBacking را امضا می‌کنند، که یکی‌یکی ارسال می‌شوند. انجام شد: list_nonce برابر 7 است.
  5. برای هر پشتوانه: ابتدا روی Rand فهرست کنید، سپس setToken را روی endpoint مبدأ اجرا کنید.
  6. چرخش مجموعه‌ی guardian (مجموعه‌ی 0 ← مجموعه‌ی 1، با امضای 5 از 6 توسط مجموعه‌ی فعلی) روی چهار endpoint مبدأ، سپس همان گواهی روی Rand. Rand در تمام عمر زنجیره‌ی 14 مجموعه‌ی 0 را نشان می‌داد؛ زنجیره‌ی 15 از مجموعه‌ی 1 شروع می‌شود.
  7. یک رفت‌وبرگشت زنده برای هر زنجیره‌ی مبدأ با مبالغ کوچک — bridge به داخل، mint کردن zUSD، انتقال، burn به عقب — و تطبیق locked هر پشتوانه (rand_getTokenSupply) با موجودی نگهداری‌شده در endpoint.

وضعیت عمومی

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 بایت به صورت hex. دیگر next_index وجود ندارد: توکن bridge‌شده پیش از آنکه بتوان آن را واریز کرد، زیر اندیسی که ثبتش تعیین کرده فهرست می‌شود، پس اندیسی برای پیش‌بینی باقی نمی‌ماند. rotation_nonce مقداری است که M_rotate_pq/M_rotate_pause/M_cancel بعدی باید حمل کند؛ rules_v2 در زنجیره‌ای که این گروه را ندارد (زنجیره‌ی 14) برابر null است، و min_inbound_sequence در زنجیره‌ای که کف بازپخش ندارد (زنجیره‌ی 15 و پیش‌تر) برابر null است؛ در زنجیره‌ی 20 مقدار آن { "2": 3, "3": 3, "4": 3, "5": 7 } است. rotation_rules، pending_rotations و fees در زنجیره‌ای که این گروه‌ها را ندارد (زنجیره‌های 14 تا 19) برابر null هستند. نمونه‌ی بالا شکل را نشان می‌دهد، نه مقادیر زنجیره‌ی 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} پاسخ می‌دهد.

بدون موجودی: ارزش bridge‌شده note است، و کیف پول آنچه را در اختیار دارد با rand asset-balance [INDEX] از note‌های خودش می‌خواند. ریشه‌ی bridge به emitterها، مجموعه‌های guardian، digestهای خرج‌شده، burn sequence، مجموعه‌ی guardian پساکوانتومی، و کلید توقف، پرچم توقف و nonceهای حاکمیتی (دو تا، و rotation_nonce تحت قوانین v2)، کف بازپخش در جایی که genesis یکی تعیین کند، و در زنجیره‌ی 20 قوانین چرخش به همراه هر چرخش در انتظار و گروه کارمزد متعهد می‌شود؛ فقط در زنجیره‌ای که bridge دارد در ریشه‌ی state تا می‌شود. رجیستری توکن، با locked هر پشتوانه، جداگانه به صورت tokens_root متعهد می‌شود. GET /bridge و GET /bridge/assets در explorer همین state را ارائه می‌دهند، دومی با مجموع واریز و burn هر پشتوانه به صورت رشته‌های 8 رقم اعشاری؛ GET /tokens و GET /tokens/:id رجیستری را ارائه می‌دهند.

چه چیزی عمومی است و چه چیزی پنهان

چیزچه کسی می‌بیند
مبلغ یک واریز، اندیس دارایی و نشانی گیرندههمه، در همان یک تراکنش
گیرنده بعداً کدام note را خرج می‌کندهیچ‌کس
مبلغ، دارایی، coin، مقصد و کارمزد relayer یک burnهمه — guardianها به آن نیاز دارند
مبلغ، دارایی و مالک یک note کارمزد bridge، خزانه (زنجیره‌ی 20)همه: از فیلدهای عمومی بازسازی می‌شود
کدام note‌ها یک burn را تأمین کرده‌اندهیچ‌کس
موجودی‌های bridge‌شدهفقط دارنده، یا دارنده‌ی کلید مشاهده
مجموعه‌های guardian، emitterها، رجیستری با locked هر پشتوانه، گزارش burnهمه

شکاف‌های شناخته‌شده

از فهرست خود fullnode: هیچ relayer‌ای روی Rand پرداخت نمی‌گیرد؛ envelope یک واریز به هیچ چیز گره نخورده است، پس یک relayer متخاصم می‌تواند داده‌ی بی‌معنا را مهروموم کند، و به همین دلیل کیف پول note‌های واریز را از فیلدهای عمومی بازسازی می‌کند؛ بردارهای آزمون مشترکِ سطح ledger روی استخر note بازسازی نشده‌اند؛ bridge-burn هنوز سیاست خود endpoint مقصد را فقط پس از پرداخت هزینه‌ی یک proof درمی‌یابد؛ زمان‌مهر بلوک‌های برابر با والد در زنجیره‌ی bridge‌شده مجاز است، پس leaderهای هم‌دست می‌توانند یک مجموعه‌ی guardian منسوخ را در پنجره‌ی مهلتش نگه دارند؛ گزارش burn درون‌حافظه‌ای نامحدود است؛ هیچ light client یا راستی‌آزمایی روی زنجیره‌ای از state زنجیره‌ی مبدأ وجود ندارد و برنامه‌ای هم برای آن نیست.

آخرین بار با کامیت c0878c3f از fullnode (زنجیرهٔ 21) در تاریخ 2026-10-12 بررسی شد. هرجا کد و این صفحه با هم اختلاف دارند، حق با کد است؛ به ما اطلاع دهید.