هر برنامهای که کد ماشین RV32IM باشد قابل استقرار است، فارغ از اینکه چه چیزی آن را تولید کرده باشد. این صفحه دو مسیر را نشان میدهد: C از طریق rand-guest build --lang c، و برنامهای که خودتان به زبان اسمبلی مینویسید و خودتان بستهبندی میکنید. هر دو به همان کانتینر تصویری ختم میشوند که استقرار یک برنامه Rust تولید میکند، و هر دو با rand program deploy مستقر میشوند. ابتدا چکاوت circuits و rand-guest را راهاندازی کنید، مانند گام ۱ در آن صفحه. برای زنجیرههای 18 تا 20 این چکاوت روی کامیت aeacf31 است که fullnode نسخههای v0.6.7 و v0.6.8 هر دو به آن پین شدهاند. خروجیهای زیر در 2026-09-19 روی 7ef3220 تولید شدهاند و دوباره اجرا نشدهاند. پوشه rand-guest، با guest.h و rt.c، در هر دو کامیت یکسان است.
C با clang
آنچه نیاز دارید
| ابزار | نسخه | روش |
|---|---|---|
| clang | 23.1.1، با بکاند RISC-V | brew install llvm در macOS؛ clang سیستمی Apple بکاند RISC-V ندارد. یا $CLANG را روی یک باینری 23.1.1 تنظیم کنید |
rust-lld | از Rust 1.98.1 | rustup +1.98.1 component add llvm-tools |
rand-guest هر clang را که نسخهاش 23.1.1 نباشد رد میکند. RAND_GUEST_CLANG_UNPINNED=1 با نسخهای دیگر build میکند، همراه با هشداری که میگوید hc با تصویرهای منتشرشده یکسان نخواهد بود.
نوشتن guest
پوشه guest به زبان C فقط فایلهای .c (و یک .ld اختیاری) دارد. rand-guest فایلهای guest.h، _start و یک runtime کوچک را فراهم میکند. guest.h یا start.S خودتان را اضافه نکنید: build --lang c پوشهای را که یکی از این دو را داشته باشد رد میکند.
این فایل guests-compiled/c-fib/fib.c است که کامیت شده است:
#include "guest.h"
static uint32_t fib(uint32_t n) {
uint32_t a = 0, b = 1;
for (uint32_t i = 0; i < n; i++) {
uint32_t c = a + b;
a = b;
b = c;
}
return a;
}
void main(void) {
rand_write_output(0, fib(rand_read_input(0)));
rand_halt();
}
guest.h توابع rand_read_input، rand_read_public، rand_write_output، rand_poseidon2، rand_keccak، rand_sha256_compress و rand_halt را اعلام میکند. برای فراخوانی سیستمی 7 هیچ wrapper ندارد، یعنی POSEIDON2_LEN با کران طول که ماشین از مجموعه محدودیت 7 به بعد اجرا میکند، و rand-guest check در aeacf31 هر ecall با a7 = 7 را رد میکند (zkp-circuits issue #1). بنابراین guest نوشتهشده به C یا اسمبلی برای زنجیره 20 از فراخوانیهای سیستمی 0 تا 6 استفاده میکند.
ساخت، اجرا، بررسی
$ rand-guest build --lang c guests-compiled/c-fib --out c-fib.bin
25 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote c-fib.bin and its .sha256 (25 words, hc 76e723cb4c218f9a7f657f39b9f95ee2353d8fbeb601aaea394afa29928f3aee, program id c213cf5c94679cab4585ed1e81b61736a7d2fe5605dc7884e4acec4144665999)
$ rand-guest run c-fib.bin --input 20 --tier 10
out[0] = 6765
out[1] = 0
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 116
tier 10
tier 10: fits (cycles 126 of 1023, Poseidon2 permutations 10 of 128)
fib(20) = 6765، همان پاسخ guest fib نوشتهشده به Rust.
build --lang c همه فایلهای .c پوشه را (به ترتیب مرتبشده)، بهعلاوه start.S و rt.c همراه ابزار، با این گزینهها کامپایل میکند:
clang --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax
-nostdlib -ffreestanding -fno-builtin -ffunction-sections -fdata-sections -Os
-ffile-prefix-map=<checkout>=/rand-circuits -c
و آنها را با rust-lld -flavor gnu --gc-sections -T <ld> از toolchain پینشده Rust پیوند میدهد. --gc-sections فقط آنچه را guest فراخوانی میکند نگه میدارد.
runtime زبان C
-ffreestanding -fno-builtin clang را از شناسایی توابع کتابخانهای بازمیدارد، اما از تولید فراخوانی به آنها بازنمیدارد. کپی یک struct به memcpy تبدیل میشود، مقداردهی اولیه صفر بزرگ به memset، و تقسیم 64 بیتی بر مقداری که در زمان اجرا معلوم میشود به فراخوانی یک تابع کمکی، زیرا RV32IM فقط مقادیر 32 بیتی را تقسیم میکند. rt.c این هشت تابع را تعریف میکند:
| تابع | کاربرد |
|---|---|
memcpy، memmove، memset، memcmp | کپی، پر کردن، مقایسه |
__udivdi3، __umoddi3، __divdi3، __moddi3 | / و % 64 بیتی |
اینها حلقههای بایتی و تقسیم شیفت-تفریقاند. کند هستند: در یک حلقه پرتکرار، کپی و تقسیم را بهصورت کلمههای 32 بیتی انجام دهید.
برنامهای که با دست نوشته میشود
میتوانید کد ماشین را خودتان هم بنویسید: آن را اسمبل کنید، با همان اسکریپت پیوندده (linker script) پیوند دهید و ELF را بستهبندی کنید. این برنامه دو ورودی خصوصی را جمع میکند و حاصلجمع را منتشر میکند.
add.S:
# Reads private inputs 0 and 1, writes their sum to output slot 0, halts.
.section .text._start
.globl _start
_start:
li a7, 2 # READ_INPUT
li a0, 0
ecall # a0 = input 0
mv t0, a0
li a7, 2 # READ_INPUT
li a0, 1
ecall # a0 = input 1
add t1, t0, a0
li a7, 1 # WRITE_OUTPUT slot, word
li a0, 0
mv a1, t1
ecall
li a7, 0 # HALT
ecall
شماره هر فراخوانی سیستمی را با li a7, n درست پیش از ecall آن تنظیم کنید. در این صورت checker میتواند آن شماره را تأیید کند.
با clang پینشده اسمبل کنید، با rust-lld و guest-sdk/guest.ld پیوند دهید، سپس بستهبندی کنید. از داخل چکاوت circuits اجرا کنید:
CLANG=/opt/homebrew/opt/llvm/bin/clang
LLD=$(rustc +1.98.1 --print sysroot)/lib/rustlib/$(rustc +1.98.1 -vV | sed -n 's/host: //p')/bin/rust-lld
$CLANG --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax -c add.S -o add.o
$LLD -flavor gnu -T guest-sdk/guest.ld add.o -o add.elf
$ rand-guest pack add.elf --out add.bin
wrote add.bin (80 bytes) and add.bin.sha256
$ rand-guest check add.bin
14 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
$ rand-guest run add.bin --input 40 --input 2
out[0] = 42
out[1] = 0
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 14
tier 10
$ rand-guest info add.bin
form: image container
text 14 words at 0x1000; data 0 words (0 non-zero) at 0x0; prologue 0 words; program 14 words from base_pc 0x1000
hc 42986ac1f93396d7de75f6757149523e7f1e9c5999d748af9b1915ada8ee14bd
program id 3b34c9d16adbd81a4849148675a914d10e5da0bd5b6aa64967e5f8a5fc75d941
14 words against a cap of 4096: fits
pack بخش .text فایل ELF را بهعنوان سگمنت متن و هر بخش PROGBITS تخصیصیافته دیگر را بهعنوان یک سگمنت داده برمیدارد. .bss کنار گذاشته میشود، زیرا RAM از صفر شروع میشود.
بستهبندی دستی کانتینر
کانتینر تصویر شش کلمه سرآیند است و سپس خود کلمهها (معماری مجموعه دستورهای RISC-V). میتوانید بدون هیچ toolchain ای یکی بنویسید. این اسکریپت Python همان 80 بایتی را مینویسد که pack در بالا نوشت:
import struct
text = [
0x00200893, # li a7, 2 READ_INPUT
0x00000513, # li a0, 0
0x00000073, # ecall a0 = input 0
0x00050293, # mv t0, a0
0x00200893, # li a7, 2 READ_INPUT
0x00100513, # li a0, 1
0x00000073, # ecall a0 = input 1
0x00a28333, # add t1, t0, a0
0x00100893, # li a7, 1 WRITE_OUTPUT
0x00000513, # li a0, 0 slot 0
0x00030593, # mv a1, t1 word
0x00000073, # ecall
0x00000893, # li a7, 0 HALT
0x00000073, # ecall
]
# magic ("RAND") version text_base n_text data_base n_data
header = [0x444E4152, 1, 0x1000, len(text), 0, 0]
with open("add-hand.bin", "wb") as f:
f.write(struct.pack(f"<{len(header) + len(text)}I", *header, *text))
rand-guest info add-hand.bin همان hc 42986ac1…14bd را چاپ میکند و cmp add-hand.bin add.bin هیچ تفاوتی نمییابد. همیشه روی تصویر بستهبندیشده با دست rand-guest check را اجرا کنید: node هر کلمهای را که رمزگشایی نشود رد میکند، اما فقط در زمان استقرار.
کیف پول دو شکل قدیمیتر را هم مستقر میکند: فایل .json به شکل { "base_pc", "words" }، و یک .bin خام از کلمههای little-endian که روی پایه 0 بارگذاری میشود. کانتینر تنها شکلی است که سگمنت داده دارد.
استقرار و فراخوانی
همه مسیرهای بالا یکسان به پایان میرسند. با کیف پول rand از fullnode نسخه v0.3 یا جدیدتر (در زنجیره 20، زنجیره زنده، نسخه v0.6.8 یا جدیدتر: کیف پول v0.6.7 نمیتواند در آن ارسال کند، زیرا زنجیره 20 هر اثبات و امضا را به genesis hash خود پیوند میدهد؛ استقرار یک برنامه Rust را ببینید):
rand program deploy add.bin
rand call <program id> --input 40 --input 2
کارمزد استقرار 0.001 RAND + 0.0001 RAND × words است: 0.0024 RAND برای 14 کلمه add.bin و 0.0035 RAND برای 25 کلمه c-fib.bin. هر دو کمتر از 4,096 کلمهاند، بنابراین در سقف هر زنجیرهای جا میشوند: پیشفرض 4,096 و سقف زنجیره 20 یعنی 65,535. فراخوانی بهصورت محلی در tier 10 اثبات میشود. در زنجیره 15 هزینه آن 0.002 RAND بود. در زنجیره 20 کارمزد دیگر 0.001 + call_fee(tier) نیست: کف آن BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉ است، rand call پیش از اثبات gas: <exact> (declaring <limit>, tier <t>) و کارمزد را چاپ میکند، و چهار فراخوانی که در زنجیره 18 اندازهگیری شد هرکدام 0.0057 تا 0.0075 RAND هزینه داشت (برنامههای نمونه). هر استقرار و فراخوانی در زنجیره 20 همچنین دو اثبات سمت bundle دارد: یک مجوز خرج در tier 10 و bundle در tier 14. استقرار یک برنامه Rust توضیح میدهد هر فرمان چه میکند.
برای اینکه دیگران بتوانند آنچه را مستقر کردهاید بررسی کنند، سورس و کامیت circuits خود را منتشر کنید (aeacf31 برای زنجیرههای 18 تا 20). با clang 23.1.1 و Rust 1.98.1، rand-guest build روی هر ماشینی همان hc را میدهد.