Rand Protocol
ドキュメント
RISC-V プログラム

RISC-V プログラムのデプロイ

Rust を使わずにデプロイ可能なイメージを得る 2 つの方法です。固定されたバージョンの clang を用いて rand-guest でビルドする C プログラムと、手書きでアセンブルしてパックするプログラム、またはイメージコンテナへバイト単位で直接パックする方法を扱います。

RV32IM のマシンコードであれば、何から生成されたものでもデプロイできます。このページでは 2 つの方法を示します。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 を指定すると別の clang でもビルドできますが、hc が公開済みのイメージと一致しないという警告が出ます。

ゲストを書く

C のゲストディレクトリに置くのは .c ファイル(と任意の .ld)だけです。guest.h、_start、小さなランタイムは rand-guest が提供します。独自の 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 は長さ境界付きの POSEIDON2_LEN で、マシンは制約セット 7 以降これを実行していますが、aeacf31 の rand-guest check は a7 = 7 の ecall を拒否します(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 がライブラリ関数を認識するのを止めますが、それらの呼び出しを生成するのは止めません。構造体のコピーは memcpy に、大きなゼロ初期化子は memset に、実行時の値による 64 ビット除算はヘルパー呼び出しになります。RV32IM の除算は 32 ビット値に限られるためです。rt.c は次の 8 つを定義しています。

関数用途
memcpy、memmove、memset、memcmpコピー、埋め込み、比較
__udivdi3、__umoddi3、__divdi3、__moddi364 ビットの / と %

これらはバイトループとシフト減算による除算で、低速です。ホットループでは、コピーと除算を 32 ビットワード単位で行ってください。

手書きのプログラム

マシンコードを自分で書くこともできます。アセンブルし、同じリンカスクリプトでリンクし、ELF をパックします。このプログラムは 2 つのプライベート入力を足し、その合計を公開します。

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 セクションをすべて 1 つのデータセグメントとして取り込みます。RAM はゼロから始まるため、.bss は含まれません。

コンテナを手でパックする

イメージコンテナは、6 つのヘッダーワードとそれに続くワード本体からなります(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 を実行してください。ノードはデコードできないワードをすべて拒否しますが、それはデプロイ時になってからです。

ウォレットは、さらに 2 つの古い形式もデプロイできます。{ "base_pc", "words" } という .json ファイルと、ベース 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 で測定した 4 件の呼び出しは、1 件あたり 0.0057 から 0.0075 RAND でした(プログラムの例)。チェーン 20 では、デプロイも呼び出しも、バンドル側の 2 つの証明、つまりティア 10 のスペンド認可とティア 14 のバンドルを伴います。各コマンドの動作は、Rust プログラムのデプロイで説明しています。

デプロイしたものを他の人が検証できるように、ソースと使用した circuits のコミット(チェーン 18 から 20 では aeacf31)を公開してください。clang 23.1.1 と Rust 1.98.1 を使えば、rand-guest build はどのマシンでも同じ hc を生成します。

fullnode のコミット c0878c3f(チェーン 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。 コードとこのページが食い違う場合は、コードが正です。お知らせください。