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

برنامه‌ها

مدل برنامه‌های محرمانه - یک guest مبتنی بر RISC-V که در zkVM اثبات می‌شود، یک بار مستقر می‌شود و با ورودی‌های خصوصی فراخوانی می‌شود - همراه با دستورهای build، deploy، call، invoke و receipt، وضعیت برنامه RPL-2 روی chain 20، و فهرستی صادقانه از آنچه هنوز ممکن نیست.

برای استفاده از این صفحه: یک برنامه را روی زنجیره قرار دهید و آن را فراخوانی کنید. صفحه توضیح می‌دهد برنامه چیست، چگونه ساخته می‌شود، یک فراخوانی چه چیزی را منتشر و چه چیزی را پنهان می‌کند، رسید چگونه خوانده می‌شود و با چه محدودیت‌هایی روبه‌رو می‌شوید. هر برنامه کدی عمومی است؛ هر فراخوانی یک اثبات است. برنامه‌های نمونه برنامه‌های کاملی دارد و randprotocol/example-programs سورس برنامه‌های مستقرشده روی chain 20 را در بر دارد، از جمله برنامه‌های RPL-2 (در ادامه).

برنامه چیست

برنامه کد ماشین RV32IM برای zkVM شبکه Rand است: { base_pc: u32, words: Vec<u32> }. حداکثر اندازه آن سقف برنامه در زنجیره است (65,535 کلمه روی chain 20، مانند هر زنجیره از chain 13 به بعد؛ 4,096 روی genesis بدون max_program_words، مانند chain 12). base_pc باید مضرب 4 باشد و هر کلمه باید به یک دستور رمزگشایی شود (کلمه‌ای که رمزگشایی نشود هنگام استقرار رد می‌شود، نه هنگام اجرا). chain 20، مانند chain 19، برنامه‌ای را هم که هیچ فراخوانی نتواند آن را در خود جا دهد مستقر نمی‌کند: بیش از 8,180 کلمه بدون قطعه عمومی، و کمتر از آن با قطعه عمومی (در ادامه). برنامه با محتوایش آدرس‌دهی می‌شود و تغییرناپذیر است:

program_id = blake3("rand-program" || base_pc || words)       // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public)   // deployed with --public
code_hash  = hc = Poseidon2 digest of (base_pc, len, words)    // what calls are verified against

فراخوانی یک اثبات STARK است که نشان می‌دهد برنامه‌ای با چکیده hc روی ورودی‌های خصوصی اجرا شده، بیش از حد گس اعلام‌شده مصرف نکرده و هشت کلمه خروجی را منتشر کرده است. گره اثبات را تأیید می‌کند، یک رسید ثبت می‌کند و دیگر هیچ اتفاقی نمی‌افتد: خروجی‌ها داده‌اند، نه اثر. یک فراخوانی نمی‌تواند RAND را جابه‌جا کند؛ فراخواننده ارزش را در بسته‌ای جابه‌جا می‌کند که هزینه فراخوانی را می‌پردازد، و خروجی‌های برنامه همان معنایی را دارند که برنامه و فراخواننده‌اش بر آن توافق کنند. روی chain 20، Invoke استثنا است: اثبات آن یک گذار حالت اعلام‌شده را تضمین می‌کند و دفتر کل سپس آن را اعمال می‌کند (حالت برنامه (RPL-2)).

کارهایی که guest می‌تواند انجام دهد:

گروهدستورها
بالایی / پرش‌هاLUI AUIPC JAL JALR
شاخه‌هاBEQ BNE BLT BGE BLTU BGEU
بارگذاری / ذخیرهLW SW LB LH LBU LHU SB SH
ALUADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND
RV32MMUL MULH MULHU MULHSU DIV DIVU REM REMU
سیستمECALL؛ هرگز FENCE، CSRها، EBREAK و افزونه‌های A یا C

فراخوان‌های سیستمی، به‌ترتیب شماره در a7:

#فراخوان سیستمیاثر
0HALTاجرا را پایان می‌دهد
1WRITE_OUTPUT slot wordخروجی عمومی slot < 8 را تثبیت می‌کند؛ هر شکاف حداکثر یک بار، شکاف‌های ننوشته صفر هستند
2READ_INPUT idxکلمه ورودی خصوصی idx را برمی‌گرداند که به تعهد نمک‌دار H_IN متصل است
3POSEIDON2 ptr nn ≤ 4096 کلمه را در همان محل با اسفنج Poseidon2 هش می‌کند
4KECCAK ptrیک جایگشت Keccak-f[1600] در همان محل؛ باعث می‌شود اثبات جدول keccak را حمل کند
5SHA256 ptrیک تابع فشرده‌ساز SHA-256 در همان محل؛ باعث می‌شود اثبات جدول sha256 را حمل کند
6READ_PUBLIC idxکلمه idx از قطعه عمومی را برمی‌گرداند؛ روی chain 20 قطعه عمومی هنگام استقرار ثابت می‌شود (rand program deploy --public)؛ روی genesis بدون max_program_public_words خالی است و این فراخوان چیزی نمی‌خواند
7POSEIDON2_LEN ptr nPOSEIDON2 با طول پیام که در اسفنج گنجانده می‌شود، به‌طوری که [a] و [a, 0] هش‌های متفاوتی دارند (مجموعه قیود 7، chain 16 و بعد از آن). rand-guest check در circuits aeacf31 هنوز آن را رد می‌کند (بخش بعد)

حافظه: 32 ثبات که x0 صفر است؛ RAM که از صفر شروع می‌شود؛ هر آدرس کمتر از 2³⁰؛ و پشته 64 KiB برای guestهای کامپایل‌شده. برنامه‌ای که با rand-guest ساخته شود داده فقط‌خواندنی و مقداردهی‌شده‌اش را در کانتینر تصویر حمل می‌کند، و یک پیش‌درآمد بارگذار که بخشی از برنامه است پیش از اجرای کد خود برنامه این داده را می‌نویسد (معماری مجموعه دستورهای RISC-V). تعهدهای یادداشت، ابطال‌گرها و بررسی‌های Merkle فراخوان سیستمی نیستند، بلکه روال‌های کتابخانه‌ای روی POSEIDON2 هستند.

از chain 18 (مجموعه قیود 8) هر اجرا با گس سنجیده می‌شود. یک دستور معمولی و فراخوان‌های 0، 1، 2 و 6 هر کدام 1 گس هزینه دارند، POSEIDON2 روی n کلمه 3 + 3 × ⌈n / 4⌉ هزینه دارد، یک جایگشت KECCAK هزینه‌اش 192 و یک فشرده‌سازی SHA256 هزینه‌اش 64 است. برنامه‌های نمونه جدول را دارد و توضیح می‌دهد یک فراخوانی چه چیزی را اعلام می‌کند.

ساختن یک برنامه

سه راه، که همه کلمات RV32IM تولید می‌کنند:

اسمبلر داخلی. کیف پول پنج guest دست‌اسمبل‌شده دارد و آن‌ها را به یک فایل JSON با ساختار { "base_pc", "words" } اسمبل می‌کند:

rand program build --guest private_payment --arg 1000 --out pp.json   # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable

برنامه‌های کامپایل‌شده، با rand-guest. rand-guest در مخزن circuits یک crate زبان Rust از نوع no_std (riscv32im-unknown-none-elf، Rust 1.98.1) یا یک پوشه C (clang 23.1.1) را به کانتینر تصویر می‌سازد، هر کلمه را با رمزگشای ماشین بررسی می‌کند و hc و شناسه برنامه را چاپ می‌کند. ساخت هرمتیک است، پس سورس یکسان روی هر ماشین hc یکسان می‌دهد. برای chainهای 18 تا 20، زنجیره ابزار را در کامیت aeacf31 از circuits بسازید (شاخه feat/v067-circuits)، همان کامیتی که fullnode v0.6.7 و v0.6.8 هر دو به آن پین شده‌اند. در آن کامیت rand-guest check فقط فراخوان‌های 0 تا 6 را فهرست می‌کند، پس guestی که guest_sdk::poseidon2_len (فراخوان 7) را صدا بزند هنگام ساخت رد می‌شود، هرچند ماشین آن را اجرا می‌کند (zkp-circuits issue #1). گام‌به‌گام: استقرار یک برنامه Rust و استقرار یک برنامه RISC-V.

برنامه‌های ترجمه‌شده. sbpf2rv یک ELF از Solana sBPF را و evm2rv بایت‌کد زمان اجرای EVM را به برنامه‌هایی ترجمه می‌کند که خروجی‌شان کلمه به کلمه با guestهای مفسر Rand برابر است: مترجم Solana و مترجم Ethereum.

خود کیف پول کامپایلر ندارد. rand program deploy یک فایل .json، یک .bin خام از کلمات little-endian، یا کانتینر تصویری را که rand-guest می‌نویسد می‌پذیرد (fullnode v0.3 و بعد از آن).

استقرار

rand program deploy pp.json          # a bundle pays the floor; waits for the commit; prints the id
rand program show <id>               # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest

testnet همان chain 20 است و استقرار، فراخوانی یا Invoke روی آن به کیف پول rand از fullnode نسخه v0.6.8 یا بعد از آن نیاز دارد. کیف پول قدیمی‌تر اصلاً نمی‌تواند به آنجا ارسال کند: genesis در chain 20 مقدار binding_domain: 1 را تنظیم می‌کند، پس هر اتصال اثبات و هر پیام امضاشده هش genesis را در بر می‌گیرد، در حالی که اتصال‌های کیف پول v0.6.7 فقط شناسه زنجیره را دارند. نقطه پایانی عمومی https://rpc.randprotocol.org همگام‌سازی، استقرارها و فراخوانی‌های کیف پول را سرویس می‌دهد (راه‌اندازی).

کیف پول ابتدا شناسه برنامه، تعداد کلمات و hc را چاپ می‌کند و با rand_estimateFee کارمزد را از گره می‌پرسد؛ این روش برنامه‌ای را که از سقف زنجیره بزرگ‌تر باشد پیش از هر اثباتی رد می‌کند. سپس یک بسته انتقال به خود با ارزش صفر می‌سازد (همان بسته دارایی پنهان 4-ورودی-4-خروجی که هر تراکنش حمل می‌کند؛ کارمزد از شکاف‌های RAND آن کسر می‌شود) که 0.001 RAND + 100,000 units × words را از یادداشت‌های خودش می‌پردازد (کلمات قطعه عمومی هم جزو کلمات کد شمرده می‌شوند)، Action::Deploy { base_pc, words, public } را به آن متصل می‌کند، بسته را در برابر اتصال تراکنش اثبات می‌کند و ارسال می‌کند. از chain 17 این دو اثبات است و کیف پول برای هر کدام یک خط چاپ می‌کند: یک مجوز خرج از tier-10 (چند ثانیه، حدود 1.37 MB) و بسته tier-14 (حدود دو دقیقه روی یک لپ‌تاپ، حدود 1.5 MB). پیش از chain 17 فقط اثبات بسته بود، حدود 100 s و حدود 1.42 MB روی chain 15. کارمزد استقرار جمله‌ای برای گس یا بایت ندارد: گس فقط فراخوانی‌ها را قیمت‌گذاری می‌کند. گره بررسی می‌کند هر کلمه رمزگشایی شود، hc را محاسبه می‌کند، رکورد را ذخیره می‌کند و ساخت کلید تأییدکننده را در پس‌زمینه آغاز می‌کند. کد عمومی است؛ اینکه چه کسی هزینه آن را پرداخته عمومی نیست — rand_getProgram فیلد deployer ندارد. استقرار دوباره همان کلمات بی‌اثر است.

→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
    "public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }

فراخوانی

rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>

کیف پول کد و قطعه عمومی را از گره می‌گیرد و بررسی می‌کند که با شناسه هش برابر باشند. فراخوانی را در شبیه‌ساز اجرا می‌کند و پیش از اثبات هر چیزی گس و کارمزد را چاپ می‌کند:

gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)

سپس رونوشت ورودی را مهروموم می‌کند (در ادامه)، فراخوانی را محلی و تحت پروفایل FRI زنجیره، با ورودی‌ها به‌عنوان بردار خصوصی، اثبات می‌کند، بسته‌ای را که کارمزد را می‌پردازد اثبات می‌کند، ارسال می‌کند، منتظر می‌ماند و رسید را چاپ می‌کند. روی chain 20 اثبات فراخوانی به تراکنش خودش متصل است (اتصالی که هش genesis را در بر می‌گیرد) و بسته دو اثبات است، پس یک فراخوانی سه اثبات دارد: فراخوانی، مجوز خرج tier-10 و بسته tier-14. دو خط بالا از فراخوانی رأی‌گیری ناشناس روی chain 18 در 2026-09-30 است (یک رأی‌گیری ناشناس).

هزینه. در chain 20 کف هزینه برابر است با BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉، که در آن BUNDLE_BASE برابر 0.001 RAND است، بایت‌ها شامل اثبات فراخوانی و پاکت ورودی هستند، و قیمت‌ها از 100 و 800 واحد آغاز شدند (هر واحد 10⁻⁹ RAND است) و با پر بودن بلوک‌ها تغییر می‌کنند. chain 20 آن‌ها را به 10,000 و 80,000 واحد محدود می‌کند و فقط بایت‌هایی که قیمت بایت را می‌پردازند (اثبات‌ها و پاکت‌های فراخوانی‌ها) در بار آن به حساب می‌آیند (byte_load: "paying")؛ در chain 18 و 19 قیمت‌ها سقفی نداشتند و بایت‌های هر تراکنش قیمت بایت را جابه‌جا می‌کرد. جدول ثابت دفتر کل یعنی 0.001 RAND + call_fee(tier)، که قاعدهٔ chain 15 بود، در آن اعمال نمی‌شود. کیف پول هزینه را پیش از وجود اثبات تعیین می‌کند، بنابراین اثبات را به سقف 4 MiB زنجیره قیمت‌گذاری می‌کند و دو گام قیمتی حاشیهٔ اطمینان می‌افزاید. چهار فراخوانی اندازه‌گیری‌شده در chain 18 هر کدام 0.0057 تا 0.0075 RAND هزینه داشتند (محاسبه). rand fee call <tier> --gas <limit> --bytes <B> کف هزینه را از خود نود می‌پرسد. private_payment چهار موجودی خصوصی را می‌خواند و اگر مجموعشان دست‌کم به آستانهٔ آن برسد، مازاد را منتشر می‌کند: خروجی‌ها [1, 0, 25, 0, 0, 0, 0, 0]. زنجیره اثبات، tier و همین هشت کلمه را می‌بیند؛ موجودی‌ها را هرگز نمی‌بیند.

پرچم‌ها: --tier T یک tier را اجباری می‌کند؛ --gas-limit <N|max> به‌جای مقدار پیش‌فرض، یعنی گاز دقیق گردشده به بالا تا مضربی از 2^(tier − 2)، یک سقف گاز دقیق یا سقف خود tier را اعلام می‌کند؛ --fee <RAND> هزینه را تعیین می‌کند؛ --expect-public <FILE> پیش از اثبات‌سازی امتناع می‌کند مگر اینکه بخش عمومی برنامه همان کلمه‌های آن فایل باشد؛ --auditor <rand1…> رونوشت را برای یک حسابرس مشخص نیز مهروموم می‌کند؛ --print-call-key کلید هر فراخوانی را چاپ می‌کند؛ --no-envelope هیچ رونوشتی منتشر نمی‌کند؛ --cuda بک‌اند GPU را برمی‌گزیند (روی سخت‌افزار آزمایش نشده و با مهروموم کردن پاکت ناسازگار است). chain 20 استفاده از --cuda را در یک فراخوانی یکسره رد می‌کند: اثبات فراخوانی که به تراکنشش بسته شده، روی بک‌اند CPU ساخته می‌شود.

رسیدها

برای هر فراخوانی ثبت‌شده یک رسید ذخیره می‌شود و هنگام همگام‌سازی یا راستی‌آزمایی زنجیره توسط نود، دوباره محاسبه می‌شود:

→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
    "outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }

h_in تعهد نمک‌دار اثبات به ورودی‌های خصوصی است؛ به‌تنهایی چیزی افشا نمی‌کند. h_pub چکیدهٔ بخش عمومی برنامه در زمان استقرار است، یعنی public_digest برنامه، یا null برای برنامه‌ای که بدون آن مستقر شده است. فیلد effect وجود ندارد و سقف گاز اعلام‌شده در رسید نیست. explorer همان شیء را برای یک call با نام receipt در GET /transactions/:hash نشان می‌دهد، فراخوانی‌های یک برنامه را در GET /programs/:id فهرست می‌کند و GET /transactions?kind=call&program=<id> را فیلتر می‌کند.

پاکت‌های ورودی: افشای یک فراخوانی در آینده

rand call به‌طور پیش‌فرض رونوشت (salt, inputs) فراخوانی را در یک پاکت ورودی فراخوانی مهروموم‌شده منتشر می‌کند تا ورودی‌ها را بتوان بعداً بدون اثبات دوم نشان داد:

بخشبایتچیست
body12 + 16 + 4·n + 16salt ‖ inputs تحت یک کلید تازهٔ هر فراخوانی K_call
to_sender60K_call بسته‌بندی‌شده با کلید دیدِ خروجی فراخواننده
kem_ct1,088 یا 0یک کپسوله‌سازی ML-KEM-768 برای حسابرس، بدون حسابرس تهی
to_auditor60 یا 0K_call بسته‌بندی‌شده زیر راز آن کپسوله‌سازی

دادهٔ وابستهٔ بدنه H_IN است، پس پاکتی که روی فراخوانی دیگری برداشته شود برای هیچ‌کس باز نمی‌شود. زنجیره فقط اندازه را بررسی می‌کند (حداکثر max_call_envelope_bytes: 65,536 بایت در chain 20 و 18,432 در genesis بدون این فیلد) و آن را بی‌کم‌وکاست با rand_getCallEnvelope(tx) ارائه می‌دهد. سه کلید آن را باز می‌کنند و بیشتر از این نه: کلید دید فراخواننده (هر فراخوانی که آن کیف پول انجام داده)، کلید هر فراخوانی (همان یک فراخوانی)، کلید دید حسابرس (همان یک فراخوانی).

rand open-call <txhash>                    # as the caller
rand open-call <txhash> --as-auditor       # as the auditor
rand open-call <txhash> --call-key <hex>   # with the per-call key alone

open-call رونوشت را باز می‌کند، input_digest(salt, inputs) == H_IN را بررسی می‌کند، برنامه را روی ورودی‌های بازیابی‌شده با شبیه‌ساز دوباره اجرا می‌کند، خروجی‌ها را با خروجی‌های رسید مقایسه می‌کند — و اگر هر یک از دو بررسی شکست بخورد، با کد غیرصفر خارج می‌شود تا یک اسکریپت هشدار را با تأیید اشتباه نگیرد. --no-envelope بازگشت‌ناپذیر است: وقتی salt از میان رفت، هیچ‌کس، حتی فراخواننده، نمی‌تواند آن فراخوانی را باز کند.

از v0.6.8 اجرای دوباره روی همان بخش عمومی انجام می‌شود که اثبات روی آن ساخته شده است: کلمه‌های عمومی برنامه در زمان استقرار و، تحت hardening_v6، پس از آن‌ها اتصال هشت‌کلمه‌ای فراخوانی (issue شمارهٔ #116 در fullnode). در v0.6.7، open-call فقط برای برنامه‌ای کار می‌کرد که بدون بخش عمومی مستقر شده بود؛ برای برنامه‌ای که بخش عمومی داشت، ورودی‌ها را چاپ می‌کرد و سپس با re-running the program on these inputs failed: PublicIndex(0) متوقف می‌شد و با کد غیرصفر خارج می‌شد، بی‌آنکه هیچ‌یک از دو بررسی را اجرا کند.

وضعیت برنامه (RPL-2)

chain 20 نخستین زنجیره‌ای است که RPL-2 دارد و بخش program_state در genesis آن را روشن کرده است. یک برنامه سه چیز می‌گیرد که پیش‌تر نداشت (docs/program-state.md در fullnode، crates/randprotocol-core/src/ledger/program_state.rs در v0.6.8):

سلول‌هانگاشتی از یک کلید هشت‌کلمه‌ای به یک مقدار هشت‌کلمه‌ای، برای هر برنامه. سلول غایب هشت صفر خوانده می‌شود؛ نوشتن هشت صفر آن را حذف می‌کند. در ریشهٔ وضعیت متعهد می‌شود
یک خزانهیک موجودی عمومی برای هر دارایی (0 همان RAND است). ارزش از طریق bundle یک invoke وارد می‌شود و به شکل note‌هایی که زنجیره محاسبه می‌کند خارج می‌شود
توکن مخصوص خودشتوکنی که با rand token create --program <id> ثبت شود (مرجع ضرب Program(id)، بدون عرضهٔ اولیه) فقط با invokeهای همان برنامه ضرب و سوزانده می‌شود

سلول‌ها و موجودی‌های خزانه عمومی‌اند، مانند عرضهٔ یک توکن. هیچ‌چیز در آن‌ها note نیست.

Invoke (Action 33) فراخوانی‌ای است که یک گذار نیز اعلام می‌کند و اثبات فراخوانی‌اش نشان می‌دهد برنامه دقیقاً همان گذار را می‌پذیرد:

Invoke     { program, proof, input_envelope, transition }
Transition { reads:  [{ key, value }]     // cells read, with the values read; keys strictly ascending
             writes: [{ key, value }]     // cells written; keys strictly ascending
             inflow: none | deposit | burn
             pays:   [{ asset, amount, recipient, r, envelope }]   // notes out of the vault
             mints:  [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token

آنچه وارد می‌شود همان است که bundle می‌سوزاند: burn_r همان RAND واریزشده به خزانه است؛ burn_a از burn_asset یک توکن است که واریز می‌شود (جریان ورودی deposit) یا نابود می‌شود (جریان ورودی burn، فقط توکن خود برنامه). آنچه خارج می‌شود به ازای هر پرداخت یک note است که زنجیره محاسبه می‌کند، نخست پرداخت‌ها سپس ضرب‌ها، با مبلغ و pk گیرنده که عمومی‌اند، همان‌طور که در ضرب عمومی‌اند. دفتر کل گذار را فقط در صورتی اعمال می‌کند که برنامه وجود داشته باشد، شکل مجاز باشد، هر سلول در reads همچنان مقدار اعلام‌شده را داشته باشد، خزانه (پس از واریز خود همین تراکنش) هر پرداخت را پوشش دهد، هر ضرب از توکن خود برنامه باشد و اثبات‌ها راستی‌آزمایی شوند. خوانشی که دیگر مطابقت نداشته باشد با StaleRead رد می‌شود؛ کیف پول دوباره می‌خواند و دوباره اثبات می‌سازد.

تفاوت invoke با call.

Call (RPL-1)Invoke (RPL-2)
بخش عمومی‌ای که اثبات روی آن ساخته می‌شودpublic ‖ call_bindingpublic ‖ call_binding ‖ context، گذار به‌صورت کلمه‌ها
کاری که زنجیره در صورت موفقیت می‌کندیک رسید ذخیره می‌کند؛ خروجی‌ها داده‌اندرسید را ذخیره می‌کند و گذار را اعمال می‌کند: سلول‌ها را می‌نویسد، خزانه را جابه‌جا می‌کند، note‌های پرداخت را می‌افزاید
ارزشفقط انتقال خود bundleواریز به خزانه از طریق سوزاندن‌های bundle؛ پرداخت‌ها و ضرب‌ها از آن
وابسته به وضعیتخیراثبات نه (بخش از تراکنش ساخته می‌شود)، اما بررسی خوانش بله، هنگام پذیرش و در pool
هزینهکف هزینهٔ فراخوانیکف هزینهٔ فراخوانی به‌علاوهٔ cell_fee برای هر سلول ایجادشده

برنامه کلمهٔ context شمارهٔ i را با READ_PUBLIC(public_len + 8 + i) می‌خواند. کلمهٔ 0 نسخه است (1)، کلمه‌های 1 تا 4 تعداد خوانش‌ها، نوشتن‌ها، پرداخت‌ها و ضرب‌ها، سپس burn_r، نوع جریان ورودی، burn_asset و burn_a، سپس هر خوانش و نوشتن به‌صورت کلید و مقدار (هر کدام 8 کلمه)، و سپس هر پرداخت به‌صورت دارایی و مبلغ. گیرنده‌ها در context نیستند: برنامه مبلغ‌ها را تعیین می‌کند و binding مشخص می‌کند به چه کسی پرداخت می‌شود. برنامه باید هر کلمهٔ context را که به آن نشان داده می‌شود مقید کند و نباید هیچ مسیر panic داشته باشد، زیرا panic handler در guest-sdk اجرا را متوقف می‌کند و اجرای متوقف‌شده اجرایی اثبات‌پذیر است.

محدودیت‌های chain 20 (rand_getLimits.program_state):

محدودیتchain 20
cell_fee10,000,000 واحد، 0.01 RAND، برای هر سلول ایجادشده (سلولی که غیرصفر نوشته شود و پیش‌تر چیزی در آن ذخیره نبوده باشد)؛ بازنویسی یا حذف یک سلول رایگان است
max_reads8 سلول
max_writes8 سلول
max_payouts4 note، پرداخت‌ها و ضرب‌ها روی هم

قاعدهٔ بخش معمولاً سخت‌گیرانه‌تر است: کل بخش باید در جدول عمومی‌ای جا شود که فراخوانی به همان برنامه روی آن اثبات می‌شود، یعنی 127 کلمه برای برنامه‌ای بدون ورودی عمومی. این 119 کلمهٔ context باقی می‌گذارد، مثلاً سه خوانش، سه نوشتن و چهار پرداخت.

فرمان‌ها و RPC (کیف پول v0.6.8):

rand program build --guest rpl2_counter --out counter.json   # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>]                   # rand_getProgramCell / rand_getProgramCells
rand program vault <id>                                      # rand_getProgramVault

program invoke پیش از اثبات‌سازی هر مدخل reads را با سلول زنده می‌سنجد و در صورت خوانش کهنه، چه پیش از اثبات و چه هنگام درج در بلوک، با کد 3 خارج می‌شود، تا اسکریپت دوباره بخواند و دوباره تلاش کند. rand_estimateFee با "kind": "invoke" هزینه را نقل می‌کند. قالب فایل گذار در docs/cli.md در fullnode آمده است. برنامه‌های کامل RPL-2 مستقر در chain 20 (amm، stableswap، orderbook، stablecoin، lending، perp، crowdfund، stoploss) و برنامه‌های RPL-1 (sealed-auction، ballot، eligibility، private-join، credit-score) در randprotocol/example-programs هستند.

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

عمومیپنهان
شناسه برنامه و کد آن؛ سطح (tier)؛ سقف گس اعلام‌شده؛ هشت خروجی؛ H_IN؛ کارمزد بسته و فیلدهای عمومی آن؛ اندازه envelope؛ اینکه فراخوانی از Keccak استفاده کرده یا SHA-256 و تقریباً با چه تکراری (سرآیند اثبات ارتفاع هر جدول هش را اعلام می‌کند)ورودی‌های خصوصی؛ رجیسترها، حافظه، شاخه‌ها؛ تعداد واقعی چرخه‌ها و گس دقیق؛ salt مربوط به H_IN؛ اینکه چه کسی آن را فراخوانی کرده و با چه چیزی هزینه را پرداخته است

دو اثبات از یک اجرای واحد، بایت‌های متفاوتی هستند. این در chain 15 و پیش از آن کافی نبود: فراخوانی‌ای با 31 کلمه خصوصی یا کمتر، جدولی بسیار کوچک برای پنهان‌سازی اثبات می‌کرد و ورودی‌هایش از روی اثبات قابل بازیابی بود. اثبات‌کننده از v0.6 این جدول‌ها را روی کف 128 ردیف نگه داشته است، و مجموعه قید 7 (chain 16 و پس از آن) مجموع‌های هر جدول را که هنوز نشت می‌کردند کور می‌کند. hc الزام‌آور (binding) است اما پنهان‌کننده (hiding) نیست: هر کسی که بتواند برنامه‌های نامزد را برشمرد، می‌تواند تأیید کند کدام یک مستقر شده است.

چه چیزهایی هنوز ممکن نیست

آن‌طور که fullnode اعلام می‌کند:

  • فراخوانی بین‌برنامه‌ای وجود ندارد و هیچ وضعیتی بیرون از RPL-2 نیست. یک Call یک اثبات روی یک اجراست؛ هیچ چیز از فراخوانی‌ای به فراخوانی دیگر منتقل نمی‌شود، جز آنچه فراخواننده خودش برمی‌گرداند. در chain 20، یک Invoke سلول‌ها و خزانه (vault) خود برنامه را می‌خواند و می‌نویسد (بالا)؛ در هیچ chain پیش از 20 وضعیت برنامه وجود ندارد، و یک invoke نمی‌تواند به سلول‌های برنامه دیگر دست بزند.
  • هیچ انتقال برنامه‌محوری بیرون از یک vault وجود ندارد. نوع اثر 1، یعنی انتقال پرداخت‌شده توسط برنامه به یک حساب، همراه با حساب‌ها حذف شد و خروجی‌های یک Call گزاره‌اند. در chain 20 یک Invoke از vault خود برنامه پرداخت می‌کند، حداکثر چهار نوت، با مبالغ و گیرندگان عمومی.
  • هیچ کامپایلری در کیف پول نیست. برنامه‌ها یا guestهای اسمبل‌شده داخلی‌اند، یا imageهایی که با rand-guest در مخزن circuits ساخته شده‌اند.
  • ورودی عمومی در زمان deploy ثابت می‌شود، نه در هر فراخوانی. مجموعه قید 6 دستورهای READ_PUBLIC و H_PUB را افزود. در chain 20 یک deploy می‌تواند تا 32,768 کلمه عمومی را حمل کند، شناسه برنامه آن‌ها را مقید می‌کند، و اثبات هر فراخوانی در برابر چکیده آن‌ها بررسی می‌شود. کلمه عمومی به‌ازای هر فراخوانی وجود ندارد.
  • فراخوانی‌های سیستمی هش اثبات‌های بزرگ می‌سازند. اثبات حامل keccak حدود 3.2 MB است، یعنی کاملاً بالاتر از سقف پیش‌فرض 2 MiB برای اثبات؛ اثبات حامل sha256 حدود 400 KB به آن می‌افزاید. سقف chain 20 برابر 4,194,304 بایت است (4 MiB، مانند chain 17 تا 19؛ در chain 13 تا 16 برابر 8 MiB بود) که هر دو را می‌پذیرد: در chain 18 یک فراخوانی سطح 12 با جدول Keccak برابر 3.35 MB و یک فراخوانی سطح 14 با جدول SHA-256 برابر 1.91 MB اندازه‌گیری شد (برنامه‌های نمونه). هر فراخوانی همچنین به 128 جایگشت Keccak-f و 128 فشرده‌سازی SHA-256 محدود است (در ادامه)، و هر KiB اثبات به قیمت بایت محاسبه می‌شود.
  • برنامه‌های EVM و Solana در chain 20 مستقر نمی‌شوند، و فراخوانی‌هایشان از سقف سطح فراخوانی فراتر است. guestهای مفسر (evm.bin با 18,009 کلمه؛ sbpf.bin با 8,317) و imageهای ترجمه‌شده (ERC-20 با 11,686 کلمه؛ SPL Token با 65,096) از سقف پیش‌فرض 4,096 کلمه بزرگ‌ترند و زیر 65,535 قرار دارند که chain 13 تعیین کرد و هر chain پس از آن حفظ کرده است. هر دو image ترجمه‌شده در 2026-09-19 روی chain 13 مستقر شدند، SPL Token با ELF خود به‌عنوان ورودی عمومی. chain 14 و 15 از نو ساخته شدند و هیچ‌کدام از این دو image روی آن‌ها دوباره مستقر نشد. chain 20 مانند chain 19 از deploy هر چهار مورد خودداری می‌کند: بر اساس قاعده hardening_v6 آن، برنامه‌ای که هیچ فراخوانی سطح 14 نتواند آن را در خود جای دهد، یعنی بیش از 8,180 کلمه بدون بخش عمومی و کمتر از آن با بخش عمومی، هنگام deploy رد می‌شود (ProgramUncallable)، تا کارمزد deploy برای برنامه‌ای که هیچ‌کس نمی‌تواند فراخوانی‌اش کند پرداخت نشود. یک فراخوانی ERC-20 در سطح 16 یا 18 اثبات می‌شود: approve سطح 16 در 2026-09-19 روی chain 13 با یک اثبات تولیدی 3,412,405 بایتی فراخوانی شد (مترجم Ethereum)، و chain 20 هر فراخوانی بالاتر از سطح 14 را رد می‌کند (مورد بعدی)، پس امروز هیچ فراخوانی ERC-20 در آنجا پذیرفتنی نیست. فراخوانی‌های SPL Token سطح 20 هستند که به حدود 330 GB نیاز دارد؛ هیچ اثبات فراخوانی SPL وجود ندارد (حافظه اثبات). opcodeهای زمینه بلوک در EVM trap می‌کنند، و هیچ وضعیت EVM روی زنجیره وجود ندارد.
  • یک فراخوانی در سطح 14 یا پایین‌تر اثبات می‌شود. از fullnode v0.5.6 (2026-09-25) اثبات فراخوانی بالاتر از سطح 14 رد می‌شود (CallTierTooHigh)، و همچنین اثباتی با بیش از 128 جایگشت Keccak-f یا 128 فشرده‌سازی SHA-256. approve ERC-20 سطح 16 در chain 13 در chain 20 رد می‌شد، همان‌طور که در chain 15، 18 و 19 می‌شد، و هر فراخوانی ERC-20 سطح 18 یا SPL Token سطح 20 نیز چنین است؛ این‌ها همچنان خارج از زنجیره اثبات می‌شوند (بودجه).
  • اثبات‌سازی با GPU هرگز روی سخت‌افزار اجرا نشده است. --cuda به یک build با --features cuda و PTX کامیت‌شده نیاز دارد؛ هیچ‌کدام روی fleet وجود ندارد. در chain 20 اثبات فراخوانی صرف‌نظر از build روی بک‌اند CPU ساخته می‌شود.
  • hc پنهان‌کننده نیست، همان‌طور که در بالا آمد، و یک درخواست witness همچنان به node می‌گوید کیف پول کدام leaf را خرج می‌کند.

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