این صفحه را پیش از نوشتن برنامه برای 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 |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL 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 یا مقدار شیفتی که ماشین پیادهسازی نکرده است |
Fence | FENCE یا FENCE.I؛ ماشین ترتیب حافظه ندارد |
Csr | هر دستور CSR؛ ماشین CSR ندارد |
Ebreak | EBREAK؛ تلهها مدل نشدهاند |
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) |
|---|---|---|---|---|---|
| 0 | HALT | اجرا را پایان میدهد | 1 | halt() | rand_halt() |
| 1 | WRITE_OUTPUT slot word | خروجی عمومی slot < 8 را تنظیم میکند، حداکثر یک بار برای هر slot؛ slotهای نوشتهنشده صفر هستند | 1 | write_output(slot, word) | rand_write_output |
| 2 | READ_INPUT idx | کلمهٔ ورودی خصوصی idx را برمیگرداند که به تعهد نمکدار H_IN بسته شده است | 1 | read_input(idx) | rand_read_input |
| 3 | POSEIDON2 ptr n | n ≤ 4096 کلمه را در نشانی کلمهٔ ptr درهم میکند و چکیدهٔ ۸ کلمهای را روی ptr..ptr+8 مینویسد؛ بدون پر کردن | 3 + 3·⌈n/4⌉ | unsafe poseidon2(ptr, n) | rand_poseidon2 |
| 4 | KECCAK ptr | یک جایگشت Keccak-f[1600] درجا روی ۵۰ کلمه | 192 | keccak(state), keccak256(msg) | rand_keccak |
| 5 | SHA256 ptr | یک تابع فشردهسازی SHA-256 درجا روی ۲۴ کلمه | 64 | sha256_compress(buf), sha256(msg) | rand_sha256_compress |
| 6 | READ_PUBLIC idx | کلمهٔ idx از قطعهٔ عمومی را برمیگرداند که به H_PUB بدون نمک بسته شده است | 1 | read_public(idx) | rand_read_public |
| 7 | POSEIDON2_LEN ptr n | POSEIDON2 با طول 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 } تبدیل میکند:
- برای هر کلمهٔ دادهٔ غیرصفر یک جفت
li/swمینویسد. کلمههای صفر نادیده گرفته میشوند، زیرا RAM از پیش صفر میخواند. - آن مقدمه را مستقیماً زیر
text_baseقرار میدهد، پسbase_pc = text_base − 4 × prologue words. مقدمه به_startخود برنامه میرسد و از آن عبور میکند. hcو شناسهٔ برنامه روی کلwords، با احتساب مقدمه، محاسبه میشوند. بنابراینhcداده را مانند کد به خود میبندد.
ظرفی بدون داده مقدمهای ندارد و برنامه و hc آن با یک فایل دودویی تخت بدون سرآیند در همان مبنا یکسان است.
rand program deploy در fullnode نسخهٔ v0.3 و پس از آن، ظرف را از روی کلمهٔ نخست آن تشخیص میدهد. 0x444e4152 هرگز بهعنوان دستورالعمل رمزگشایی نمیشود، پس یک .bin خام را نمیتوان با ظرف اشتباه گرفت. پروندهای که با عدد جادویی آغاز شود اما ناقص باشد خطاست و به حالت جایگزین نمیرود.
دو هویت
| نام | چیست | چه کسی استفاده میکند |
|---|---|---|
hc | چکیدهٔ Poseidon2 مدار از (base_pc, len, words) | اعتبار هر اثبات یک فراخوانی در برابر آن سنجیده میشود؛ rand_getProgram آن را بهصورت code_hash نشان میدهد |
| program id | blake3("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_bytes | 4,194,304 (4 MiB)؛ 8 MiB در chainهای 13 تا 16 |
max_block_bytes | 20,971,520 (20 MiB) |
max_call_envelope_bytes | 65,536 |
max_program_public_words | 32,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، مفسر EVM | 18,009 | خیر | بله | خیر |
sbpf.bin، مفسر sBPF | 8,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 بودجه را گامبهگام بررسی میکند.