Rand Protocol
문서
예제 프로그램

chain 20의 프로그램 네 가지

testnet에 배포된 완결된 기밀 프로그램 네 가지(익명 투표, 부채 증명, 오프체인 원장, 비공개 신용 심사)를 다룹니다. 각 프로그램의 program id, 공통 설정, chain 18이 프로그램 작성자에게 가져온 변경 사항, 그리고 직접 호출하는 방법을 설명합니다.

이 네 프로그램은 각각 기밀 호출(confidential call)을 적용한 완결된 애플리케이션입니다. 문제 정의, 인터페이스, Rust 게스트, 호스트 측 도구, 그리고 라이브 testnet에서의 배포와 호출을 모두 포함합니다. 튜토리얼보다 분량이 길고, 각 프로그램은 머신의 서로 다른 부분을 활용합니다. Rust 또는 C로 프로그램 작성하기는 네 프로그램이 모두 사용하는 모델을 설명하는 가이드입니다. read_input, 공개 세그먼트, 여덟 개의 출력 슬롯이 낯설다면 먼저 읽어 보십시오.

여기 실린 모든 내용은 2026-09-30에 fullnode v0.6.7을 사용해 chain 18에서 처음 실행한 것이며, 이 페이지의 모든 출력은 그 실행 결과를 그대로 붙여 넣은 것입니다. 프로그램은 체인 컷(chain cut)을 넘어 이어지지 않으므로, 2026-10-01에 네 프로그램을 변경 없이 chain 19에, 이어서 chain 20(fullnode v0.6.8)에 다시 배포했고 그때마다 호출을 반복했습니다. 프로그램 id는 체인이 아니라 코드와 공개 세그먼트의 해시이므로 모든 id가 동일합니다. 트랜스크립트의 트랜잭션 해시는 chain 18의 것이며, 아래 표에는 chain 20의 해시를 링크했습니다. chain 20에서는 프로그램이 호출 사이에 상태를 유지할 수도 있지만(RPL-2), 이 네 프로그램은 상태를 사용하지 않습니다. 상태를 사용하는 프로그램은 프로그램 상태와 example-programs 저장소에서 다룹니다.

네 가지 프로그램

프로그램호출이 증명하는 것활용하는 요소워드티어
익명 투표호출자가 공개된 목록의 구성원이며, 누구인지 밝히지 않은 채, 이전에 투표한 적이 없음Keccak-256, 공개 세그먼트42712
부채 증명비공개 고객 잔액의 합이 공개된 준비금을 넘지 않으며, 각 고객이 확인할 수 있는 루트를 따름SHA-256, 프로그램 내부에서 구성한 트리99414
오프체인 원장일괄 처리된 전송이 원장을 한 상태에서 다음 상태로 옮기며, 모든 송신자가 승인되었고 새로 생성된 것이 없음Poseidon2, 호출 간에 이어지는 상태77314
비공개 신용 결정공개된 모델이, 인증자(attester)가 보증한 비공개 신청자 데이터를 승인하거나 거절함정수 연산, 공개 세그먼트 안의 모델46212

네 프로그램 모두 chain 20에서 운영 중입니다. 표의 각 호출은 해당 프로그램 페이지에 실린 chain 18 호출을 동일한 입력으로 반복한 것입니다.

프로그램프로그램 idchain 20 배포 트랜잭션chain 20 첫 호출
익명 투표ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970f6ae5528…0f823a304e7a…427b
부채 증명fce8474dfdf80b9733e04fb72d4d8d19c40ba5ab646b3d867322daf4b9b599c0a8bc281b…17d96116c15f…d62a
오프체인 원장c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab72cc95f2…3d519c9998bb…66eb
비공개 신용 결정7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c413e6658…e42bd3ceb306…124a

탐색기에서는 randscan.org/programs에서 모든 프로그램과 그 호출을 확인할 수 있습니다. testnet 체인은 주기적으로 교체되며 프로그램은 이어지지 않습니다. 다음 컷 이후에는 이 id들이 사라지므로 프로그램을 다시 배포해야 합니다.

설정

툴체인. 게스트는 circuits 저장소의 rand-guest로 빌드하며, 사용하는 커밋은 fullnode v0.6.7과 v0.6.8이 모두 vendoring하는 커밋입니다.

git clone https://github.com/randprotocol/zkp-circuits circuits
cd circuits
git checkout aeacf31                      # branch feat/v067-circuits
rustup toolchain install 1.98.1
rustup target add riscv32im-unknown-none-elf --toolchain 1.98.1
(cd rand-guest && cargo +1.98.1 build --release)
alias rand-guest="$PWD/rand-guest/target/release/rand-guest"

이 커밋이 중요한 이유는 두 가지입니다. 첫째, hc는 소스와 툴체인의 함수이므로 guest-sdk의 다른 커밋에서 게스트를 다시 빌드하면 hc가 달라질 수 있습니다. 둘째, SDK의 시그니처가 바뀌었습니다. aeacf31에서는 keccak과 sha256_compress가 이전 커밋의 raw 포인터 대신 배열 참조를 받습니다.

소스. 네 게스트와 호스트 스크립트, 데모 데이터는 하나의 다운로드로 제공됩니다. rand-guest build가 게스트 디렉터리에서 위쪽으로 탐색하며 guest-sdk를 찾으므로, 체크아웃 안에서 압축을 풀어야 합니다.

mkdir -p my-guests
curl -sSf https://randprotocol.org/examples/rand-example-programs.tar.gz | tar -xz --strip-components=1 -C my-guests
ls my-guests
# allowlist  ledger  ledger-host  score  solvency

파일은 https://randprotocol.org/examples/programs/ 아래에서 하나씩 열람할 수도 있습니다. 예를 들면 allowlist/src/main.rs입니다.

지갑. 배포와 호출에는 fullnode v0.6.8의 rand 지갑(태그 v0.6.8의 fullnode 체크아웃에서 cargo build --release -p randprotocol-client를 실행하거나, v0.6.8 릴리스에 첨부된 rand), 키, 그리고 RAND가 필요합니다. 이전 버전의 지갑은 chain 20에서 트랜잭션을 보낼 수 없습니다. chain 20은 모든 증명과 서명된 메시지를 제네시스에 결속하는데, v0.6.7 지갑은 그렇게 하지 않기 때문입니다.

RPC. 공개 엔드포인트 https://rpc.randprotocol.org만으로 이 페이지의 모든 작업을 수행할 수 있습니다. 프로그램과 영수증 읽기, 지갑 동기화, 배포와 호출이 모두 가능합니다. 지갑의 첫 동기화는 모든 블록 헤더와 노트를 포함한 모든 블록을 읽으며, 2026-09-30 chain 18의 높이에서 645건의 요청이 필요했고 엔드포인트를 통해 약 3분이 걸렸습니다. 엔드포인트는 주소당 분당 읽기 1,200건과 전송 6건을 허용합니다. 직접 운영하는 노드(노드 운영하기)에는 이런 제한이 없습니다.

export RAND_RPC=https://rpc.randprotocol.org
export RAND_KEY=$HOME/wallet.key.json

chain 18에서 익명 투표 페이지에서 집계한 투표 중 하나인 fdbcc960…fc2b는 노트북에서 증명을 생성해 공개 엔드포인트만으로 전송했습니다. chain 19와 chain 20에서는 매번 열세 건의 트랜잭션 전부, 즉 네 건의 배포와 아홉 건의 호출이 같은 방식으로 처리되었습니다.

chain 18이 프로그램에 바꾼 것

호출은 가스 한도를 선언하고, 그 값을 지불합니다. 제약 집합(constraint set) 8은 모든 실행을 계량합니다. 일반 사이클은 1 가스입니다. 해시 시스콜은 예외입니다.

명령가스
모든 RV32IM 명령, HALT, WRITE_OUTPUT, READ_INPUT, READ_PUBLIC1
n개 워드에 대한 POSEIDON23 + 3 × ⌈n / 4⌉
SHA256, 압축 1회64
KECCAK, 순열 1회192

증명에는 새로운 공개 값인 선언된 한도가 하나 추가되며, 회로는 실행의 가스가 이 한도 이하임을 강제합니다. 정확한 가스는 비공개로 유지됩니다. rand call은 먼저 에뮬레이터에서 프로그램을 실행하고 두 값을 모두 출력합니다.

gas: 5636 (declaring 6144, tier 12)

기본적으로 지갑은 정확한 가스를 2^(tier − 2)의 배수로 올림합니다. 티어 12에서는 1,024, 티어 14에서는 4,096이므로 선언된 한도는 실행에 대해 알려 주는 바가 거의 없습니다. --gas-limit max는 티어의 상한을 선언하며, 티어 외에는 아무것도 드러내지 않습니다. --gas-limit N은 정확한 값을 선언합니다.

수수료는 선언된 한도와 바이트 수를 따릅니다.

floor = 0.001 RAND  +  gas price × declared limit  +  byte price × ⌈bytes / 1024⌉

가격은 가스당 100 단위, KiB당 800 단위(1 단위는 10⁻⁹ RAND)로 시작하며, 블록마다 목표를 향해 조금씩 움직입니다. rand_getLimits가 현재 가격을 제공합니다. 호출은 가격이 책정된 뒤 두세 블록 후에 반영되므로, 지갑은 가격 두 단계만큼의 여유, 즉 1.125²배를 더합니다.

한 가지 세부 사항이 수수료의 대부분을 결정합니다. chain 18부터 호출의 증명은 해당 트랜잭션에 결속되므로, 지갑은 증명이 만들어지기 전에 수수료를 확정해야 하며, 증명의 실제 크기가 아니라 체인의 상한인 4 MiB로 증명 가격을 책정합니다. 익명 투표의 경우 (1,000,000 + 100 × 6,144 + 800 × 4,097) × 1.125² = 6,191,437 단위, 즉 0.0062 RAND이며, 이 가운데 가스는 약 8분의 1입니다. 이 페이지들의 네 호출은 각각 0.0057 RAND에서 0.0075 RAND 사이의 비용이 들었습니다. --fee로 기본값을 재정의할 수 있습니다.

모든 번들은 두 개의 증명입니다. chain 18은 분리된 승인(split authorisation)을 사용합니다. 수수료를 지불하는 번들은 티어 14 번들 증명 옆에 작은 티어 10 승인 증명을 함께 담습니다. 지갑은 둘 다 생성하고 각각에 대해 한 줄씩 출력합니다. 따라서 호출 하나는 호출, 승인, 번들의 세 증명으로 이루어집니다. Apple Silicon 노트북에서 여기 실린 네 호출은 각각 3분에서 5분 30초가 걸렸고, 메모리 사용량은 최대 6.1~6.6 GB였습니다. 번들 증명은 매번 그중 약 2분을 차지합니다. 티어 12 호출 증명은 30초를, 티어 14 호출 증명은 2분을 더합니다.

호출이 충족해야 하는 상한.

한도chain 18~20
티어최대 14: 16,383 사이클, 다이제스트 행 포함
Poseidon2 순열티어 14에서 2,048회, 프로그램, 입력, 공개 다이제스트 포함
Keccak 순열128
SHA-256 압축128
호출 증명4,194,304바이트(4 MiB)
프로그램공개 세그먼트가 없으면 8,180 워드, 있으면 그보다 조금 적음: chain 20은 chain 18, 19와 마찬가지로 max_program_words(65,535)가 허용하는 크기와 관계없이, 티어 14 호출로는 담을 수 없는 프로그램의 배포를 거부합니다(ProgramUncallable)
공개 세그먼트32,768 워드, 배포 시 고정
봉인된 트랜스크립트가 있는 비공개 입력16,071 워드

rand-guest run --tier 14는 배포 전에 앞의 두 항목을 검사합니다. 해시 테이블은 증명을 더 크게 만듭니다. 해시 테이블이 없는 원장의 티어 14 증명은 1.50 MB입니다. SHA-256 테이블은 부채 증명의 증명을 1.91 MB로, Keccak 테이블은 투표의 증명을 3.35 MB로 키우며, 이는 4 MiB 상한의 대부분입니다.

네 프로그램이 공유하는 규약

  • 슬롯 0은 상태 값이며, 0은 유효한 상태가 아닙니다. 실패를 포함한 모든 결과에는 1부터 시작하는 번호가 있으며, 실패는 증명이 가능하도록 깔끔하게 중단됩니다. 모든 값이 0인 영수증은 결과로 오인될 수 없습니다.
  • 슬롯 7은 건드리지 않습니다. 패닉이 발생한 Rust 게스트는 이 슬롯에 0xdeadbeef(3,735,928,559)를 기록합니다. 판독자는 슬롯 7을 가장 먼저 확인합니다.
  • 슬롯 2~6은 160비트 값을 담습니다. nullifier, 루트, 링크, 커밋먼트 등이 해당합니다. 256비트 전체 해시는 8개 워드이므로 상태를 넣을 자리가 남지 않습니다. 5개 워드로 줄이면 80비트의 충돌 저항성이 유지되며, 각 페이지는 그것이 무엇을 보호하는지 설명합니다.
  • 바이트 순서는 해시를 따릅니다. Keccak 값은 리틀 엔디언 워드이고 SHA-256 값은 빅 엔디언 워드입니다. 각 syscall이 블록을 읽는 방식이 그렇기 때문입니다. 호스트 스크립트와 게스트가 이 점에서 서로 다르면 다이제스트가 결코 일치하지 않습니다.
  • 인스턴스마다 달라지는 값은 공개 세그먼트에 들어가고, 호출마다 달라지는 값은 비공개 입력입니다. 투표의 루트, 준비금 수치, 모델의 가중치는 배포 시점에 고정되어 프로그램 id에 묶입니다. 호출은 자체적인 공개 워드를 전혀 담지 않습니다.
  • 입력 워드는 명령줄에서 십진수로 지정합니다. 각 페이지는 $(sed 's/[0-9][0-9]*/--input &/g' witness.txt)를 사용하여 witness 파일을 플래그로 변환합니다.

직접 호출해 보기

소스를 풀고 툴체인을 빌드했으며 자금이 있는 v0.6.8 지갑이 있다면, 데모 구성원으로서 투표하는 것은 네 개의 명령으로 끝납니다.

cd my-guests/allowlist
python3 allowlist.py witness 10 1 demo-secrets.txt 6 1 > witness.txt    # member 6, choice 1
rand-guest build . --max-words 65535      # only needed to run it in the emulator first
rand call ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 \
    $(sed 's/[0-9][0-9]*/--input &/g' witness.txt)

호출에는 이미지가 필요하지 않습니다. rand call은 노드에서 프로그램의 코드와 공개 세그먼트를 가져와 이들이 id로 해시되는지 확인한 뒤, 그것을 대상으로 증명을 생성합니다.

결과를 읽는 데는 지갑도 노드도 필요하지 않습니다. 모든 영수증은 공개 엔드포인트에 대한 한 번의 요청으로 조회할 수 있습니다.

$ curl -s https://rpc.randprotocol.org -H 'content-type: application/json' \
    -d '{"jsonrpc":"2.0","id":1,"method":"rand_getReceipt","params":["8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"]}'
{"id":1,"jsonrpc":"2.0","result":{"h_in":"ea71e7701e62d49cac37b239bef132178f481835027d62b34da45fd6cf2667c6","h_pub":"2296cfca8db9d38bd0b7579056c9dea3c8d8005df7b42e2b1e954a75fed67510","height":98335,"index":0,"outputs":[1,2,392974773,873526743,1921224601,3950868247,2248722827,0],"program":"ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970","tier":12,"tx":"8aa13d1f76d28f63a46c30bb09ef44b1ba35083ca80ba7301997f98e5f783666"}}

탐색기의 API는 한 번의 요청으로 프로그램의 호출 내역을 제공합니다. https://randscan.org/api/v1/programs/<program id>는 call_count와 recent_calls를 반환합니다(탐색기 API).

어느 프로그램도 하지 않는 일

Rand의 프로그램은 계산에 대한 증명이며, 체인은 그 출력 중 어느 것에 대해서도 동작하지 않습니다. 이 프로그램들 가운데 RAND나 토큰을 옮기거나, 잔액을 보유하거나, 이전 호출을 기억하는 것은 없습니다. 투표는 영수증을 읽는 쪽이 집계합니다. 부채는 별도의 증거가 필요한 준비금과 대조됩니다. 원장의 잔액은 오프체인 트리에 있는 숫자입니다. 신용 결정은 대출 기관이 따를지 말지를 선택하는 하나의 비트입니다. 각 페이지는 그 증명이 사용자에게 맡겨 두는 사항의 목록으로 끝납니다.

fullnode 커밋 48dc04b(chain 18) 기준으로 2026-09-30에 마지막으로 검증되었습니다, 그리고 circuits `aeacf31` (`guest-sdk/src/lib.rs`, `rand-guest`), the commit fullnode v0.6.7 vendors; `docs/fees.md`, `docs/zkvm.md`, `docs/cli.md` and `crates/randprotocol-client/src/main.rs` at fullnode `48dc04b`; the four deploys and the four calls were made on chain 18 on 2026-09-30, and every receipt was read back from `https://rpc.randprotocol.org`. 코드와 이 페이지의 내용이 다를 경우 코드가 옳습니다. 알려 주시기 바랍니다.