Rand Protocol
문서
RISC-V 프로그램

RISC-V 프로그램 배포

Rust 없이 배포 가능한 이미지를 만드는 두 가지 방법입니다. 고정된 clang으로 rand-guest가 빌드하는 C 프로그램, 그리고 직접 작성하여 어셈블하고 패킹한 프로그램 또는 이미지 컨테이너에 바이트 단위로 직접 패킹한 프로그램입니다.

RV32IM 머신 코드라면 무엇으로 만들었든 배포할 수 있습니다. 이 페이지는 두 가지 경로를 보여줍니다. rand-guest build --lang c를 통한 C, 그리고 어셈블리로 직접 작성하여 직접 패키징하는 프로그램입니다. 두 경로 모두 Rust 프로그램 배포에서 만들어지는 것과 동일한 이미지 컨테이너로 끝나며, 둘 다 rand program deploy로 배포합니다. 먼저 해당 페이지의 1단계와 같이 circuits 체크아웃과 rand-guest를 설정하십시오. 체인 18~20에서 체크아웃은 커밋 aeacf31이며, fullnode v0.6.7과 v0.6.8 모두 이 커밋을 고정하여 사용합니다. 아래 출력은 2026-09-19에 7ef3220에서 생성되었으며 다시 실행하지 않았습니다. guest.h와 rt.c를 포함한 rand-guest 디렉터리는 두 커밋에서 동일합니다.

clang을 통한 C

필요한 것

도구버전방법
clang23.1.1, RISC-V 백엔드 포함macOS에서는 brew install llvm. Apple의 시스템 clang에는 RISC-V 백엔드가 없습니다. 또는 $CLANG을 23.1.1 바이너리로 설정하십시오
rust-lldRust 1.98.1 제공rustup +1.98.1 component add llvm-tools

rand-guest는 버전이 23.1.1이 아닌 clang을 거부합니다. 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에 대한 래퍼는 없습니다. syscall 7은 제약 집합 7부터 머신이 실행해 온 길이 한정 POSEIDON2_LEN이며, aeacf31의 rand-guest check는 a7 = 7인 ecall을 거부합니다(zkp-circuits 이슈 #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이 라이브러리 함수를 인식하지 못하게 할 뿐, 해당 함수에 대한 호출을 생성하는 것까지 막지는 않습니다. 구조체 복사는 memcpy가 되고, 큰 0 초기화는 memset이 되며, 실행 시점 값에 의한 64비트 나눗셈은 헬퍼 호출이 됩니다. RV32IM은 32비트 값만 나눌 수 있기 때문입니다. rt.c는 다음 여덟 가지를 정의합니다.

함수용도
memcpy, memmove, memset, memcmp복사, 채우기, 비교
__udivdi3, __umoddi3, __divdi3, __moddi364비트 / 및 %

이들은 바이트 단위 루프와 시프트-감산 나눗셈입니다. 느리므로, 반복이 많은 루프에서는 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 체크아웃 내부에서 실행하십시오.

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 섹션을 하나의 데이터 세그먼트로 사용합니다. RAM은 0에서 시작하므로 .bss는 제외됩니다.

컨테이너 직접 패키징

이미지 컨테이너는 여섯 개의 헤더 워드와 그 뒤에 이어지는 워드들로 구성됩니다(RISC-V ISA). 툴체인 없이도 직접 작성할 수 있습니다. 다음 Python 스크립트는 위에서 pack이 기록한 것과 동일한 80바이트를 기록합니다.

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를 실행하십시오. 노드는 디코딩되지 않는 워드를 거부하지만, 그 시점은 배포 시점뿐입니다.

지갑은 이전 형식 두 가지도 배포할 수 있습니다. { "base_pc", "words" } 형태의 .json 파일과, 리틀 엔디언 워드로 이루어져 base 0에 로드되는 원시 .bin입니다. 데이터 세그먼트를 가질 수 있는 형식은 컨테이너뿐입니다.

배포 및 호출

위의 모든 경로는 같은 방식으로 끝납니다. fullnode v0.3 이상의 rand 지갑을 사용하는 경우(현재 운영 중인 체인 20에서는 v0.6.8 이상이어야 합니다. 체인 20은 모든 증명과 서명을 제네시스 해시에 바인딩하므로 v0.6.7 지갑은 전송할 수 없습니다. 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)에 들어갑니다. 호출은 로컬에서 티어 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 커밋(체인 18~20에서는 aeacf31)을 공개하십시오. clang 23.1.1과 Rust 1.98.1을 사용하면 rand-guest build는 어떤 머신에서든 동일한 hc를 만들어 냅니다.

fullnode 커밋 c0878c3f(chain 21) 기준으로 2026-10-12에 마지막으로 검증되었습니다, 그리고 circuits `7ef3220` (`rand-guest/README.md`, `guests-compiled/c-fib`, `guest-sdk/guest.ld`); every `rand-guest` output on this page was produced on 2026-09-19, circuits `aeacf31` (`rand-guest/guest.h`, `rand-guest/src/check.rs`; `git diff 7ef3220 aeacf31 -- guest-sdk rand-guest`), read on 2026-09-30; no output on this page was re-run at that commit, fullnode tag v0.6.8 `c9c9bd3c` (`docs/deploy.md` on `binding_domain`, `.github/workflows/ci.yml` circuits pin) and `rand_getLimits` on chain 20, read on 2026-10-01; nothing on this page was deployed or called on chain 20, fullnode tag v0.8.0 `c0878c3f` (`CHANGELOG.md`, `docs/zkvm.md` §8a) and `rand_getLimits` on chain 21, read on 2026-10-12; nothing on this page was deployed or called on chain 21. 코드와 이 페이지의 내용이 다를 경우 코드가 옳습니다. 알려 주시기 바랍니다.