कोई भी प्रोग्राम जो RV32IM मशीन कोड है, डिप्लॉय किया जा सकता है, चाहे उसे किसी ने भी बनाया हो। यह पृष्ठ दो रास्ते दिखाता है: rand-guest build --lang c के माध्यम से C, और ऐसा प्रोग्राम जिसे आप असेंबली में लिखते हैं और स्वयं पैक करते हैं। दोनों का परिणाम वही इमेज कंटेनर होता है जो Rust प्रोग्राम डिप्लॉय करें बनाता है, और दोनों rand program deploy से डिप्लॉय होते हैं। पहले circuits checkout और rand-guest सेट अप करें, जैसा उस पृष्ठ के चरण 1 में बताया गया है। चेन 18 से 20 के लिए checkout कमिट aeacf31 पर है, जिसे fullnode v0.6.7 और v0.6.8 दोनों पिन करते हैं। नीचे दिए गए आउटपुट 2026-09-19 को 7ef3220 पर तैयार किए गए थे और उन्हें दोबारा नहीं चलाया गया है। rand-guest डायरेक्टरी, guest.h और rt.c सहित, दोनों कमिट पर एक जैसी है।
clang के माध्यम से C
आपको क्या चाहिए
| टूल | संस्करण | कैसे |
|---|---|---|
| clang | 23.1.1, RISC-V बैकएंड के साथ | macOS पर brew install llvm; Apple के सिस्टम clang में 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 घोषित हैं। इसमें syscall 7 के लिए कोई रैपर नहीं है, जो लंबाई-सीमित POSEIDON2_LEN है और जिसे मशीन constraint set 7 से चला रही है, और aeacf31 पर rand-guest check ऐसे ecall को अस्वीकार करता है जिसमें a7 = 7 हो (zkp-circuits issue #1)। इसलिए चेन 20 के लिए C या असेंबली गेस्ट syscall 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, वही उत्तर जो Rust fib गेस्ट देता है।
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 टूलचेन के rust-lld -flavor gnu --gc-sections -T <ld> से लिंक करता है। --gc-sections केवल वही रखता है जिसे गेस्ट कॉल करता है।
C रनटाइम
-ffreestanding -fno-builtin clang को लाइब्रेरी फ़ंक्शन पहचानने से रोकते हैं, पर उन्हें कॉल उत्सर्जित करने से नहीं। struct की प्रतिलिपि 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
हर syscall संख्या को उसके ecall से ठीक पहले li a7, n से सेट करें। तब चेकर संख्या का सत्यापन कर सकता है।
पिन किए गए clang से असेंबल करें, rust-lld और guest-sdk/guest.ld से लिंक करें, फिर पैक करें। circuits checkout के भीतर से चलाएँ:
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 ELF के .text को टेक्स्ट सेगमेंट के रूप में और हर अन्य आवंटित PROGBITS सेक्शन को एक डेटा सेगमेंट के रूप में लेता है। .bss छोड़ दिया जाता है, क्योंकि RAM शून्य से आरंभ होती है।
कंटेनर को हाथ से पैक करना
इमेज कंटेनर में छह हेडर शब्द होते हैं और फिर स्वयं शब्द (RISC-V ISA)। आप इसे बिना किसी टूलचेन के लिख सकते हैं। यह 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 चलाएँ: नोड ऐसे किसी भी शब्द को अस्वीकार करता है जो डिकोड नहीं होता, पर केवल डिप्लॉय के समय।
वॉलेट दो पुराने रूप भी डिप्लॉय करता है: एक .json फ़ाइल { "base_pc", "words" }, और little-endian शब्दों की एक कच्ची .bin जो बेस 0 पर लोड होती है। कंटेनर ही एकमात्र रूप है जिसमें डेटा सेगमेंट होता है।
डिप्लॉय और कॉल
ऊपर का हर रास्ता एक ही तरह समाप्त होता है। fullnode v0.3 या बाद के rand वॉलेट के साथ (चेन 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 है: add.bin के 14 शब्दों के लिए 0.0024 RAND और c-fib.bin के 25 के लिए 0.0035 RAND। दोनों 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 पर हर डिप्लॉय और कॉल के साथ बंडल-पक्ष के दो प्रूफ़ भी होते हैं, एक tier-10 व्यय प्राधिकरण और tier-14 बंडल। Rust प्रोग्राम डिप्लॉय करें बताता है कि प्रत्येक कमांड क्या करता है।
दूसरों को यह जाँचने देने के लिए कि आपने क्या डिप्लॉय किया, स्रोत और अपना circuits कमिट (चेन 18 से 20 के लिए aeacf31) प्रकाशित करें। clang 23.1.1 और Rust 1.98.1 के साथ, rand-guest build किसी भी मशीन पर वही hc देता है।