يمكن نشر أي برنامج هو شيفرة آلة RV32IM، مهما كانت الأداة التي أنتجته. تعرض هذه الصفحة مسارين: لغة C عبر rand-guest build --lang c، وبرنامج تكتبه بلغة التجميع وتحزمه بنفسك. ينتهي المساران بحاوية الصورة نفسها التي ينتجها نشر برنامج Rust، ويُنشر كلاهما بالأمر rand program deploy. أعدّ نسخة مستودع circuits والأداة rand-guest أولاً، كما في الخطوة 1 من تلك الصفحة. في السلاسل من 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 البناء بإصدار آخر، مع تحذير بأن hc لن يطابق الصور المنشورة.
كتابة برنامج الضيف
يحتوي مجلد ضيف C على ملفات .c فقط (وملف .ld اختياري). توفّر rand-guest الملف guest.h و_start ووقت تشغيل صغيراً. لا تضف ملف 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، وهو POSEIDON2_LEN المقيَّد بالطول الذي تشغّله الآلة منذ مجموعة القيود 7، كما أن rand-guest check عند aeacf31 يرفض أي ecall تكون فيه a7 = 7 (المسألة رقم 1 في zkp-circuits). لذلك يستخدم ضيف 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، وهي الإجابة نفسها التي يعطيها ضيف 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> من سلسلة أدوات Rust المثبّتة. ويُبقي --gc-sections على ما يستدعيه الضيف فقط.
وقت تشغيل C
يمنع -ffreestanding -fno-builtin المصرّف clang من التعرف على دوال المكتبة، لكنه لا يمنعه من توليد استدعاءات إليها. فنسخ البنية يصبح memcpy، والمهيّئ الصفري الكبير يصبح memset، وقسمة 64 بت على قيمة تُعرف وقت التشغيل تصبح استدعاء دالة مساعدة، لأن RV32IM تقسم قيماً بعرض 32 بت فقط. يعرّف rt.c هذه الدوال الثماني:
| الدالة | الغرض |
|---|---|
memcpy وmemmove وmemset وmemcmp | النسخ والملء والمقارنة |
__udivdi3 و__umoddi3 و__divdi3 و__moddi3 | / و% بعرض 64 بت |
وهي حلقات على البايتات وقسمة بالإزاحة والطرح. وهي بطيئة: في الحلقة الساخنة انسخ واقسم بكلمات من 32 بت.
برنامج مكتوب يدوياً
يمكنك أيضاً كتابة شيفرة الآلة بنفسك: جمّعها، واربطها بنص الربط نفسه، ثم احزم ملف 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 الخاصة به. عندها يستطيع المدقّق التحقق من الرقم.
جمّع باستخدام 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 لأن الذاكرة تبدأ من الصفر.
حزم الحاوية يدوياً
حاوية الصورة هي ست كلمات رأسية ثم الكلمات نفسها (مجموعة تعليمات RISC-V). يمكنك كتابة واحدة دون أي سلسلة أدوات. يكتب هذا النص البرمجي بلغة Python البايتات الثمانين نفسها التي كتبها 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 دائماً على أي صورة محزومة يدوياً: فالعقدة ترفض أي كلمة لا يمكن فك ترميزها، لكن ذلك لا يحدث إلا وقت النشر.
وتنشر المحفظة أيضاً شكلين أقدم: ملف .json بالصيغة { "base_pc", "words" }، وملف .bin خام من كلمات little-endian يُحمَّل عند العنوان الأساسي 0. والحاوية هي الشكل الوحيد الذي يتضمن مقطع بيانات.
النشر والاستدعاء
ينتهي كل مسار أعلاه بالطريقة نفسها. مع محفظة rand من fullnode v0.3 أو أحدث (وعلى السلسلة 20، وهي السلسلة الحية، الإصدار v0.6.8 أو أحدث: فمحفظة v0.6.7 لا تستطيع الإرسال هناك، لأن السلسلة 20 تربط كل إثبات وتوقيع بتجزئة التكوين الأولي genesis الخاصة بها؛ انظر نشر برنامج Rust):
rand program deploy add.bin
rand call <program id> --input 40 --input 2
رسم النشر هو 0.001 RAND + 0.0001 RAND × words: أي 0.0024 RAND للكلمات الأربع عشرة في add.bin، و0.0035 RAND للكلمات الخمس والعشرين في c-fib.bin. وكلاهما دون 4,096 كلمة، فيناسبان الحد الأقصى في أي سلسلة: الافتراضي 4,096 وحد السلسلة 20 البالغ 65,535. ويُثبَت الاستدعاء محلياً في الطبقة 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 أيضاً إثباتَين من جهة الحزمة، هما تفويض إنفاق من الطبقة 10 وحزمة الطبقة 14. ويصف نشر برنامج Rust ما يفعله كل أمر.
ولكي يتمكن الآخرون من التحقق مما نشرته، انشر الشيفرة المصدرية وإيداع circuits الذي استخدمته (aeacf31 للسلاسل من 18 إلى 20). ومع clang 23.1.1 وRust 1.98.1 يعطي rand-guest build قيمة hc نفسها على أي جهاز.