Rand Protocol
문서
RISC-V 프로그램

RISC-V ISA

Rand에서 RV32IM이 의미하는 바를 다룹니다. zkVM이 증명하는 명령어, 검사기가 거부하는 항목, 여덟 가지 syscall, 메모리, 프로그램이 담겨 배포되는 이미지 컨테이너, 그리고 프로그램 크기 상한을 설명합니다.

이 페이지는 Rand용 프로그램을 작성하기 전에 읽어야 합니다. 코드가 어떤 머신에서 실행되는지, 툴체인이 무엇을 거부하는지, 프로그램이 외부와 어떻게 통신하는지, 어떤 파일을 배포하는지 설명합니다. 다음 페이지들이 이 내용을 이어받습니다. Deploy a Rust program, Deploy a RISC-V program, 그리고 두 개의 트랜스파일러가 그것입니다.

머신

Rand 프로그램은 RV32IM입니다. 32비트 RISC-V 기본 정수 명령어 집합에 곱셈 및 나눗셈 확장을 더한 것입니다. 프로그램은 Rand zkVM 안에서 실행되며, zkVM은 각 실행을 Plonky3 batch STARK로 증명합니다. verifier는 명령어를 직접 보지 않습니다. 프로그램의 회로 내 Poseidon2 다이제스트인 hc에 대해 증명을 검사합니다.

groupinstructions
upper / jumpsLUI AUIPC JAL JALR
branchesBEQ BNE BLT BGE BLTU BGEU
loads / storesLW SW LB LH LBU LHU SB SH
ALUADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND
RV32MMUL MULH MULHU MULHSU DIV DIVU REM REMU
systemECALL only

JALR은 RV32I의 규정대로 목표 주소의 0번 비트를 지우며, 그 인코딩은 funct3 = 0이어야 합니다. 두 규칙은 모두 constraint set 7(chain 16)과 함께 도입되었습니다. 그 이전에는 홀수 목표 주소를 fetch할 수 없어 실행을 증명할 수 없었습니다. 짝수이지만 4의 배수가 아닌 목표 주소는 여전히 fetch할 수 없습니다.

부동소수점, 원자적 연산(A 확장), 압축 명령어(C 확장), 64비트 레지스터는 없습니다. C에서의 64비트 나눗셈은 소프트웨어 헬퍼 호출로 바뀝니다. the C path를 참조하십시오.

거부되는 항목

rand-guest check는 모든 텍스트 워드를 머신 자체의 디코더에 통과시킵니다. 이를 통과한 프로그램은 인코딩, syscall 번호 또는 레이아웃 문제로 회로에서 실패할 수 없습니다. 검사기는 다음을 거부합니다.

findingwhat it is
Compressed16비트 RVC 인코딩. 머신은 32비트 워드만 디코딩합니다
Undecodable머신이 구현하지 않은 opcode, funct 또는 시프트 양
FenceFENCE 또는 FENCE.I. 머신에는 메모리 순서 지정이 없습니다
Csr모든 CSR 명령어. 머신에는 CSR이 없습니다
EbreakEBREAK. 트랩은 모델링되어 있지 않습니다
Syscall바로 앞의 li로 a7이 설정되었으나 어떤 syscall도 가리키지 않는 ecall
Cap로더가 세는 방식으로 센 워드 수가 --max-words를 초과함
Layout워드 단위로 정렬되지 않은 텍스트 베이스

fence, ebreak, 그리고 9번 ecall을 포함한 네 워드짜리 프로그램에 대해 rand-guest check가 보고하는 결과입니다(종료 코드 1).

$ rand-guest check bad.elf
note: the loader refuses this image (Decode { index: 0, word: 267386895, err: Opcode(15) }), so its data prologue could not be counted; the word count below is the text alone
0x00001000  0x0ff0000f  Fence: FENCE/FENCE.I: the machine has no memory ordering instructions
0x00001004  0x00100073  Ebreak: EBREAK: traps are not modelled
0x0000100c  0x00000073  Syscall: ecall with a7 = 9, which is no syscall the machine implements
4 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED

검사기가 a7을 볼 수 없는 ecall(li 이외의 방식으로 설정되었거나 분기 이후에 설정된 경우)은 거부되지 않고 unresolved_ecalls로 집계됩니다. 잘못된 번호에 대해서는 머신이 실행 시점에 여전히 트랩합니다.

검사기는 포인터 값, 길이, 입력 인덱스, RAM에 대한 64 KiB 스택, 실행 시점에 계산되는 주소의 정렬은 검사하지 않습니다. 정렬되지 않은 접근은 제약 위반이므로, 툴체인은 -unaligned-scalar-mem으로 컴파일합니다. rand-guest run은 실행 시점의 모든 트랩을 보고합니다.

Syscalls

ECALL은 a7에서 syscall 번호를, a0에서 첫 번째 인자를, a1에서 두 번째 인자를 읽습니다. 값은 a0으로 반환됩니다.

#syscalleffectgasRust (guest-sdk)C (guest.h)
0HALT실행을 종료합니다1halt()rand_halt()
1WRITE_OUTPUT slot word공개 출력 slot < 8을 설정하며, 슬롯당 최대 한 번만 가능합니다. 쓰지 않은 슬롯은 0입니다1write_output(slot, word)rand_write_output
2READ_INPUT idx솔트가 적용된 커밋먼트 H_IN에 바인딩된 비공개 입력 워드 idx를 반환합니다1read_input(idx)rand_read_input
3POSEIDON2 ptr n워드 주소 ptr의 n ≤ 4096개 워드를 해시하고 8워드 다이제스트를 ptr..ptr+8에 덮어씁니다. 패딩 없음3 + 3·⌈n/4⌉unsafe poseidon2(ptr, n)rand_poseidon2
4KECCAK ptr50개 워드에 대해 Keccak-f[1600] 순열을 제자리에서 한 번 수행합니다192keccak(state), keccak256(msg)rand_keccak
5SHA256 ptr24개 워드에 대해 SHA-256 압축을 제자리에서 한 번 수행합니다64sha256_compress(buf), sha256(msg)rand_sha256_compress
6READ_PUBLIC idx솔트가 적용되지 않은 H_PUB에 바인딩된 공개 세그먼트 워드 idx를 반환합니다1read_public(idx)rand_read_public
7POSEIDON2_LEN ptr n길이 n을 스펀지에 바인딩한 POSEIDON2이며, 순열을 최소 한 번 수행합니다(constraint set 7)3 + 3·max(⌈n/4⌉, 1)poseidon2_len(buf, n)none

그 밖의 모든 명령어는 가스 1을 소모합니다. 가스는 cpu 행당 1이며, 해시 syscall만 더 무겁습니다(constraint set 8, chain 18부터). Poseidon2 호출은 ecall 행 하나, 쓰기 반영 행 두 개, 그리고 행당 가스 3인 ⌈n/4⌉개의 absorb 행으로 이루어집니다.

POSEIDON2는 패딩하지 않습니다. 스펀지는 네 워드 청크마다 앞쪽 레인을 덮어쓰므로 [a], [a, 0], [a, 0, 0], [a, 0, 0, 0]은 하나의 다이제스트로 해시됩니다. 길이가 달라질 수 있는 데이터를 해시하는 프로그램은 메시지에 길이를 넣거나, 고정 길이 인코딩을 해시하거나, POSEIDON2_LEN을 호출해야 합니다. POSEIDON2_LEN은 n을 스펀지의 capacity에 시드로 넣어 동일한 워드에 대해 다른 다이제스트를 만듭니다. Rust SDK에서 poseidon2는 원시 포인터를 받는 unsafe fn이고, poseidon2_len은 슬라이스를 받는 안전한 호출입니다. keccak과 sha256_compress는 각각 &mut [u32; 50]과 &mut [u32; 24]를 받습니다.

툴체인의 한 가지 공백. circuits aeacf31에서 머신, 에뮬레이터, Rust SDK는 syscall 7을 구현하지만 rand-guest check는 구현하지 않습니다. 이 검사기의 syscall 목록은 6에서 끝나므로 a7 = 7인 ecall을 Syscall finding으로 보고하고, poseidon2_len을 호출하는 guest에 대해 rand-guest build는 0이 아닌 값으로 종료합니다(circuits issue #1). guest.h에도 이를 위한 래퍼가 없습니다. 이 문제가 수정될 때까지, 기본 도구로 빌드하는 guest는 고정 길이 메시지에 POSEIDON2를 사용합니다.

증명이 공개하는 것은 정확히 다음과 같습니다. hc, H_IN, H_PUB, 티어, 선언된 가스 한도, 그리고 여덟 개의 출력 워드입니다. 현재 온체인에는 두 가지 제한이 적용됩니다.

  • 공개 세그먼트는 배포 시점에 고정됩니다. chain 20에서 rand program deploy --public은 프로그램의 공개 워드(최대 max_program_public_words, 32,768개)를 설정하며, 모든 호출은 이에 대해 검사됩니다. 프로그램 자체의 호출별 공개 워드는 없습니다. chain 20은 chain 19와 마찬가지로 모든 호출의 세그먼트 뒤에 여덟 개의 워드를 덧붙입니다. 이는 증명을 해당 트랜잭션에 묶는 호출 바인딩이며(chain 20에서는 genesis 해시도 포함합니다), guest는 자신의 워드를 이전과 같은 인덱스에서 읽습니다. RPL-2 Invoke는 바인딩 뒤에 선언된 전이를 덧붙이며, 프로그램은 이를 public_len + 8부터 읽습니다(Programs). chain 12처럼 제한 필드가 없는 체인에서는 공개 세그먼트가 비어 있어 READ_PUBLIC이 읽을 것이 없습니다.
  • 호출의 비공개 입력 워드는 봉인된 envelope에 의해 제한됩니다. chain 20의 max_call_envelope_bytes 65,536은 16,071개 워드를 허용하며, 이 필드가 없는 체인은 4,295개를 허용합니다.

KECCAK 또는 SHA256 호출은 증명에 테이블을 추가하며, 그러면 증명의 헤더에 해당 호출이 해시를 수행했음이 나타납니다. keccak을 포함하는 증명은 약 3.3 MB입니다. 기본 2 MiB 증명 상한을 넘고 chain 20의 4 MiB 상한에는 못 미치며, 한 호출은 최대 128회의 Keccak-f 순열과 128회의 SHA-256 압축을 사용할 수 있습니다(Programs).

메모리

  • 레지스터는 32개이며 x0는 0입니다.
  • RAM은 0에서 시작합니다. 모든 주소는 2³⁰ 미만입니다.
  • 기본 링커 스크립트 guest-sdk/guest.ld는 프로그램을 0x1000에 두고, 그 뒤에 .rodata, .data, .bss를, 그다음 __stack_top에 64 KiB 스택을 둡니다.
  • 데이터 세그먼트가 있는 프로그램은 텍스트 아래에 로더의 프롤로그를 위한 공간이 필요합니다(다음 절). 이 프로그램 자체의 .ld는 evm.ld와 sbpf.ld처럼 ORIGIN을 0x10000으로 올립니다.

이미지 컨테이너

rand-guest build와 rand-guest pack은 이미지 컨테이너라는 하나의 파일 형식을 작성합니다. 모든 숫자는 리틀 엔디언 32비트 워드입니다.

[0x444e4152 ("RAND"), 1, text_base, n_text, data_base, n_data] text… data…

로더는 컨테이너를 프로그램 { base_pc, words }으로 변환합니다.

  1. 0이 아닌 모든 데이터 워드에 대해 li/sw 쌍을 작성합니다. RAM은 이미 0으로 읽히므로 0인 워드는 건너뜁니다.
  2. 이 프롤로그를 text_base 바로 아래에 배치하므로 base_pc = text_base − 4 × prologue words입니다. 프롤로그는 프로그램 자체의 _start로 이어집니다.
  3. hc와 프로그램 id는 프롤로그를 포함한 전체 words에 대해 계산됩니다. 따라서 hc는 코드뿐 아니라 데이터에도 바인딩됩니다.

데이터가 없는 컨테이너는 프롤로그가 없으며, 같은 베이스의 헤더 없는 평면 바이너리와 동일한 프로그램 및 hc를 만듭니다.

fullnode v0.3 이후의 rand program deploy는 첫 번째 워드로 컨테이너를 인식합니다. 0x444e4152는 명령어로 디코딩되지 않으므로, 원시 .bin이 컨테이너로 오인될 수 없습니다. 매직으로 시작하지만 형식이 잘못된 파일은 폴백이 아니라 오류입니다.

두 가지 identity

이름의미사용처
hc(base_pc, len, words)에 대한 회로의 Poseidon2 다이제스트호출의 모든 증명이 이 값에 대해 검증됩니다. rand_getProgram은 이를 code_hash로 표시합니다
program idblake3("rand-program" ‖ base_pc ‖ words). --public을 사용한 배포는 공개 워드까지 바인딩하며, 이때 태그는 rand-program-2입니다배포된 프로그램이 저장되고 호출될 때 사용되는 키

rand-guest build와 rand-guest info는 둘 다 출력합니다. hc는 바인딩(binding) 속성은 있으나 은닉(hiding) 속성은 없습니다. 후보 프로그램을 가진 사람은 누구나 이를 확인할 수 있습니다.

프로그램 상한

배포에는 체인의 프로그램 상한(워드 단위)까지만 담을 수 있습니다.

체인상한
max_program_words가 없는 제네시스 (chain 12)4,096
max_program_words가 있는 제네시스해당 값, 1..=65535
zkVM의 하드 한계, 모든 체인65,535

상한은 제네시스 해시의 일부이므로 운영 중인 체인에서는 변경할 수 없습니다. rand-guest는 --max-words(기본값 4,096)를 기준으로 검사합니다. rand program deploy는 rand_estimateFee로 노드에 문의하고, 상한을 초과하는 프로그램은 증명을 시작하기 전에 거부합니다.

현재 운영 중인 체인인 chain 20은 상한을 높였습니다. 제네시스 해시는 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135이고 max_program_words는 65,535입니다. chain 13(v0.4 체인)이 도입한 한계를 그대로 유지하되, chain 17부터 20까지 절반으로 줄인 증명 상한만 다릅니다. 나머지 한계는 다음과 같습니다.

한계chain 20
max_proof_bytes4,194,304 (4 MiB). chain 13~16은 8 MiB
max_block_bytes20,971,520 (20 MiB)
max_call_envelope_bytes65,536
max_program_public_words32,768

chain 19와 마찬가지로 chain 20에서도 워드 상한은 실질적인 제약이 아닙니다. chain 20의 제네시스는 hardening_v6를 설정하며, 이에 따라 어떤 호출도 해당 프로그램을 증명할 수 없는 경우 배포는 거부됩니다(ProgramUncallable). 호출은 tier 14로 제한되고, 이 tier의 Poseidon2 테이블은 2,048개의 순열을 담습니다. 프로그램 다이제스트는 네 워드마다 하나, 입력의 salt 행은 하나, 호출 바인딩 워드 여덟 개는 둘을 사용하므로 2,045개가 남습니다. 이는 공개 입력이 없는 프로그램의 경우 8,180 워드이고, 공개 입력이 있으면 더 적습니다(공개 워드 아홉 개와 함께면 8,168). 모든 노드의 풀은 어떤 체인에서든 이러한 배포를 거부합니다.

chain 20에서는 블록 집계가 꺼져 있습니다. 제네시스에 aggregation 섹션이 없기 때문입니다. chain 20은 2026-10-01 06:17 UTC부터 fullnode v0.6.8로 운영 중이며, 높이 7,258의 chain 19 상태에서 분기한 하드 포크입니다(chain 19는 높이 7,700에서 멈췄습니다). chain 19(같은 날 03:43 UTC부터 운영)는 그 자체가 높이 141,935에서 chain 18을 다시 제네시스한 체인이었습니다. 프로그램은 체인 전환 시 이월되지 않으므로, 이전 체인에 배포된 프로그램은 chain 20에 존재하지 않습니다. 또한 chain 20은 RPL-2 프로그램 상태를 갖춘 최초의 체인입니다(프로그램). chain 20은 다음 부트스트랩 노드를 통해 네트워크에서 실행됩니다.

/ip4/164.90.239.200/tcp/30303/p2p/12D3KooWEQEbUwZRgqhDPXhW7vNBmcRcnhUDGUADe81VFdv3ALYe
/ip4/165.245.173.74/tcp/30303/p2p/12D3KooWJyc5oDHggAr89e9QrRFBy9Tg8TxXh18yzrb1SM6KKLYw
/ip4/188.166.235.187/tcp/30303/p2p/12D3KooWSyX84P1DAbWByLHpRBMLRWD53r3Jj4aqVt6tHoF8B2ZQ
이미지워드4,096에 적합65,535에 적합chain 20에 배포 가능 (8,180 호출 가능)
fib (Rust)26예예예
c-fib (C)25예예예
ERC-20, 변환본 (2단계)11,686아니요예아니요
evm.bin, EVM 인터프리터18,009아니요예아니요
sbpf.bin, sBPF 인터프리터8,317아니요예아니요
SPL Token, 변환본65,096아니요예, 439 여유아니요

Tier

증명의 tier t는 실행을 2ᵗ − 1개의 행으로 제한합니다. 행은 사이클과 다이제스트 행(프로그램 워드 네 개마다 하나, 입력 워드 네 개마다 하나, 그리고 공개 입력 다이제스트)으로 구성됩니다. tier는 별도의 Poseidon2 예산도 정합니다. rand-guest run --tier t는 두 가지를 모두 보고합니다.

tier 10: fits (cycles 146 of 1023, Poseidon2 permutations 10 of 128)

tier는 공개되며, 증명이 선언하는 가스 한도도 마찬가지입니다. chain 20에서 호출 수수료는 tier가 아니라 이 한도와 증명의 크기를 따릅니다. 수수료는 체인의 현재 가격 기준으로 최소 0.001 RAND + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉이며, 가격은 10⁻⁹ RAND 단위로 각각 100과 800에서 시작해 블록 포화도에 따라 움직이고, chain 20에서는 10,000과 80,000 단위에서 멈춥니다(수수료). 한도는 헤더의 상한인 (2ᵗ − 1) + 2ᵗ⁻²에 증명이 선언하는 Keccak 블록당 191, SHA-256 블록당 63을 더한 값을 넘을 수 없습니다. 해시 테이블이 없을 때 tier 10에서는 1,279, tier 14에서는 20,479입니다. rand-guest run은 가스가 아니라 사이클과 순열을 보고합니다. rand call은 먼저 프로그램을 실행하여 소비되는 가스와 선언할 한도를 출력한 뒤 증명합니다. 한도는 기본적으로 정확한 가스를 2ᵗ⁻²의 다음 배수로 올림한 값입니다.

gas: 5636 (declaring 6144, tier 12)

이 출력은 2026-09-30에 chain 18에서 수행한 호출의 것입니다(익명 투표). 제네시스에 gas 섹션이 없는 체인, 즉 chain 15 이하에서는 수수료가 tier 일정표였습니다. 즉 0.001 + (0.001 + 0.0001 × (t − 10) / 2) RAND였습니다. 증명에 필요한 메모리는 tier가 오를수록 가파르게 증가합니다. 검증자 및 aggregator 하드웨어를 참조하십시오.

fullnode v0.5.6(2026-09-25)부터 호출은 최대 tier 14까지만 선언할 수 있습니다. 노드는 더 높은 tier의 호출 증명을 verifier 키를 만들기 전에 CallTierTooHigh로 거부하며, 이를 담은 블록은 유효하지 않습니다. tier 14의 예산은 16,383개의 행과 2,048개의 Poseidon2 순열이고, 프로그램 자체의 다이제스트가 네 워드마다 약 하나의 순열을 소비하므로 8,180 워드를 넘는 프로그램은 호출할 수 없으며, chain 20은 chain 19와 마찬가지로 이러한 프로그램의 배포를 거부합니다. Rust 또는 C로 프로그램 작성하기에서 이 예산을 단계별로 설명합니다.

fullnode 커밋 c0878c3f(chain 21) 기준으로 2026-10-12에 마지막으로 검증되었습니다, 그리고 circuits `aeacf31` (`rand-guest/README.md`, `rand-guest/src/check.rs`, `research/docs/01-isa.md`, `guest-sdk/`), fullnode tag v0.6.8 `c9c9bd3c` (`docs/program-state.md`, `ledger/program_state.rs`, `deploy/cut-chain20-genesis.sh`), chain 20's genesis and `rand_getLimits`, read on 2026-10-01, fullnode tag v0.8.0 `c0878c3f` (`docs/zkvm.md` §8a, constraint set 9), `deploy/genesis-chain21.json` and `rand_getLimits` on chain 21, read on 2026-10-12. 코드와 이 페이지의 내용이 다를 경우 코드가 옳습니다. 알려 주시기 바랍니다.