Rand Protocol
مستندات
برنامه‌های RISC-V

مجموعه دستورالعمل RISC-V

معنای RV32IM در Rand - دستورهایی که zkVM اثبات می‌کند، آنچه بررسی‌کننده رد می‌کند، هشت فراخوانی سیستمی، حافظه، قالب تصویر (image) که برنامه در آن عرضه می‌شود، و سقف اندازهٔ برنامه.

این صفحه را پیش از نوشتن برنامه برای Rand بخوانید. توضیح می‌دهد کد شما روی چه ماشینی اجرا می‌شود، زنجیرهٔ ابزار چه چیزهایی را رد می‌کند، برنامه چگونه با بیرون ارتباط می‌گیرد و چه پرونده‌ای را باید مستقر کنید. صفحه‌های بعدی از آن استفاده می‌کنند: استقرار برنامهٔ Rust، استقرار برنامهٔ RISC-V و دو ترجمه‌گر.

ماشین

برنامه‌های Rand از نوع RV32IM هستند: مجموعهٔ پایهٔ اعداد صحیح ۳۲ بیتی RISC-V به‌همراه افزونهٔ ضرب و تقسیم. این برنامه‌ها درون zkVM مربوط به Rand اجرا می‌شوند و zkVM هر اجرا را با یک batch STARK مبتنی بر Plonky3 اثبات می‌کند. تأییدکننده هرگز هیچ دستورالعملی را نمی‌بیند. او اثبات را با hc می‌سنجد، یعنی چکیدهٔ Poseidon2 درون‌مدار برنامه.

گروهدستورها
بالایی / پرش‌ها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

JALR همان‌طور که RV32I می‌گوید بیت ۰ مقصد خود را پاک می‌کند و رمزگذاری آن باید funct3 = 0 داشته باشد. هر دو قاعده با مجموعهٔ قید ۷ (chain 16) آمدند؛ پیش از آن، مقصد فرد قابل واکشی نبود و اجرا قابل اثبات نمی‌شد. مقصدی که زوج است اما مضرب چهار نیست همچنان قابل واکشی نیست.

ممیز شناور، عمل‌های اتمی (افزونهٔ A)، دستورهای فشرده (افزونهٔ C) و ثبات‌های ۶۴ بیتی وجود ندارند. تقسیم ۶۴ بیتی در C به فراخوانی یک تابع کمکی نرم‌افزاری تبدیل می‌شود؛ مسیر C را ببینید.

چه چیزی رد می‌شود

rand-guest check هر کلمهٔ متن برنامه را از رمزگشای خود ماشین عبور می‌دهد. برنامه‌ای که از این بررسی بگذرد، به دلیل رمزگذاری، شمارهٔ فراخوان سیستمی یا چیدمان نمی‌تواند در مدار شکست بخورد. بررسی‌کننده این موارد را رد می‌کند:

یافتهتوضیح
Compressedرمزگذاری ۱۶ بیتی RVC؛ ماشین فقط کلمه‌های ۳۲ بیتی را رمزگشایی می‌کند
Undecodableکد عملیات، funct یا مقدار شیفتی که ماشین پیاده‌سازی نکرده است
FenceFENCE یا FENCE.I؛ ماشین ترتیب حافظه ندارد
Csrهر دستور CSR؛ ماشین CSR ندارد
EbreakEBREAK؛ تله‌ها مدل نشده‌اند
Syscallیک ecall که a7 آن درست پیش از آن با li مقدار گرفته و نام هیچ فراخوان سیستمی نیست
Capکلمه‌های بیشتر از --max-words، با همان شمارشی که بارگذار انجام می‌دهد
Layoutمبنای متنی که هم‌تراز با کلمه نیست

برنامه‌ای چهار کلمه‌ای با یک fence، یک ebreak و ecall شمارهٔ ۹، آن‌گونه که rand-guest check گزارش می‌کند (کد خروج ۱):

$ rand-guest check bad.elf
note: the loader refuses this image (Decode { index: 0, word: 267386895, err: Opcode(15) }), so its data prologue could not be counted; the word count below is the text alone
0x00001000  0x0ff0000f  Fence: FENCE/FENCE.I: the machine has no memory ordering instructions
0x00001004  0x00100073  Ebreak: EBREAK: traps are not modelled
0x0000100c  0x00000073  Syscall: ecall with a7 = 9, which is no syscall the machine implements
4 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED

ecallی که a7 آن را بررسی‌کننده نمی‌تواند ببیند (مقداردهی با چیزی جز li، یا پس از یک انشعاب) در unresolved_ecalls شمرده می‌شود و رد نمی‌شود. ماشین همچنان هنگام اجرا روی شمارهٔ نادرست تله می‌کند.

بررسی‌کننده مقدار اشاره‌گرها، طول‌ها یا اندیس ورودی‌ها، پشتهٔ ۶۴ KiB نسبت به RAM و هم‌ترازی نشانی‌هایی را که هنگام اجرا محاسبه می‌شوند بررسی نمی‌کند. دسترسی ناهم‌تراز نقض قید است، به همین دلیل زنجیرهٔ ابزار با -unaligned-scalar-mem کامپایل می‌کند. rand-guest run هر تلهٔ زمان اجرا را گزارش می‌کند.

فراخوان‌های سیستمی

ECALL شمارهٔ فراخوان سیستمی را از a7، آرگومان اول را از a0 و آرگومان دوم را از a1 می‌خواند. مقدار بازگشتی در a0 برمی‌گردد.

#فراخوان سیستمیاثرگسRust (guest-sdk)C (guest.h)
0HALTاجرا را پایان می‌دهد1halt()rand_halt()
1WRITE_OUTPUT slot wordخروجی عمومی slot < 8 را تنظیم می‌کند، حداکثر یک بار برای هر slot؛ slotهای نوشته‌نشده صفر هستند1write_output(slot, word)rand_write_output
2READ_INPUT idxکلمهٔ ورودی خصوصی idx را برمی‌گرداند که به تعهد نمک‌دار H_IN بسته شده است1read_input(idx)rand_read_input
3POSEIDON2 ptr nn ≤ 4096 کلمه را در نشانی کلمهٔ ptr درهم می‌کند و چکیدهٔ ۸ کلمه‌ای را روی ptr..ptr+8 می‌نویسد؛ بدون پر کردن3 + 3·⌈n/4⌉unsafe poseidon2(ptr, n)rand_poseidon2
4KECCAK ptrیک جایگشت Keccak-f[1600] درجا روی ۵۰ کلمه192keccak(state), keccak256(msg)rand_keccak
5SHA256 ptrیک تابع فشرده‌سازی SHA-256 درجا روی ۲۴ کلمه64sha256_compress(buf), sha256(msg)rand_sha256_compress
6READ_PUBLIC idxکلمهٔ idx از قطعهٔ عمومی را برمی‌گرداند که به H_PUB بدون نمک بسته شده است1read_public(idx)rand_read_public
7POSEIDON2_LEN ptr nPOSEIDON2 با طول n که در اسفنج بسته می‌شود، دست‌کم یک جایگشت (مجموعهٔ قید ۷)3 + 3·max(⌈n/4⌉, 1)poseidon2_len(buf, n)ندارد

هر دستور دیگر ۱ گس هزینه دارد: گس برای هر سطر cpu یک واحد است و فقط فراخوان‌های درهم‌ساز سنگین‌ترند (مجموعهٔ قید ۸، از chain 18). یک فراخوان Poseidon2 شامل سطر ecall خود، دو سطر بازنویسی و ⌈n/4⌉ سطر جذب است که هرکدام ۳ گس هزینه دارند.

POSEIDON2 پیام را پر نمی‌کند. اسفنج مجموعه‌ای از مسیرهای نخست خود را با هر قطعهٔ چهار کلمه‌ای بازنویسی می‌کند، پس [a]، [a, 0]، [a, 0, 0] و [a, 0, 0, 0] همگی یک چکیده می‌دهند. برنامه‌ای که داده‌ای با طول متغیر را درهم می‌کند باید طول را در پیام بگنجاند، رمزگذاری با طول ثابت را درهم کند، یا POSEIDON2_LEN را فراخوانی کند که n را در ظرفیت اسفنج می‌نشاند و برای همان کلمه‌ها چکیدهٔ متفاوتی می‌دهد. در SDK مربوط به Rust، poseidon2 یک unsafe fn روی اشاره‌گر خام است و poseidon2_len فراخوانی امن روی یک برش است؛ keccak و sha256_compress به‌ترتیب &mut [u32; 50] و &mut [u32; 24] می‌گیرند.

یک شکاف در زنجیرهٔ ابزار. در circuits aeacf31، ماشین، شبیه‌ساز و SDK مربوط به Rust فراخوان سیستمی ۷ را پیاده‌سازی کرده‌اند، اما rand-guest check نکرده است: فهرست فراخوان‌های آن به ۶ ختم می‌شود، پس ecallی با a7 = 7 را یافتهٔ Syscall گزارش می‌کند و rand-guest build برای برنامه‌ای که poseidon2_len را فراخوانی می‌کند با کد غیرصفر خارج می‌شود (circuits issue #1). guest.h نیز برای آن پوشش‌دهنده‌ای ندارد. تا زمان رفع این مشکل، برنامه‌ای که با ابزار پیش‌فرض ساخته می‌شود برای پیام‌های با طول ثابت از POSEIDON2 استفاده می‌کند.

یک اثبات دقیقاً این‌ها را منتشر می‌کند: hc، H_IN، H_PUB، ردهٔ اجرا (tier)، سقف گس اعلام‌شده و هشت کلمهٔ خروجی. دو محدودیت امروز روی زنجیره اعمال می‌شود:

  • قطعهٔ عمومی هنگام استقرار ثابت می‌شود. در chain 20، rand program deploy --public کلمه‌های عمومی یک برنامه را تعیین می‌کند (حداکثر max_program_public_words، یعنی 32,768) و هر فراخوانی با آن‌ها سنجیده می‌شود. هیچ کلمهٔ عمومی مختص هر فراخوانی از خود برنامه وجود ندارد. chain 20 مانند chain 19 هشت کلمه به قطعهٔ هر فراخوانی پس از آن‌ها می‌افزاید، یعنی اتصال فراخوانی که اثبات را به تراکنش آن گره می‌زند (در chain 20 شامل هش genesis نیز هست)؛ برنامه کلمه‌های خود را در همان اندیس‌های پیشین می‌خواند. یک Invoke از نوع RPL-2 گذار اعلام‌شده را پس از این اتصال می‌افزاید که برنامه آن را از public_len + 8 به بعد می‌خواند (برنامه‌ها). در زنجیره‌ای که فیلدهای محدودیت را ندارد، مانند chain 12، قطعهٔ عمومی تهی است و READ_PUBLIC چیزی برای خواندن ندارد.
  • کلمه‌های ورودی خصوصی یک فراخوانی را پاکت مهروموم‌شده محدود می‌کند. مقدار max_call_envelope_bytes در chain 20 که 65,536 است، 16,071 کلمه را مجاز می‌کند؛ زنجیره‌ای بدون این فیلد 4,295 کلمه را مجاز می‌کند.

یک فراخوانی KECCAK یا SHA256 جدولی به اثبات می‌افزاید و سرآیند اثبات سپس نشان می‌دهد که فراخوانی درهم‌سازی کرده است. اثبات حاوی keccak حدود 3.3 MB است: بیش از سقف پیش‌فرض اثبات یعنی 2 MiB و کمتر از 4 MiB در chain 20، و یک فراخوانی حداکثر می‌تواند از 128 جایگشت Keccak-f و 128 فشرده‌سازی SHA-256 استفاده کند (برنامه‌ها).

حافظه

  • ۳۲ ثبات؛ x0 صفر است.
  • RAM از صفر آغاز می‌شود. هر نشانی کمتر از 2³⁰ است.
  • اسکریپت پیوندگر پیش‌فرض، guest-sdk/guest.ld، برنامه را در 0x1000 قرار می‌دهد، سپس .rodata، .data و .bss و سپس یک پشتهٔ ۶۴ KiB در __stack_top.
  • برنامه‌ای که قطعهٔ داده دارد، زیر متن خود به جا برای مقدمهٔ بارگذار نیاز دارد (بخش بعد). .ld اختصاصی آن ORIGIN را به 0x10000 بالا می‌برد، همان‌طور که evm.ld و sbpf.ld می‌کنند.

ظرف تصویر

rand-guest build و rand-guest pack یک قالب پرونده می‌نویسند: ظرف تصویر. همهٔ اعداد کلمه‌های ۳۲ بیتی little-endian هستند:

[0x444e4152 ("RAND"), 1, text_base, n_text, data_base, n_data] text… data…

بارگذار ظرف را به برنامه‌ای به شکل { base_pc, words } تبدیل می‌کند:

  1. برای هر کلمهٔ داده‌ٔ غیرصفر یک جفت li/sw می‌نویسد. کلمه‌های صفر نادیده گرفته می‌شوند، زیرا RAM از پیش صفر می‌خواند.
  2. آن مقدمه را مستقیماً زیر text_base قرار می‌دهد، پس base_pc = text_base − 4 × prologue words. مقدمه به _start خود برنامه می‌رسد و از آن عبور می‌کند.
  3. hc و شناسهٔ برنامه روی کل words، با احتساب مقدمه، محاسبه می‌شوند. بنابراین hc داده را مانند کد به خود می‌بندد.

ظرفی بدون داده مقدمه‌ای ندارد و برنامه و hc آن با یک فایل دودویی تخت بدون سرآیند در همان مبنا یکسان است.

rand program deploy در fullnode نسخهٔ v0.3 و پس از آن، ظرف را از روی کلمهٔ نخست آن تشخیص می‌دهد. 0x444e4152 هرگز به‌عنوان دستورالعمل رمزگشایی نمی‌شود، پس یک .bin خام را نمی‌توان با ظرف اشتباه گرفت. پرونده‌ای که با عدد جادویی آغاز شود اما ناقص باشد خطاست و به حالت جایگزین نمی‌رود.

دو هویت

نامچیستچه کسی استفاده می‌کند
hcچکیدهٔ Poseidon2 مدار از (base_pc, len, words)اعتبار هر اثبات یک فراخوانی در برابر آن سنجیده می‌شود؛ rand_getProgram آن را به‌صورت code_hash نشان می‌دهد
program idblake3("rand-program" ‖ base_pc ‖ words)؛ استقرار با --public واژه‌های عمومی را نیز، با برچسب rand-program-2، پیوند می‌زندکلیدی که برنامهٔ مستقرشده زیر آن ذخیره و فراخوانی می‌شود

rand-guest build و rand-guest info هر دو را چاپ می‌کنند. hc الزام‌آور است اما پنهان‌کننده نیست: هر کسی که برنامه‌ای نامزد در اختیار دارد می‌تواند آن را تأیید کند.

سقف برنامه

یک استقرار حداکثر می‌تواند به اندازهٔ سقف برنامهٔ زنجیره، بر حسب واژه، باشد.

زنجیرهسقف
ژنسیس بدون max_program_words (chain 12)4,096
ژنسیس با max_program_wordsهمان مقدار، 1..=65535
حد سخت zkVM، در هر زنجیره65,535

سقف بخشی از هش ژنسیس است، بنابراین روی زنجیرهٔ در حال اجرا قابل تغییر نیست. rand-guest با --max-words (پیش‌فرض 4,096) بررسی می‌کند. rand program deploy با rand_estimateFee از گره می‌پرسد و برنامهٔ بیش از سقف را پیش از آنکه چیزی را اثبات کند رد می‌کند.

Chain 20، زنجیرهٔ فعال، سقف را بالا می‌برد. هش ژنسیس آن 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135 و max_program_words آن 65,535 است. حدهایی را که chain 13 (زنجیرهٔ v0.4) معرفی کرد حفظ می‌کند، به‌جز سقف اثبات که chainهای 17 تا 20 آن را نصف می‌کنند. حدهای دیگر آن:

حدchain 20
max_proof_bytes4,194,304 (4 MiB)؛ 8 MiB در chainهای 13 تا 16
max_block_bytes20,971,520 (20 MiB)
max_call_envelope_bytes65,536
max_program_public_words32,768

در chain 20، مانند chain 19، سقف واژه حد الزام‌آور نیست. ژنسیس آن hardening_v6 را تنظیم می‌کند که بر پایهٔ آن استقرار رد می‌شود (ProgramUncallable) اگر هیچ فراخوانی نتواند هرگز برنامه را اثبات کند. هر فراخوانی به tier 14 محدود است که جدول Poseidon2 آن 2,048 جایگشت دارد. چکیدهٔ برنامه به ازای هر چهار واژه یک جایگشت، ردیف salt ورودی یکی و هشت واژهٔ پیوند فراخوانی دو جایگشت مصرف می‌کند، که 2,045 باقی می‌گذارد: 8,180 واژه برای برنامه‌ای بدون ورودی عمومی، و کمتر در کنار آن (8,168 با نُه واژهٔ عمومی). استخر هر گره چنین استقراری را در هر زنجیره‌ای رد می‌کند.

تجمیع بلوک در chain 20 خاموش است: ژنسیس آن بخش aggregation ندارد. Chain 20 از 2026-10-01 06:17 UTC روی fullnode v0.6.8 فعال است، یک هارد فورک که از وضعیت chain 19 در ارتفاع 7,258 بریده شد (chain 19 در 7,700 متوقف شد)؛ chain 19 (فعال از 03:43 UTC همان روز) خود یک ژنسیس مجدد از chain 18 در ارتفاع 141,935 بود. برنامه‌ها از یک برش عبور نمی‌کنند، بنابراین هیچ برنامه‌ای که روی زنجیرهٔ قبلی مستقر شده بود روی chain 20 وجود ندارد. Chain 20 همچنین نخستین زنجیره با وضعیت برنامهٔ RPL-2 است (برنامه‌ها). این زنجیره از طریق این گره‌های راه‌انداز روی شبکه اجرا می‌شود:

/ip4/164.90.239.200/tcp/30303/p2p/12D3KooWEQEbUwZRgqhDPXhW7vNBmcRcnhUDGUADe81VFdv3ALYe
/ip4/165.245.173.74/tcp/30303/p2p/12D3KooWJyc5oDHggAr89e9QrRFBy9Tg8TxXh18yzrb1SM6KKLYw
/ip4/188.166.235.187/tcp/30303/p2p/12D3KooWSyX84P1DAbWByLHpRBMLRWD53r3Jj4aqVt6tHoF8B2ZQ
تصویرواژه‌هادر 4,096 جا می‌شوددر 65,535 جا می‌شودروی chain 20 مستقر می‌شود (8,180 قابل فراخوانی)
fib (Rust)26بلهبلهبله
c-fib (C)25بلهبلهبله
ERC-20، ترجمه‌شده (مرحلهٔ دوم)11,686خیربلهخیر
evm.bin، مفسر EVM18,009خیربلهخیر
sbpf.bin، مفسر sBPF8,317خیربلهخیر
SPL Token، ترجمه‌شده65,096خیربله، با 439 واژه باقی‌ماندهخیر

ردهها

ردهٔ t یک اثبات، اجرا را به 2ᵗ − 1 ردیف محدود می‌کند: چرخه‌ها به‌علاوهٔ ردیف‌های چکیده (یکی به ازای هر چهار واژهٔ برنامه، یکی به ازای هر چهار واژهٔ ورودی، و چکیدهٔ ورودی عمومی). رده همچنین بودجه‌ای جداگانه برای Poseidon2 تعیین می‌کند. rand-guest run --tier t هر دو را گزارش می‌کند:

tier 10: fits (cycles 146 of 1023, Poseidon2 permutations 10 of 128)

رده عمومی است، و همچنین سقف گازی که اثبات اعلام می‌کند. در chain 20 کارمزد فراخوانی از این سقف و اندازهٔ اثبات پیروی می‌کند، نه از رده: دست‌کم 0.001 RAND + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉، به قیمت‌های جاری زنجیره، که از 100 و 800 واحد 10⁻⁹ RAND آغاز می‌شوند، با پُری بلوک تغییر می‌کنند و در chain 20 در 10,000 و 80,000 واحد متوقف می‌شوند (کارمزدها). سقف نباید از بالاترین حد سرایند فراتر رود، یعنی (2ᵗ − 1) + 2ᵗ⁻² به‌علاوهٔ 191 به ازای هر بلوک Keccak و 63 به ازای هر بلوک SHA-256 که اثبات اعلام می‌کند: 1,279 در tier 10 و 20,479 در tier 14 بدون جدول هش. rand-guest run چرخه‌ها و جایگشت‌ها را گزارش می‌کند، نه گاز را. rand call ابتدا برنامه را اجرا می‌کند و پیش از اثبات، گازی را که مصرف می‌کند و سقفی را که اعلام می‌کند چاپ می‌کند؛ این سقف به‌طور پیش‌فرض گاز دقیق است که به بالاترین مضرب 2ᵗ⁻² گرد شده است:

gas: 5636 (declaring 6144, tier 12)

آن خط از یک فراخوانی روی chain 18 در 2026-09-30 است (یک رأی‌گیری ناشناس). در زنجیره‌ای بدون بخش gas در ژنسیس، یعنی chain 15 و قبل‌تر، کارمزد یک جدول پلکانی ردهای بود: 0.001 + (0.001 + 0.0001 × (t − 10) / 2) RAND. حافظه‌ای که یک اثبات لازم دارد با رده به‌شدت بالا می‌رود؛ سخت‌افزار اعتبارسنج و تجمیع‌گر را ببینید.

از fullnode v0.5.6 (2026-09-25) یک فراخوانی می‌تواند حداکثر tier 14 را اعلام کند: گره اثبات فراخوانی با ردهٔ بالاتر را با CallTierTooHigh پیش از ساخت کلید راستی‌آزما رد می‌کند، و بلوکی که چنین اثباتی را حمل کند نامعتبر است. بودجهٔ tier 14 برابر 16,383 ردیف و 2,048 جایگشت Poseidon2 است، و چکیدهٔ خود برنامه تقریباً یک جایگشت به ازای هر چهار واژه مصرف می‌کند، پس برنامه‌ای بیش از 8,180 واژه قابل فراخوانی نیست، و chain 20 مانند chain 19 استقرار آن را رد می‌کند. نوشتن برنامه با Rust یا C بودجه را گام‌به‌گام بررسی می‌کند.

آخرین بار با کامیت c0878c3f از fullnode (زنجیرهٔ 21) در تاریخ 2026-10-12، و circuits `aeacf31` (`rand-guest/README.md`, `rand-guest/src/check.rs`, `research/docs/01-isa.md`, `guest-sdk/`), fullnode tag v0.6.8 `c9c9bd3c` (`docs/program-state.md`, `ledger/program_state.rs`, `deploy/cut-chain20-genesis.sh`), chain 20's genesis and `rand_getLimits`, read on 2026-10-01, fullnode tag v0.8.0 `c0878c3f` (`docs/zkvm.md` §8a, constraint set 9), `deploy/genesis-chain21.json` and `rand_getLimits` on chain 21, read on 2026-10-12 بررسی شد. هرجا کد و این صفحه با هم اختلاف دارند، حق با کد است؛ به ما اطلاع دهید.