This page takes a program from source to a deployed RAND program. The toolchain is rand-guest,
in the circuits repo (github.com/dendisuhubdy/zkp-circuits, local
~/Github/randprotocol/circuits, main 7ef3220). Its full reference is rand-guest/README.md
there. The chain side is in confidential.md and cli.md. To deploy
Solidity or Solana programs, see translators.md.
How the outputs were made. Every output below was produced on 2026-09-19 with circuits
7ef3220 and fullnode 0cfd1e3, on a macOS laptop. The deploys and the call ran against a local
one-validator chain with the test FRI profile, not the fleet. On a production-profile chain the
commands and the printed lines are the same; the proof sizes and times differ.
| property | value |
|---|---|
| base ISA | RV32IM: RV32I plus the M extension (mul mulh mulhu mulhsu div divu rem remu) |
| encodings | 32-bit only. RVC (16-bit) is refused |
| loads and stores | word, halfword and byte; memory is word-addressed underneath |
| alignment | a misaligned access is a constraint violation. Rust guests build with -unaligned-scalar-mem |
| program size | at most 65 535 words at any tier; the chain's cap is lower (§8) |
| proof system | Plonky3 batch STARK over Goldilocks, Poseidon2 hashing |
| tiers | tier t (10, 12, …, 20) proves up to 2^t − 1 cycles, with a separate Poseidon2 budget. The proof reveals the tier, not the cycle count |
check runs every text word through the machine's own decoder. A guest that passes cannot fail
in-circuit for an encoding, syscall-number or layout reason.
| finding | refused |
|---|---|
Compressed |
a 16-bit (RVC) encoding |
Undecodable |
an opcode, funct or shift amount the machine does not implement |
Fence |
FENCE and FENCE.I: no memory-ordering instructions |
Csr |
any CSR instruction: the machine has no CSRs |
Ebreak |
EBREAK: traps are not modelled |
Syscall |
an ecall whose a7 is statically known and names no implemented syscall |
Cap |
more words than --max-words, counted as the loader counts them (text plus the data prologue) |
Layout |
a text base that is not word-aligned |
An ecall whose a7 is not statically known is counted (unresolved_ecalls, printed as "ecall(s)
with a non-static a7"), not refused. check does not check pointer arguments, stack overflow, or
run-time alignment; run reports those as traps.
This is what a refusal looks like. The input is a hand-built image with one fence added:
$ rand-guest check fence.elf
note: the loader refuses this image (Decode { index: 3, word: 267386895, err: Opcode(15) }), so its data prologue could not be counted; the word count below is the text alone
0x0000100c 0x0ff0000f Fence: FENCE/FENCE.I: the machine has no memory ordering instructions
10 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED
The exit status is 1.
The syscall number goes in a7, the first argument in a0, a second in a1. A returned word
comes back in a0. Pointer arguments are word addresses (the byte pointer divided by 4).
a7 |
syscall | arguments | C wrapper (guest.h) |
Rust (guest_sdk) |
|---|---|---|---|---|
| 0 | halt | — | rand_halt() |
halt() |
| 1 | write output | a0 slot 0–7, a1 word |
rand_write_output(slot, word) |
write_output(slot, word) |
| 2 | read private input | a0 index; returns the word |
rand_read_input(idx) |
read_input(idx) |
| 3 | Poseidon2 | a0 word ptr, a1 n ≤ 4096; digest overwrites ptr..ptr+8 |
rand_poseidon2(ptr, n) |
poseidon2(ptr, n) |
| 4 | Keccak-f[1600] | a0 word ptr to a 50-word state, permuted in place |
rand_keccak(ptr) |
keccak(ptr), keccak256(msg) |
| 5 | SHA-256 compression | a0 word ptr to 24 words: block at 0..16, state at 16..24 |
rand_sha256_compress(ptr) |
sha256_compress(ptr), sha256(msg) |
| 6 | read public input | a0 index; returns the word |
rand_read_public(idx) |
read_public(idx) |
| 7 | Poseidon2, length-bound (constraint set 7) | as syscall 3, with the message length in the sponge's capacity | — (no C wrapper in guest.h as of circuits aeacf31) |
poseidon2_len(buf, n) |
Use syscall 7 in new guests. Syscall 3 does not pad: [a] and [a, 0] hash alike (HCS-4). It
stays as it is because every commitment and program digest on the chain depends on it; in the
Rust SDK it is an unsafe fn and poseidon2_len is the safe call (docs/confidential.md,
"Privacy").
- Private inputs are bound to the salted commitment
H_IN. A read past the end cannot be proven. rand callseals the private inputs into the call envelope, so the envelope cap bounds them: at most(max_call_envelope_bytes − 1 252) / 4words, read fromrand_getLimits. That is 4 295 words on a chain without the field (18 432 bytes) and 16 071 on chain 13 (65 536 bytes). A node withoutrand_getLimitsgets the old 4 096.--no-envelopeseals nothing and applies no word cap.- Public inputs are bound to the unsalted
H_PUB. A program's public input is fixed at deploy (rand program deploy --public <FILE>, §8.1): every call proves over the same words, and a call carries none of its own. - The eight output words are published in the call's receipt. What they mean is up to the program.
Three chain rules limit which syscalls a deployed program can use. Each depends on the chain's genesis limits (§8.1):
| syscall | chain without the limit fields (chain 12) | chain 13 |
|---|---|---|
| 6, read public | every call is verified against the empty public segment, so a program that reads public words cannot be called | the program's public input is fixed at deploy (up to max_program_public_words, 32 768) and every call is verified against its digest |
| 4, Keccak | a proof that carries the keccak table measured 3 198 430 bytes at tier 10, production profile, above the 2 MiB proof cap: refused by size | the proof cap is 8 MiB (max_proof_bytes), so such a proof fits; bytes past 2 MiB + 18 432 cost 1 000 units per KiB |
| 5, SHA-256 | the sha256 table adds 400 563 bytes to a production proof (measured upstream); with a hash-free 1 298 729-byte tier-10 proof, about 1.70 MB, under 2 MiB. This is a sum of two measurements, not a measured proof | fits under 8 MiB |
rand-guest build and rand-guest pack write the image container. rand program deploy reads it.
| word | value |
|---|---|
| 0 | 0x444e4152 (IMAGE_MAGIC, the bytes RAND read little-endian) |
| 1 | 1 (the version) |
| 2 | text_base |
| 3 | n_text |
| 4 | data_base |
| 5 | n_data |
| then | n_text text words, then n_data data words |
- The loader (
Program::from_flat_image) synthesises a prologue ofli/swinstructions below the text. It writes the data segment into RAM before the guest's own code runs. So a guest with.rodataworks. - The deployed program is the prologue plus the text. Its
base_pcis the prologue's start. IMAGE_MAGICnever decodes as an RV32 instruction, so a raw.binof words is never mistaken for a container. A file that starts with the magic but is malformed is a deploy error.
| component | pinned version | how |
|---|---|---|
| Rust | 1.98.1 | the cargo +1.98.1 / rustup +1.98.1 commands below need it installed |
| RISC-V target | riscv32im-unknown-none-elf |
rustup +1.98.1 target add riscv32im-unknown-none-elf |
| linker | rust-lld from the pinned toolchain |
rustup +1.98.1 component add llvm-tools |
| clang (C guests and translators) | 23.1.1 | brew install llvm (Apple's clang has no RISC-V backend), or set $CLANG |
cc crate (translators only) |
1.4.6 | pinned in the generated Cargo.toml and Cargo.lock |
Build the tool once, from the circuits checkout:
$ (cd rand-guest && cargo +1.98.1 build --release)
The examples below call it as rand-guest, meaning rand-guest/target/release/rand-guest.
A guest directory must sit inside a circuits checkout. build finds guest-sdk/ above it:
$ rand-guest build --lang c /some/dir/outside/csum
Error: /some/dir/outside/csum is not inside a circuits checkout (no guest-sdk/ above it)
A guest is a no_std, no_main binary crate that depends on guest-sdk. This one sums a list of
private inputs. Put it at my-guests/sum/ in the circuits checkout.
my-guests/sum/Cargo.toml:
[package]
name = "sum-guest"
version = "0.1.0"
edition = "2021"
[dependencies]
guest-sdk = { path = "../../guest-sdk" }
[profile.release]
opt-level = 3
lto = true
panic = "abort"
codegen-units = 1
[workspace]my-guests/sum/src/main.rs:
#![no_std]
#![no_main]
use guest_sdk::{halt, read_input, write_output};
/// Input 0 is a count n; inputs 1..=n are the values.
/// Output 0 is their sum (mod 2^32); output 1 is n.
#[no_mangle]
pub extern "C" fn main() -> ! {
let n = read_input(0);
let mut sum = 0u32;
for i in 1..=n {
sum = sum.wrapping_add(read_input(i));
}
write_output(0, sum);
write_output(1, n);
halt();
}- The entry point is
main.guest-sdksupplies_start. - End with
halt(). It never returns. - The empty
[workspace]keeps the guest out of any parent workspace. - Do not add a
.cargo/config.tomlor any RUSTFLAGS.buildrefuses config files (§7).
build compiles, checks and packs in one step. It writes image.bin and image.bin.sha256 into
the guest directory unless --out says otherwise.
$ rand-guest build my-guests/sum
Locking 1 package to latest compatible version
Compiling guest-sdk v0.1.0 (…/guest-sdk)
warning: unstable feature specified for `-Ctarget-feature`: `unaligned-scalar-mem`
warning: `guest-sdk` (lib) generated 1 warning
Compiling sum-guest v0.1.0 (…/my-guests/sum)
warning: `sum-guest` (bin "sum-guest") generated 1 warning (1 duplicate)
Finished `release` profile [optimized] target(s) in 0.29s
31 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote my-guests/sum/image.bin and its .sha256 (31 words, hc a2af88deb4b21f4577acb4ac740e8644386de91a3d136b0ef8bcd2bb63df45b4, program id b2d5b50cbb2f2d686eccef01711be7f0eed9c335be20e01c9bbb29465fa428f4)
The unaligned-scalar-mem warning is expected. build sets that flag on purpose (§1).
build already ran the check. To re-check an image or an ELF:
$ rand-guest check my-guests/sum/image.bin
31 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
Pass --max-words N to check against a raised chain cap. The default is 4096.
run executes the image on the emulator. --input takes the private words in order. --tier
also reports whether the run fits that tier.
$ rand-guest run my-guests/sum/image.bin --input 3 100 200 300 --tier 10
out[0] = 600
out[1] = 3
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 39
tier 10
tier 10: fits (cycles 50 of 1023, Poseidon2 permutations 11 of 128)
tier 10 is the smallest tier the run fits. The fit line counts the digest rows too, so its cycle
figure is higher than cycles. A trap prints trap: <error> and exits 2.
The wallet deploys the image file directly.
$ rand program deploy my-guests/sum/image.bin
program id: b2d5b50cbb2f2d686eccef01711be7f0eed9c335be20e01c9bbb29465fa428f4 (31 words, hc de88afa2451fb2b4acb4ac7744860e741ae96d380e6b133dbbd2bcf8b445df63)
warning: chain uses the insecure test FRI profile
proving bundle (tier 14; about a minute on a laptop)…
proved in 97.0s: tier 14, 323108 bytes
submitted deploy 60d8b3962433672dc6af036096ff509105e3b86a8e7e4d9053c208d68c38fed6
0 RAND out, 999.9959 RAND change, fee 0.0041 RAND, anchored at height 8
What the wallet does, in order:
- Loads the container and prints the program id, the word count and
hc. - Asks the node
rand_estimateFeefor a deploy of that many words. The node applies the chain's cap. An over-cap program is refused here, before any proving. - Proves a zero-value bundle that pays the deploy fee (
0.001 RAND + 100 000 units per word;rand fee deploy <words>asks the node). - Submits and waits for the commit.
The hc the wallet prints is spelled differently from rand-guest's. §7.1 explains why.
This session was captured before chain 13. On a chain-13 node rand program show also prints
public_words_len (0) and public_digest (null), and the receipt carries "h_pub": null: the
call was checked against the empty public input (docs/rpc.md, rand_getReceipt).
$ rand program show b2d5b50cbb2f2d686eccef01711be7f0eed9c335be20e01c9bbb29465fa428f4
{
"base_pc": 4096,
"code_hash": "de88afa2451fb2b4acb4ac7744860e741ae96d380e6b133dbbd2bcf8b445df63",
"deployed_at": 108,
"id": "b2d5b50cbb2f2d686eccef01711be7f0eed9c335be20e01c9bbb29465fa428f4",
"words_len": 31
}
$ rand call b2d5b50cbb2f2d686eccef01711be7f0eed9c335be20e01c9bbb29465fa428f4 --input 3 --input 100 --input 200 --input 300
warning: chain uses the insecure test FRI profile
proving the call locally (4 inputs stay private)…
proved in 6.0s: tier 10, 296521 bytes, outputs [600, 3, 0, 0, 0, 0, 0, 0]
proving bundle (tier 14; about a minute on a laptop)…
proved in 97.0s: tier 14, 323493 bytes
submitted call aeb64e6887a1da21d2403771fb0aed373fa586448d5106364cb8fc69293f3491
0 RAND out, 999.9939 RAND change, fee 0.002 RAND, anchored at height 120
{
"h_in": "3bbbb6df49482846365c5fc188ac851f1a6bf17dfcc736706b6905e409be42c4",
"height": 221,
"index": 0,
"outputs": [
600,
3,
0,
0,
0,
0,
0,
0
],
"program": "b2d5b50cbb2f2d686eccef01711be7f0eed9c335be20e01c9bbb29465fa428f4",
"tier": 10,
"tx": "aeb64e6887a1da21d2403771fb0aed373fa586448d5106364cb8fc69293f3491"
}
input transcript published; open it with `rand open-call aeb64e6887a1da21d2403771fb0aed373fa586448d5106364cb8fc69293f3491`
The receipt's outputs equal rand-guest run's. The call proof and the bundle proof are both made
in the wallet. That run's peak memory footprint was 5.74 GB (/usr/bin/time -l, test profile).
Call envelopes, auditors and open-call are in confidential.md.
A C guest directory holds only .c files. build --lang c supplies guest.h, _start and a
small runtime (rt.c: memcpy, memmove, memset, memcmp and the 64-bit division helpers).
It refuses a directory that has its own guest.h or start.S.
my-guests/csum/sum.c:
/* Input 0 is a count n; inputs 1..=n are the values.
* Output 0 is their sum (mod 2^32); output 1 is n. */
#include "guest.h"
void main(void) {
uint32_t n = rand_read_input(0);
uint32_t sum = 0;
for (uint32_t i = 1; i <= n; i++) {
sum += rand_read_input(i);
}
rand_write_output(0, sum);
rand_write_output(1, n);
rand_halt();
}$ rand-guest build --lang c my-guests/csum
29 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote my-guests/csum/image.bin and its .sha256 (29 words, hc 0ce4fddb98b6e30b1f0e2e1894b690311a3eccb158dd772c8e6b69c4ac7e3e1c, program id 5cf0d2804c0b5b282d9ba191481bb6286d3b3c5738d6f9e712f0b3b5e6d5b7d5)
$ rand-guest check my-guests/csum/image.bin
29 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
$ rand-guest run my-guests/csum/image.bin --input 3 100 200 300 --tier 10
out[0] = 600
out[1] = 3
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 36
tier 10
tier 10: fits (cycles 47 of 1023, Poseidon2 permutations 11 of 128)
The C build compiles with clang --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax -nostdlib -ffreestanding -fno-builtin -ffunction-sections -fdata-sections -Os, then links with rust-lld --gc-sections against the same guest.ld as Rust
guests. The runtime helpers are byte loops. Copy and divide in words in a hot loop.
The same command as for Rust:
rand program deploy my-guests/csum/image.bin
The committed C example, guests-compiled/c-fib, builds to 25 words and gives fib(20) = 6765,
the same as the Rust fib guest.
Any toolchain can produce the ELF. rand-guest pack wraps it into the container and check
decides whether the machine accepts it. This example is assembly.
double.S reads private input 0 and writes twice its value to output 0:
# double.S: reads private input 0, writes 2*x to output slot 0, halts.
.section .text._start
.globl _start
_start:
li a0, 0 # input index 0
li a7, 2 # SYS_READ_INPUT
ecall # a0 = input[0]
slli a1, a0, 1 # a1 = 2 * input[0]
li a0, 0 # output slot 0
li a7, 1 # SYS_WRITE_OUTPUT
ecall
li a7, 0 # SYS_HALT
ecallAssemble with the pinned clang and link with the pinned rust-lld against guest-sdk/guest.ld
(text at 0x1000):
$ CLANG=/opt/homebrew/opt/llvm/bin/clang
$ LLD=$(rustc +1.98.1 --print sysroot)/lib/rustlib/aarch64-apple-darwin/bin/rust-lld
$ $CLANG --no-default-config --target=riscv32-unknown-none-elf -march=rv32im -mabi=ilp32 -mno-relax -c double.S -o double.o
$ $LLD -flavor gnu -T guest-sdk/guest.ld double.o -o double.elf
$ rand-guest pack double.elf --out double.bin
wrote double.bin (60 bytes) and double.bin.sha256
$ rand-guest check double.bin
9 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
$ rand-guest run double.bin --input 21
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 9
tier 10
$ rand-guest info double.bin
form: image container
text 9 words at 0x1000; data 0 words (0 non-zero) at 0x0; prologue 0 words; program 9 words from base_pc 0x1000
hc 9e6b00148a12aa70c41df1cbea018c3d0c18d739a6e182cd0c20ff8f1a5ab93c
program id e5fc40a4d0883e45d006faa3ac0334645668e2b3991db8311d187d0ccda07de2
9 words against a cap of 4096: fits
The LLD path names the host triple (aarch64-apple-darwin here). Then deploy it like any other
image: rand program deploy double.bin.
- A hand-built image is not reproducible from source by anyone else unless you publish the exact
source and tools. Its
hcis still the image's digest. - Keep
a7set by anliright before eachecall.checktracksa7only in straight-line code; a jump into the middle of a block can hide a bad syscall number from it. The machine still traps on it at run time.
Both are hashes of the same (base_pc, words).
| name | what it is | used for |
|---|---|---|
hc |
the circuit's Poseidon2 digest of the program (Program::digest(), eight words) |
a call proof is verified against it. Stored as code_hash |
| program id | blake3("rand-program" ‖ base_pc ‖ words) |
the key a program is stored and called under |
hc has two spellings. rand-guest prints each of the eight words as big-endian hex. The
wallet, rand program show and rand_getProgram print each word's little-endian bytes. The digest
is the same; the text differs. The ERC-20 image from translators.md:
| printed by | hc |
|---|---|
rand-guest build / info |
a0feae92a7311c9562495717100eb7aea71270a38ed435d06dc31387e0ea8ff6 |
rand program deploy, rand program show (code_hash) |
92aefea0951c31a717574962aeb70e10a37012a7d035d48e8713c36df68feae0 |
Each group of eight hex digits is byte-reversed: a0feae92 becomes 92aefea0. The program id is
spelled the same way by both tools.
| pin | value | refused otherwise? |
|---|---|---|
| Rust | 1.98.1 | build runs cargo +1.98.1 |
| clang | 23.1.1 | yes, unless RAND_GUEST_CLANG_UNPINNED=1; then hc will not match published images |
cc crate |
1.4.6 (translators) | pinned in the generated Cargo.lock |
hc must be a function of the source and the pinned toolchain alone. Otherwise nobody could
rebuild a published source to a deployed hc. build enforces this:
- The environment is scrubbed. cargo and clang never see
RUSTFLAGS,CARGO_ENCODED_RUSTFLAGS,CARGO_BUILD_RUSTFLAGS, the guest target'sCARGO_TARGET_*overrides, anyCARGO_PROFILE_*,CARGO_INCREMENTAL, aRUSTCor wrapper override,RUSTC_BOOTSTRAP, anyCARGO_UNSTABLE_*, the target-dir variables, or clang'sCCC_OVERRIDE_OPTIONS,CPATH,C_INCLUDE_PATHandCOMPILER_PATH. - Cargo config files are refused. A
.cargo/configor.cargo/config.tomlin the guest's directory, an ancestor, or$CARGO_HOMEwould be merged into the build.buildrefuses and names each file. - Paths are remapped.
--remap-path-prefix(Rust) and-ffile-prefix-map(C) map the checkout to/rand-circuits, so panic strings do not depend on where the checkout lives. - clang reads no config files (
--no-default-config). - The previous ELF is deleted first, so a stale one is never packed.
Evidence. The Rust fib guest was rebuilt for this page in a checkout at a different path. It
gave hc ed475c16f1a55122316cd1a2a37da4318c4983ac89127795c99dd4375e27b99e, the same as the
committed guests-compiled/bin/fib.bin. rand-guest/tests/build.rs gates the four committed
guests on every run: evm.bin and sbpf.bin byte for byte, fib and keccak256 by program
equality.
A Deploy may carry at most max_program_words words.
| genesis | cap |
|---|---|
no max_program_words field (chain 12) |
4096 |
max_program_words: N, 1 ≤ N ≤ 65535 |
N |
| any value | never above 65 535, the zkVM's own limit |
- The cap is part of the genesis hash. It cannot be raised on a running chain. A raised cap needs a new chain.
rand-node genesis --max-program-words Nwrites the field. Setting it to 4096 is a different chain from leaving it out.- Every node needs a v0.4 build before
rand-node initon such a file. An older build silently drops the field and computes a different genesis hash (cli.md). rand-guest buildandcheckmeasure against--max-words, default 4096. Pass the target chain's cap.
This is what an over-cap deploy looks like on a chain without the field. The wallet stops before proving:
$ rand program deploy erc20.bin
program id: f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d (11686 words, hc 92aefea0951c31a717574962aeb70e10a37012a7d035d48e8713c36df68feae0)
Error: words must be at most 4096 (this chain's program cap) (rpc -32602)
And the same image on a local chain cut with --max-program-words 65535:
$ rand-node genesis --chain-id 992 --validator "v.key.json,1000,$ADDR" --alloc "$ADDR=1000" --faucet --fri-profile test --max-program-words 65535 --out g-65535.json
...
wrote g-65535.json (genesis hash cbf56531bc09a07dd8c87614b123a4aa56ffd2aef9df1aeeb6d3651bcc7ef510, 1 validators, 1 notes, 1000 blocks/epoch, programs up to 65535 words, faucet on, confidential on, fri test, hc_bundle 4a27356f379571036025a4a8661c294b0edec2b7cf7fbfd60b472b186cbd4afb)
$ rand fee deploy 11686
1.1696 RAND
$ rand program deploy erc20.bin
program id: f074c4eb834cf01886a8241b6a2e0caf6e1cee5327fee6cb1a1a37436607280d (11686 words, hc 92aefea0951c31a717574962aeb70e10a37012a7d035d48e8713c36df68feae0)
warning: chain uses the insecure test FRI profile
proving bundle (tier 14; about a minute on a laptop)…
proved in 96.9s: tier 14, 325668 bytes
submitted deploy effe6f7c5f4c085c6de7c483dcd3dc84a4b0fb64e12fe070f13d6d9e5af6cb33
0 RAND out, 998.8304 RAND change, fee 1.1696 RAND, anchored at height 12
A wallet's note store is per key file, not per chain. Use a separate key file (or move
<key>.notes.json aside) when you switch between chains, or the wallet fails with no leaf at index N.
Four more genesis fields follow the same rules as max_program_words: optional, bound into the
genesis hash only when present, fixed for the life of the chain, and reported by
rand_getLimits.
| field | absent (chain 12) | bounds | chain 13 |
|---|---|---|---|
max_proof_bytes |
2 097 152 | 1 MiB ..= 32 MiB | 8 388 608 |
max_block_bytes |
4 194 304 | 4 MiB ..= 64 MiB, and ≥ 2 × max_proof_bytes + 1 MiB |
20 971 520 |
max_call_envelope_bytes |
18 432 | 18 432 ..= 1 MiB | 65 536 |
max_program_public_words |
0 (no public input) | 0 ..= 65 535 | 32 768 |
rand-node genesis writes each with its flag (--max-proof-bytes, --max-block-bytes,
--max-call-envelope-bytes, --max-program-public-words; cli.md).
A public input fixed at deploy. A program that reads public words (syscall 6) gets them from its deploy:
rand program deploy image.bin --public words.txt # whitespace-separated u32 words
rand program deploy image.bin --public program.so # an ELF, word-encoded as the sBPF guest reads it
- The program id then binds the public input:
blake3("rand-program-2", base_pc ‖ u32_le(len(words)) ‖ words ‖ u32_le(len(public)) ‖ public). Without--publicthe id is unchanged,blake3("rand-program", base_pc ‖ words), sorand-guest infoprints the no-public id. The iddeployprints is the one to call. - The deploy pays for its public words as code words (
0.001 RAND + 100 000 units per wordover both), and the node stores the words and their digestH_PUB. rand call <id>fetches the public input (rand_getProgramPublic), checks that code and public input hash to the id, and proves over them. The chain checks the proof'sH_PUBagainst the deploy's digest, so a proof over any other public words is refused (PublicValues). The receipt carriesh_pub.rand call <id> --expect-public <FILE>refuses before proving unless the program's public input is exactly that file's words.
Call fee over the free allowance. A call is priced as before up to 2 097 152 + 18 432 bytes of
call proof plus input envelope; every KiB (or part of one) past that adds 1 000 units (0.000001 RAND). rand fee call <tier> --bytes B asks the node.
| chain | 13, live since 2026-09-19 |
| genesis hash | 8123ccac1883a45750e4df6964fb7cd3f0b321798cde4c0ef406a0293939ece3 |
| genesis file | deploy/genesis-chain13.json |
max_program_words |
65 535 |
max_proof_bytes |
8 388 608 (8 MiB) |
max_block_bytes |
20 971 520 (20 MiB) |
max_call_envelope_bytes |
65 536 |
max_program_public_words |
32 768 |
| pinned build | fullnode 86af6eb (Linux rand-node sha256 cc20bf84…) |
Chain 13's call limits are the values in §8.1 (deploy/cut-chain13-genesis.sh). Images of up to
65 535 words, and --public inputs of up to 32 768 words, deploy on the testnet. The 11 686-word
translated ERC-20 and the 65 096-word translated SPL Token are both deployed there
(translators.md §4.6 and §5.4).