이 페이지는 프로그램을 온체인에 올리고 호출하는 방법을 다룹니다. 프로그램이란 무엇인지, 어떻게 빌드하는지, 호출이 무엇을 공개하고 무엇을 숨기는지, 영수증을 어떻게 다시 읽는지, 그리고 마주치게 될 한계를 설명합니다. 모든 프로그램은 공개 코드이며, 모든 호출은 증명입니다. 예제 프로그램에는 완성된 애플리케이션이 있고, randprotocol/example-programs에는 RPL-2 프로그램을 포함해 chain 20에 배포된 프로그램의 소스가 있습니다(아래 참조).
프로그램이란 무엇인가
프로그램은 Rand zkVM용 RV32IM 기계어 코드입니다. 형식은 { base_pc: u32, words: Vec<u32> }이며, 워드 수는 최대 체인의 프로그램 상한까지입니다(chain 13 이후 모든 체인과 마찬가지로 chain 20에서는 65,535 워드이고, chain 12처럼 max_program_words가 없는 제네시스에서는 4,096 워드입니다). base_pc는 4의 배수여야 하고, 모든 워드는 명령어로 디코딩되어야 합니다(디코딩되지 않는 워드는 실행 시점이 아니라 배포 시점에 거부됩니다). chain 20은 chain 19와 마찬가지로 어떤 호출도 담을 수 없는 프로그램의 배포도 거부합니다. 공개 세그먼트가 없으면 8,180 워드를 초과하는 프로그램이, 공개 세그먼트가 있으면 그보다 적은 워드의 프로그램이 이에 해당합니다(아래 참조). 프로그램은 내용 주소 방식이며 불변입니다.
program_id = blake3("rand-program" || base_pc || words) // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public) // deployed with --public
code_hash = hc = Poseidon2 digest of (base_pc, len, words) // what calls are verified against
호출은 다이제스트가 hc인 프로그램이 어떤 비공개 입력에 대해 실행되었고, 선언된 가스 한도를 넘지 않았으며, 8개의 출력 워드를 공개했음을 보이는 STARK 증명입니다. 노드는 증명을 검증하고 영수증을 기록할 뿐, 그 밖의 일은 일어나지 않습니다. 출력은 데이터이지 효과가 아닙니다. 호출은 RAND를 옮길 수 없습니다. 값의 이동은 호출 비용을 지불하는 번들에서 호출자가 수행하며, 프로그램의 출력이 무엇을 뜻하는지는 프로그램과 호출자가 합의한 바에 따릅니다. chain 20의 Invoke는 예외입니다. 그 증명은 선언된 상태 전이를 보증하고, 원장이 그 전이를 적용합니다(프로그램 상태 (RPL-2)).
게스트가 할 수 있는 일:
| 그룹 | 명령어 |
|---|---|
| 상위 / 점프 | LUI AUIPC JAL JALR |
| 분기 | BEQ BNE BLT BGE BLTU BGEU |
| 로드 / 스토어 | LW SW LB LH LBU LHU SB SH |
| ALU | ADDI SLTI SLTIU XORI ORI ANDI SLLI SRLI SRAI ADD SUB SLL SLT SLTU XOR SRL SRA OR AND |
| RV32M | MUL MULH MULHU MULHSU DIV DIVU REM REMU |
| 시스템 | ECALL. FENCE, CSR, EBREAK, A 확장과 C 확장은 지원하지 않습니다 |
시스콜은 a7에 담긴 번호로 구분합니다.
| # | 시스콜 | 효과 |
|---|---|---|
| 0 | HALT | 실행을 종료합니다 |
| 1 | WRITE_OUTPUT slot word | 공개 출력 slot < 8을 고정합니다. 각 슬롯은 최대 한 번만 쓸 수 있고, 쓰지 않은 슬롯은 0입니다 |
| 2 | READ_INPUT idx | 솔트가 적용된 커밋먼트 H_IN에 바인딩된 비공개 입력 워드 idx를 반환합니다 |
| 3 | POSEIDON2 ptr n | Poseidon2 스펀지로 n ≤ 4096 워드를 제자리에서 해시합니다 |
| 4 | KECCAK ptr | Keccak-f[1600] 순열을 제자리에서 한 번 수행하며, 증명에 keccak 테이블을 포함시킵니다 |
| 5 | SHA256 ptr | SHA-256 압축을 제자리에서 한 번 수행하며, 증명에 sha256 테이블을 포함시킵니다 |
| 6 | READ_PUBLIC idx | 공개 세그먼트의 워드 idx를 반환합니다. chain 20에서 공개 세그먼트는 배포 시점에 고정되며(rand program deploy --public), max_program_public_words가 없는 제네시스에서는 비어 있어 아무것도 읽지 못합니다 |
| 7 | POSEIDON2_LEN ptr n | 메시지 길이를 스펀지에 바인딩한 POSEIDON2로, [a]와 [a, 0]의 해시가 서로 다릅니다(제약 집합 7, chain 16 이후). circuits aeacf31의 rand-guest check는 여전히 이를 거부합니다(다음 절) |
메모리: x0이 0인 32개의 레지스터, 0으로 시작하는 RAM, 2³⁰ 미만의 모든 주소, 컴파일된 게스트를 위한 64 KiB 스택. rand-guest로 빌드한 프로그램은 읽기 전용 데이터와 초기화된 데이터를 이미지 컨테이너에 담고, 프로그램의 일부인 로더 프롤로그가 프로그램 자체 코드가 실행되기 전에 그 데이터를 씁니다(RISC-V ISA). 노트 커밋먼트, 널리파이어, Merkle 검사는 시스콜이 아니라 POSEIDON2 위에 구현된 라이브러리 루틴입니다.
chain 18(제약 집합 8)부터 모든 실행에 가스가 부과됩니다. 일반 명령어와 시스콜 0, 1, 2, 6은 각각 1 가스이고, n 워드에 대한 POSEIDON2는 3 + 3 × ⌈n / 4⌉, KECCAK 순열 한 번은 192, SHA256 압축 한 번은 64입니다. 표와 호출이 선언하는 내용은 예제 프로그램에 있습니다.
빌드하기
세 가지 방법이 있으며, 모두 RV32IM 워드를 생성합니다.
내장 어셈블러. 지갑에는 손으로 어셈블한 게스트 다섯 개가 들어 있으며, 이를 { "base_pc", "words" } JSON 파일로 어셈블합니다.
rand program build --guest private_payment --arg 1000 --out pp.json # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable
rand-guest로 컴파일하는 프로그램. circuits 저장소의 rand-guest는 no_std Rust 크레이트(riscv32im-unknown-none-elf, Rust 1.98.1) 또는 C 디렉터리(clang 23.1.1)를 이미지 컨테이너로 빌드하고, 모든 워드를 머신의 디코더로 검사한 뒤 hc와 프로그램 id를 출력합니다. 빌드는 밀폐되어 있으므로 같은 소스는 어느 머신에서든 같은 hc를 만듭니다. chain 18부터 20까지는 circuits 커밋 aeacf31(브랜치 feat/v067-circuits)에서 툴체인을 빌드하십시오. fullnode v0.6.7과 v0.6.8이 모두 이 커밋을 고정해 사용합니다. 이 커밋의 rand-guest check는 시스콜 0부터 6까지만 나열하므로, guest_sdk::poseidon2_len(시스콜 7)을 호출하는 게스트는 머신에서는 실행되더라도 빌드 단계에서 거부됩니다(zkp-circuits issue #1). 단계별 안내는 Rust 프로그램 배포와 RISC-V 프로그램 배포를 참고하십시오.
변환된 프로그램. sbpf2rv는 Solana sBPF ELF를, evm2rv는 EVM 런타임 바이트코드를 프로그램으로 변환하며, 그 출력은 Rand의 인터프리터 게스트의 출력과 워드 단위로 일치합니다. 자세한 내용은 Solana 트랜스파일러와 Ethereum 트랜스파일러를 참고하십시오.
지갑 자체에는 컴파일러가 없습니다. rand program deploy는 .json, 리틀 엔디언 워드로 이루어진 원시 .bin, 또는 rand-guest가 쓰는 이미지 컨테이너를 받습니다(fullnode v0.3 이상).
배포하기
rand program deploy pp.json # a bundle pays the floor; waits for the commit; prints the id
rand program show <id> # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest
testnet은 chain 20이며, 여기에 배포, 호출 또는 인보크하려면 fullnode v0.6.8 이상의 rand 지갑이 필요합니다. 이전 버전의 지갑으로는 전송 자체가 불가능합니다. chain 20의 제네시스는 binding_domain: 1을 설정하므로 모든 증명 바인딩과 서명된 메시지에 제네시스 해시가 포함되는데, v0.6.7 지갑의 바인딩에는 체인 id만 들어 있기 때문입니다. 공개 엔드포인트 https://rpc.randprotocol.org는 지갑의 동기화, 배포, 호출을 처리합니다(설정).
지갑은 먼저 프로그램 id, 워드 수, hc를 출력하고, rand_estimateFee로 노드에 수수료를 묻습니다. 이 메서드는 증명을 시작하기 전에 체인의 상한을 넘는 프로그램을 거부합니다. 그런 다음 가치가 0인 자기 전송 번들(모든 트랜잭션이 담는, 4-in-4-out 숨김 자산 번들 하나이며 수수료는 이 번들의 RAND 슬롯에서 나갑니다)을 구성해 자신의 노트로 0.001 RAND + 100,000 units × words를 지불하게 하고(공개 세그먼트 워드도 코드 워드로 계산됩니다), Action::Deploy { base_pc, words, public }를 첨부한 뒤, 트랜잭션의 바인딩에 대해 번들을 증명하고 제출합니다. chain 17부터는 증명이 두 개이며, 지갑은 각각에 대해 한 줄씩 출력합니다. 하나는 tier-10 지출 승인(수 초, 약 1.37 MB)이고, 다른 하나는 tier-14 번들(노트북에서 약 2분, 약 1.5 MB)입니다. chain 17 이전에는 번들 증명만 있었고, chain 15에서 약 100초에 약 1.42 MB였습니다. 배포 수수료에는 가스 항목도 바이트 항목도 없으며, 가스는 호출에만 부과됩니다. 노드는 모든 워드가 디코딩되는지 확인하고, hc를 계산하고, 레코드를 저장하고, 백그라운드에서 verifier 키 생성을 시작합니다. 코드는 공개되지만, 누가 비용을 지불했는지는 공개되지 않습니다. rand_getProgram에는 deployer 필드가 없습니다. 같은 워드를 두 번 배포해도 결과는 같습니다.
→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
"public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }
호출하기
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>
지갑은 노드에서 코드와 공개 세그먼트를 가져와 이것들의 해시가 id와 일치하는지 확인합니다. 에뮬레이터에서 호출을 실행하고, 증명을 시작하기 전에 가스와 수수료를 출력합니다.
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
그런 다음 입력 트랜스크립트를 봉인하고(아래 참조), 체인의 FRI 프로파일에 따라 입력을 비공개 벡터로 사용해 호출을 로컬에서 증명하고, 수수료를 지불하는 번들을 증명하고, 제출한 뒤 대기하고, 영수증을 출력합니다. chain 20에서 호출 증명은 자체 트랜잭션에 바인딩되고(제네시스 해시를 포함하는 바인딩), 번들은 증명 두 개로 구성되므로 호출 하나에 증명이 세 개입니다. 호출, tier-10 지출 승인, tier-14 번들입니다. 위의 두 줄은 2026-09-30에 chain 18에서 실행한 익명 투표의 호출에서 가져온 것입니다(익명 투표).
수수료. chain 20에서 최저 수수료는 BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉입니다. 여기서 BUNDLE_BASE는 0.001 RAND이고, bytes는 호출 증명과 입력 envelope의 크기이며, 가격은 각각 100과 800 단위(1 단위는 10⁻⁹ RAND)에서 시작하여 블록의 가득 찬 정도에 따라 움직입니다. chain 20은 두 가격의 상한을 10,000과 80,000 단위로 두며, byte price를 부담하는 bytes(호출의 증명과 envelope)만 부하에 산입합니다(byte_load: "paying"). chain 18과 19에서는 가격에 상한이 없었고 모든 트랜잭션의 bytes가 byte price를 움직였습니다. chain 15의 규칙이었던 원장의 고정 0.001 RAND + call_fee(tier) 체계는 chain 20에 적용되지 않습니다. 지갑은 증명이 만들어지기 전에 수수료를 확정하므로, 증명의 크기를 체인의 상한인 4 MiB로 가정하여 가격을 산정하고 가격 두 단계만큼의 여유를 더합니다. chain 18에서 측정한 호출 네 건의 비용은 각각 0.0057~0.0075 RAND였습니다(계산 과정). rand fee call <tier> --gas <limit> --bytes <B>는 노드에 최저 수수료를 직접 묻습니다. private_payment는 비공개 잔액 네 개를 읽고, 그 합이 임계값 이상이면 초과분을 공개합니다. 출력은 [1, 0, 25, 0, 0, 0, 0, 0]입니다. 체인은 증명, 티어, 그리고 이 여덟 개의 워드를 보지만 잔액은 전혀 보지 못합니다.
플래그: --tier T는 티어를 강제로 지정합니다. --gas-limit <N|max>는 기본값 대신 정확한 gas limit 또는 해당 티어의 상한을 선언하며, 기본값은 정확한 gas를 2^(tier − 2)의 배수로 올림한 값입니다. --fee <RAND>는 수수료를 지정합니다. --expect-public <FILE>은 프로그램의 공개 세그먼트가 해당 파일의 워드와 같지 않으면 증명하기 전에 거부합니다. --auditor <rand1…>는 지정한 감사자를 위해서도 트랜스크립트를 봉인합니다. --print-call-key는 호출별 키를 출력합니다. --no-envelope는 트랜스크립트를 게시하지 않습니다. --cuda는 GPU 백엔드를 선택합니다(실제 하드웨어에서는 검증되지 않았으며, envelope 봉인과는 함께 쓸 수 없습니다). chain 20은 호출에 대한 --cuda를 아예 거부합니다. 트랜잭션에 결합된 호출 증명은 CPU 백엔드에서 생성되기 때문입니다.
영수증
영수증은 커밋된 호출마다 저장되며, 노드가 체인을 동기화하거나 검증할 때 다시 계산됩니다.
→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
"outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }
h_in은 비공개 입력에 대한 증명의 솔트 커밋먼트이며, 그것만으로는 아무것도 드러내지 않습니다. h_pub은 프로그램이 배포될 때 정해진 공개 세그먼트의 다이제스트, 즉 프로그램의 public_digest이며, 공개 세그먼트 없이 배포된 프로그램에서는 null입니다. effect 필드는 없으며, 선언된 gas limit도 영수증에 들어 있지 않습니다. 탐색기는 같은 객체를 call의 GET /transactions/:hash에서 receipt로 보여 주고, 프로그램의 호출을 GET /programs/:id에 나열하며, GET /transactions?kind=call&program=<id>로 필터링합니다.
입력 envelope: 호출을 나중에 공개하기
기본적으로 rand call은 호출의 (salt, inputs) 트랜스크립트를 call-input envelope에 봉인하여 게시합니다. 이렇게 하면 두 번째 증명 없이도 나중에 입력을 보일 수 있습니다.
| part | bytes | what it is |
|---|---|---|
body | 12 + 16 + 4·n + 16 | K_call이라는 호출별 새 키로 암호화한 salt ‖ inputs |
to_sender | 60 | 호출자의 outgoing viewing key로 감싼 K_call |
kem_ct | 1,088 or 0 | 감사자에게 보내는 ML-KEM-768 캡슐화이며, 감사자가 없으면 비어 있음 |
to_auditor | 60 or 0 | 해당 캡슐화의 비밀값으로 감싼 K_call |
본문의 연관 데이터(associated data)는 H_IN이므로, 다른 호출로 옮겨 붙인 envelope는 누구에게도 열리지 않습니다. 체인은 크기만 확인하며(최대 max_call_envelope_bytes: chain 20에서는 65,536 bytes, 해당 필드가 없는 genesis에서는 18,432 bytes), rand_getCallEnvelope(tx)로 그대로 제공합니다. 세 가지 키만이 envelope를 열 수 있으며 그 외에는 열 수 없습니다. 호출자의 viewing key(해당 지갑이 수행한 모든 호출), 호출별 키(그 한 건의 호출), 감사자의 viewing key(그 한 건의 호출)입니다.
rand open-call <txhash> # as the caller
rand open-call <txhash> --as-auditor # as the auditor
rand open-call <txhash> --call-key <hex> # with the per-call key alone
open-call은 트랜스크립트를 열고 input_digest(salt, inputs) == H_IN을 확인한 뒤, 복원한 입력으로 에뮬레이터에서 프로그램을 다시 실행하여 그 출력을 영수증의 출력과 비교합니다. 둘 중 하나라도 실패하면 0이 아닌 코드로 종료하므로, 스크립트가 경고를 승인으로 오인할 일이 없습니다. --no-envelope는 되돌릴 수 없습니다. 솔트가 사라지면 호출자를 포함하여 누구도 그 호출을 열 수 없습니다.
v0.6.8부터 재실행은 증명이 만들어진 공개 세그먼트를 대상으로 이루어집니다. 즉 프로그램이 배포될 때 정해진 공개 워드와, hardening_v6에서는 그 뒤에 오는 여덟 워드의 호출 바인딩입니다(fullnode issue #116). v0.6.7의 open-call은 공개 세그먼트 없이 배포된 프로그램에서만 동작했습니다. 공개 세그먼트가 있는 프로그램에서는 입력을 출력한 뒤 re-running the program on these inputs failed: PublicIndex(0)와 함께 멈추고, 두 검사를 하나도 실행하지 않은 채 0이 아닌 코드로 종료했습니다.
프로그램 상태 (RPL-2)
chain 20은 RPL-2를 갖춘 첫 번째 체인이며, genesis의 program_state 섹션으로 활성화됩니다. 프로그램은 이전에 없던 세 가지를 얻습니다(fullnode docs/program-state.md, v0.6.8의 crates/randprotocol-core/src/ledger/program_state.rs).
| 셀 | 프로그램별로 8워드 키를 8워드 값에 대응시키는 맵입니다. 존재하지 않는 셀은 0 여덟 개로 읽히며, 0 여덟 개를 쓰면 셀이 제거됩니다. 상태 루트에 커밋됩니다 |
| 볼트 | 자산별 공개 잔액입니다(0은 RAND). 가치는 invoke의 번들을 통해 들어오고, 체인이 계산하는 노트로 나갑니다 |
| 프로그램 고유의 토큰 | rand token create --program <id>로 등록한 토큰(발행 권한 Program(id), 초기 공급량 없음)은 해당 프로그램의 invoke로만 발행되고 소각됩니다 |
셀과 볼트 잔액은 토큰의 공급량처럼 공개됩니다. 그 안에 노트는 하나도 없습니다.
Invoke(Action 33)는 **전이(transition)**를 함께 선언하는 호출이며, 그 호출 증명은 프로그램이 바로 그 전이를 수락함을 보여 줍니다.
Invoke { program, proof, input_envelope, transition }
Transition { reads: [{ key, value }] // cells read, with the values read; keys strictly ascending
writes: [{ key, value }] // cells written; keys strictly ascending
inflow: none | deposit | burn
pays: [{ asset, amount, recipient, r, envelope }] // notes out of the vault
mints: [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token
들어오는 것은 번들이 소각하는 것입니다. burn_r은 볼트에 예치되는 RAND이고, burn_asset의 burn_a는 예치되거나(inflow deposit) 파기되는(inflow burn, 프로그램 자체의 토큰만 해당) 토큰입니다. 나가는 것은 지급(payout)마다 체인이 계산하는 노트 하나이며, 지급이 먼저, 발행이 그다음이고, 금액과 수령인의 pk는 발행의 경우처럼 공개됩니다. 원장은 다음 조건이 모두 충족될 때에만 전이를 적용합니다. 프로그램이 존재하고, 형식이 올바르며, reads의 모든 셀이 선언된 값을 여전히 유지하고, 볼트가(이 트랜잭션 자체의 예치 이후에) 모든 지급을 충당하며, 모든 발행이 프로그램 자체의 토큰이고, 증명이 검증됩니다. 더 이상 일치하지 않는 읽기는 StaleRead로 거부되며, 지갑은 다시 읽고 다시 증명합니다.
invoke가 call과 다른 점.
Call (RPL-1) | Invoke (RPL-2) | |
|---|---|---|
| 증명이 만들어지는 공개 세그먼트 | public ‖ call_binding | public ‖ call_binding ‖ context, 워드로 표현한 전이 |
| 성공 시 체인이 하는 일 | 영수증을 저장하며, 출력은 데이터임 | 영수증을 저장하고 전이를 적용함: 셀을 쓰고, 볼트를 움직이고, 지급 노트를 추가함 |
| 가치 | 번들 자체의 전송뿐 | 번들의 소각을 통해 볼트에 예치하며, 지급과 발행은 볼트에서 나감 |
| 상태 의존 여부 | 없음 | 증명은 의존하지 않지만(세그먼트는 트랜잭션으로부터 만들어짐), 읽기 검사는 승인 시점과 풀에서 의존함 |
| 수수료 | call 최저 수수료 | call 최저 수수료에 생성된 셀마다 cell_fee를 더한 값 |
프로그램은 READ_PUBLIC(public_len + 8 + i)로 컨텍스트 워드 i를 읽습니다. 워드 0은 버전(1)이고, 워드 1~4는 읽기, 쓰기, 지급, 발행의 개수이며, 그다음이 burn_r, inflow 종류, burn_asset, burn_a입니다. 이어서 각 읽기와 쓰기가 키와 값(각 8워드)으로, 그다음 각 지급이 자산과 금액으로 놓입니다. 수령인은 컨텍스트에 없습니다. 프로그램은 금액을 정하고, 바인딩이 누가 지급받는지를 고정합니다. 프로그램은 자신에게 제시된 모든 컨텍스트 워드를 제약해야 하며, 패닉이 발생하는 경로가 없어야 합니다. guest-sdk의 패닉 핸들러는 실행을 중단시키고, 중단된 실행도 증명 가능한 실행이기 때문입니다.
chain 20의 한도(rand_getLimits.program_state):
| limit | chain 20 |
|---|---|
cell_fee | 셀 하나를 생성할 때마다 10,000,000 단위, 즉 0.01 RAND(저장된 값이 없던 셀에 0이 아닌 값을 쓰는 경우). 셀을 다시 쓰거나 삭제하는 것은 무료 |
max_reads | 셀 8개 |
max_writes | 셀 8개 |
max_payouts | 노트 4개, 지급과 발행을 합산 |
세그먼트 규칙이 대개 더 엄격합니다. 세그먼트 전체가 같은 프로그램에 대한 call이 증명되는 공개 테이블에 들어가야 하며, 공개 입력이 없는 프로그램에서는 그 크기가 127워드입니다. 따라서 컨텍스트 워드는 119개가 남으며, 예를 들어 읽기 세 개, 쓰기 세 개, 지급 네 개가 가능합니다.
명령과 RPC(지갑 v0.6.8):
rand program build --guest rpl2_counter --out counter.json # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>] # rand_getProgramCell / rand_getProgramCells
rand program vault <id> # rand_getProgramVault
program invoke는 증명하기 전에 모든 reads 항목을 실제 셀과 대조하며, 증명 전이든 포함 시점이든 오래된 읽기가 발견되면 3으로 종료하므로 스크립트는 다시 읽고 재시도할 수 있습니다. "kind": "invoke"를 지정한 rand_estimateFee는 수수료를 견적합니다. 전이 파일의 형식은 fullnode의 docs/cli.md에 있습니다. chain 20에 배포된 완전한 RPL-2 프로그램(amm, stableswap, orderbook, stablecoin, lending, perp, crowdfund, stoploss)과 RPL-1 프로그램(sealed-auction, ballot, eligibility, private-join, credit-score)은 randprotocol/example-programs에 있습니다.
공개되는 것과 숨겨지는 것
| 공개 | 비공개 |
|---|---|
프로그램 id와 그 코드, 티어, 선언된 가스 한도, 여덟 개의 출력, H_IN, 번들의 수수료와 공개 필드, 엔벨로프의 크기, 호출이 Keccak과 SHA-256 중 무엇을 사용했는지와 대략적인 사용 빈도(증명 헤더가 각 해시 테이블의 높이를 선언합니다) | 비공개 입력, 레지스터, 메모리, 분기, 실제 사이클 수와 정확한 가스, H_IN 솔트, 호출한 사람과 그가 무엇으로 지불했는지 |
같은 실행에 대한 두 증명은 서로 다른 바이트입니다. chain 15 이전에는 이것만으로 충분하지 않았습니다. 비공개 워드가 31개 이하인 호출은 숨기기에 너무 작은 테이블을 증명했고, 그 입력을 증명에서 복원할 수 있었습니다. 증명자는 v0.6부터 이 테이블의 최소 크기를 128행으로 고정했으며, 제약 집합 7(chain 16 이후)은 여전히 누출되던 테이블별 합계를 블라인딩합니다. hc는 바인딩 성질은 있으나 은닉 성질은 없습니다. 후보 프로그램을 열거할 수 있는 사람이라면 누구나 어느 것이 배포되었는지 확인할 수 있습니다.
아직 불가능한 것
fullnode가 밝히는 그대로 기술합니다.
- 프로그램 간 호출은 없으며, RPL-2 밖의 상태도 없습니다.
Call은 한 번의 실행에 대한 하나의 증명입니다. 호출자가 다시 넣어 주는 것 외에는 호출 사이에 아무것도 이어지지 않습니다. chain 20에서Invoke는 프로그램 자신의 셀과 볼트를 읽고 씁니다(위 참조). chain 20 이전의 어떤 체인에도 프로그램 상태가 없으며, invoke는 다른 프로그램의 셀을 건드릴 수 없습니다. - 볼트 밖에서 프로그램이 주도하는 전송은 없습니다. 효과 종류 1, 즉 프로그램이 계정으로 지불하는 전송은 계정과 함께 삭제되었으며,
Call의 출력은 진술일 뿐입니다. chain 20에서Invoke는 프로그램 자신의 볼트에서 최대 네 개의 노트까지 지불하며, 금액과 수신자는 공개됩니다. - 지갑에 컴파일러는 없습니다. 프로그램은 내장된 어셈블된 게스트이거나, circuits 저장소에서
rand-guest로 빌드한 이미지입니다. - 공개 입력은 호출마다가 아니라 배포 시점에 고정됩니다. 제약 집합 6이
READ_PUBLIC과H_PUB를 추가했습니다. chain 20에서 배포는 최대 32,768개의 공개 워드를 담을 수 있고, 프로그램 id가 이를 바인딩하며, 모든 호출의 증명은 그 다이제스트에 대해 검증됩니다. 호출별 공개 워드는 없습니다. - 해시 시스콜은 큰 증명을 만듭니다. keccak을 포함한 증명은 약 3.2 MB로, 기본 증명 상한 2 MiB를 단번에 넘습니다. sha256을 포함한 증명은 약 400 KB를 더합니다. chain 20의 상한은 4,194,304바이트(4 MiB, chain 17부터 19까지와 같으며 chain 13부터 16까지는 8 MiB였습니다)로 둘 다 수용합니다. chain 18에서 Keccak 테이블을 쓴 티어 12 호출은 3.35 MB, SHA-256 테이블을 쓴 티어 14 호출은 1.91 MB로 측정되었습니다(Example programs). 또한 호출은 128회의 Keccak-f 순열과 128회의 SHA-256 압축으로 제한되며(아래 참조), 증명 1 KiB마다 바이트 가격이 부과됩니다.
- EVM 및 Solana 프로그램은 chain 20에서 배포되지 않으며, 그 호출은 호출 티어 상한을 넘습니다. 인터프리터 게스트(
evm.bin18,009워드,sbpf.bin8,317워드)와 번역된 이미지(ERC-20 11,686워드, SPL Token 65,096워드)는 기본 상한 4,096워드를 넘고, chain 13이 정해 이후 모든 체인이 유지해 온 65,535워드 미만입니다. 번역된 두 이미지는 2026-09-19에 chain 13에 배포되었으며, SPL Token은 ELF를 공개 입력으로 하여 배포되었습니다. chain 14와 15는 새로 만들어졌고, 어느 쪽에도 이미지를 다시 배포하지 않았습니다. chain 20은 chain 19와 마찬가지로 네 가지 모두의 배포를 거부합니다.hardening_v6규칙에 따라, 티어 14 호출이 담을 수 없는 프로그램(공개 세그먼트가 없으면 8,180워드 초과, 있으면 그보다 적은 워드)은 배포 시점에 거부되므로(ProgramUncallable), 아무도 호출할 수 없는 프로그램에 배포 수수료를 내는 일이 없습니다. ERC-20 호출은 티어 16 또는 18에서 증명됩니다. 티어 16approve는 2026-09-19에 chain 13에서 3,412,405바이트의 프로덕션 증명으로 호출되었으며(Ethereum transpiler), chain 20은 티어 14를 넘는 모든 호출을 거부하므로(다음 항목) 오늘 그곳에서 허용되는 ERC-20 호출은 없습니다. SPL Token 호출은 티어 20이며 약 330 GB가 필요하고, SPL 호출 증명은 존재하지 않습니다(증명 메모리). EVM 블록 컨텍스트 옵코드는 트랩되며, 온체인 EVM 상태는 없습니다. - 호출은 티어 14 이하에서 증명됩니다. fullnode v0.5.6(2026-09-25) 이후 티어 14를 넘는 호출 증명은 거부되며(
CallTierTooHigh), 128회를 넘는 Keccak-f 순열이나 128회를 넘는 SHA-256 압축을 가진 증명도 마찬가지입니다. chain 13의 티어 16 ERC-20approve는 chain 15, 18, 19에서 그랬듯 chain 20에서도 거부될 것이고, 티어 18 ERC-20 호출이나 티어 20 SPL Token 호출도 모두 그렇습니다. 이들은 여전히 오프체인에서는 증명됩니다(예산). - GPU 증명은 하드웨어에서 실행된 적이 없습니다.
--cuda에는--features cuda빌드와 커밋된 PTX가 필요한데, 플릿에는 둘 다 없습니다. chain 20에서 호출 증명은 빌드와 관계없이 CPU 백엔드에서 만들어집니다. hc는 은닉 성질이 없으며, 위에서 설명한 대로입니다. 또한 증인 요청은 지갑이 어느 리프를 사용하는지를 노드에 여전히 알려 줍니다.