Rand Protocol
문서
RISC-V 프로그램

Solana 트랜스파일러

sbpf2rv는 Solana sBPF 프로그램을 Rand의 RISC-V ISA로 변환합니다. 이 문서는 sbpf2rv가 무엇인지, 신뢰 모델과 ELF 가드, 트랩이 발생하는 경우와 거부되는 경우, 측정된 수치, 그리고 실제 SPL Token 프로그램을 변환하여 sBPF 인터프리터와 워드 단위로 일치함을 보이는 전체 과정을 다룹니다.

sbpf2rv는 컴파일된 Solana 프로그램(cargo build-sbf가 만들어 내는 .so, 즉 sBPF ELF)을 받아 Rand 프로그램으로 변환합니다. 변환 결과는 C 코드로 출력되며, rand-guest build가 이 C 코드를 RV32IM 이미지로 컴파일합니다. 이 이미지는 같은 ELF와 같은 명령어에 대해 Rand의 sBPF 인터프리터 게스트인 sbpf.bin과 동일한 여덟 개의 출력 워드를 게시합니다. 이 페이지에서는 실제 SPL Token 프로그램을 대상으로 전체 경로를 보여 줍니다.

개요

입력sBPF v1 ELF
출력C shim crate. rand-guest build가 이를 이미지 컨테이너로 만듭니다
실행 위치체인 밖, 개발자의 머신. 체인은 이미지만 봅니다
일치해야 하는 대상동일한 두 입력 테이프에서의 sbpf.bin의 여덟 개 출력 워드
저장소circuits 저장소의 sbpf2rv/
고정된 툴체인Rust 1.98.1, clang 23.1.1, cc crate 1.4.6

변환된 프로그램은 인터프리터의 ABI를 그대로 유지합니다. 같은 테이프를 읽습니다. 공개 테이프에는 ELF가, 비공개 테이프에는 직렬화된 명령어(계정, 이어서 명령어 데이터)가 있습니다. 프로그램 주위에서 동일한 sbpf-core 하네스를 실행합니다. 입력을 디코딩하고, ELF를 로드하고, 계정 영역을 검사하고, 입력과 출력을 해시합니다. 바뀌는 것은 sBPF 실행 자체뿐이며, 이 부분만 네이티브 코드로 대체됩니다. 그래서 출력이 인터프리터의 출력과 워드 단위로 일치합니다.

신뢰 모델

변환된 프로그램의 증명은 세 가지로 묶여 있습니다.

  1. hc가 변환된 코드를 묶습니다. C 코드가 이미지에 컴파일되어 들어가므로, 이미지의 hc가 이를 포괄합니다.
  2. ELF 가드가 hc를 하나의 프로그램에 묶습니다. shim은 테이프에 있는 ELF에서 프로그램을 로드하므로, 검사가 없다면 하나의 이미지가 같은 hc 아래에서 호출자가 고른 다른 ELF를 실행할 수 있습니다. 그래서 생성된 program.c는 변환의 원본이 된 로드된 프로그램의 다이제스트로 끝납니다. 이 다이제스트는 텍스트, 읽기 전용 데이터, 그 주소들, 진입점을 포괄합니다. shim은 무엇이든 실행하기 전에 테이프에 있는 로드된 프로그램을 같은 방식(Poseidon2)으로 해시하여 비교합니다. 다른 프로그램은 사전 상태에 대해 상태 2의 BadElf로 중단됩니다. 따라서 이 hc에 대해 검증되는 증명은 그 프로그램의 실행입니다.
  3. H_PUB가 ELF의 바이트를 묶습니다. ELF는 공개 테이프로 전달되며, 증명의 공개 입력 다이제스트가 이를 워드 단위로 포괄합니다.

hc가 주어진 ELF의 충실한 변환인지는 다시 빌드하여 확인합니다. 고정된 sbpf2rv를 ELF에 실행하고, 고정된 툴체인으로 빌드한 다음, hc를 비교합니다. 빌드가 재현 가능한 것은 이 때문입니다.

체인은 원본 ELF나 그 해시를 기록하지 않습니다. 이미지와 그 프로그램 id를 저장합니다. ELF가 알려지기를 원하는 배포자는 프로그램과 함께 ELF를 공개합니다.

가드가 묶는 것은 파일이 아니라 로드된 프로그램입니다. 로더가 보지 않는 부분(헤더 패딩, 심볼 테이블)에서만 다른 두 ELF는 같은 프로그램으로 로드되어 양쪽에서 동일하게 실행됩니다. .rodata의 바이트가 하나라도 바뀐 ELF는, 전송이 읽지 않는 바이트라 하더라도, 변환본에서는 거부되고 인터프리터에서는 실행됩니다.

트랩되는 것과 거부되는 것

변환 단계에서 거부되는 것은 없습니다. 형식이 잘못된 ELF는 로더 자체의 메시지와 함께 거부되지만, 올바른 형식의 ELF 안에서는 어떤 명령어도 거부되지 않습니다. 스캐너는 프로그램이 마주칠 수 있는 것을 경고로 보고하며, 변환된 프로그램은 인터프리터가 트랩할 바로 그 지점에서 런타임에 트랩합니다.

경고의미런타임 동작
UnknownSyscall런타임이 구현하지 않은 syscall 해시UnknownSyscall 트랩
Cpi크로스 프로그램 호출(sol_invoke_signed*)동일한 UnknownSyscall 트랩. CPI는 구현되어 있지 않습니다
RegisterOutOfRange레지스터 r11..r15, 또는 r10을 넘는 callx 레지스터BadInsn 트랩
UnknownOpcodev2 전용 sdiv/srem 계열과 hor64를 포함해, v1 클래스가 없는 opcodeBadInsn 트랩
JumpOutOfText함수의 텍스트 밖으로 향하는 점프, 호출 또는 fall-throughBadJump 트랩
BadCallImmSrcsrc가 2..=10인 call imm(직접 만든 ELF에서만 발생)BadInsn 트랩

도달하지 않는 코드까지 거부하는 변환기는 오늘 인터프리터가 실행하는 프로그램들을 거부하게 됩니다. SPL Token이 그런 프로그램입니다. 이 프로그램은 sol_set_return_data와 sol_get_sysvar를 호출하지만, Transfer 경로는 이 둘에 도달하지 않습니다.

세 가지 차이는 허용됩니다. 각각은 인터프리터가 중단하지 않을 수도 있는 곳에서 변환본이 중단하게 만들 뿐이며, 인터프리터가 중단하는 실행을 변환본이 끝까지 완료하는 일은 없습니다.

  1. 스캔된 집합 밖의 callx 는 BadJump로 트랩됩니다. 인터프리터는 그 주소에서 실제 코드를 실행할 수도 있습니다.
  2. 예산 검사가 한 기본 블록만큼 지연될 수 있습니다. SPL Token을 65,535 워드 상한 안에 넣기 위해, 3,546개 블록 중 1,505개가 자체 명령어 한도 검사를 건너뜁니다. 한도를 넘는 그 한 블록 안에서는 중단의 종류가 다를 수 있습니다. 상태와 여덟 개의 워드는 같습니다.
  3. 원본 프로그램만 실행됩니다. 다른 ELF는 거부됩니다(위의 가드).

Ed25519와 secp256k1. sol_ed25519_verify와 sol_secp256k1_recover의 소프트웨어 구현이 sbpf-rt/에 있지만 링크되어 있지 않습니다. 인터프리터는 두 가지 모두에서 트랩하므로, 동등성을 위해 변환본도 트랩합니다. 호출 한 번의 비용은 각각 약 14.3M, 15.7M 사이클로, 가장 큰 티어(2²⁰)를 크게 넘습니다. 이들에는 코프로세서 테이블이 필요합니다.

측정 수치

SPL Token(guests-compiled/sbpf/programs/spl_token.so, 인터프리터 자체의 테스트 픽스처)에서 ELF 가드를 적용한 상태로 측정했습니다.

이미지프로그램 워드상한hc
SPL Token, 변환본65,09665,535 (여유 439)8ca905ae…758b
sbpf.bin, 인터프리터8,31765,535059a7086…f476
벡터상태sBPF 명령어 수변환본 사이클sbpf.bin 사이클티어(양쪽)
Transfer 2501143765,851694,49820
Transfer 잔액 초과0133748,836679,81420
MintTo 2501120708,648634,42320
잘못된 authority에 의한 MintTo0134696,694627,24120
Burn 2501131710,245637,03820
Burn 잔액 초과0121695,434624,78120
계정 두 개의 Transfer069645,015569,65620
계정 과다(하네스가 거부)20303,933304,61620

Transfer에서 사이클이 쓰이는 곳:

단계변환본sbpf.bin
두 테이프 읽기302,881302,880
ELF 로드177,012173,859
계정 영역 검사103,829103,824
sBPF 스택과 힙 0으로 채우기49,18149,181
출력 및 입력 해시, 공개 워드, glue51,09142,723
ELF 가드72,949—
sBPF 프로그램 자체8,90822,031
합계765,851694,498

변환된 프로그램 자체의 실행은 sBPF 명령어당 약 2.42.5배 저렴합니다(약 62 사이클 대 154 사이클). 그러나 모든 실행의 약 98%는 고정된 하네스가 차지하며, 가드가 73k 사이클을 더합니다. 따라서 SPL Token에서는 변환된 이미지가 69k75k 사이클 더 비싸고, 둘 다 티어 20에 머뭅니다.

변환이 이득이 되는 것은 연산 부하가 큰 프로그램뿐입니다. 명령어당 약 90 사이클을 절약하므로, sBPF 명령어 10,000개를 실행하는 호출은 약 0.9M 사이클을 절약합니다. SPL Token이 실행하는 것은 120~143개입니다.

따라 하기: SPL Token을 변환하고 대조하기

모든 명령은 7ef3220 시점의 circuits 체크아웃 루트에서 실행하며, 툴체인은 Rust 프로그램 배포의 것을, clang 23.1.1은 RISC-V 프로그램 배포의 것을 사용합니다. 긴 출력은 …로 표시한 곳에서 생략했습니다.

0. 도구 빌드

(cd sbpf2rv && cargo +1.98.1 build --release)
(cd rand-guest && cargo +1.98.1 build --release)

1. 변환

--out은 circuits 체크아웃 안의 디렉터리여야 합니다.

$ sbpf2rv/target/release/sbpf2rv guests-compiled/sbpf/programs/spl_token.so --out sbpf2rv/target/spl-token --name spl-token
entry pc 225: 30 function(s), 3546 block(s), 12061 instruction(s), 30 callx target(s), 0 refusal(s), 4 warning(s)
  fn 225: 2065 block(s)
  …                                                (30 functions in all)
  warning: UnknownSyscall { pc: 7803, hash: 2720453611 }
  warning: UnknownSyscall { pc: 9530, hash: 2720453611 }
  warning: UnknownSyscall { pc: 8698, hash: 2720453611 }
  warning: UnknownSyscall { pc: 11547, hash: 331461893 }
wrote sbpf2rv/target/spl-token/program.c (521757 bytes of C) and the spl-token shim crate: rand-guest build sbpf2rv/target/spl-token

0 refusal(s)입니다. 네 개의 경고는 Transfer가 도달하지 않는 실제 syscall입니다. 해시 2720453611은 sol_set_return_data(호출 지점 세 곳)이고 331461893은 sol_get_sysvar입니다.

2. 이미지 빌드

이미지가 기본 상한인 4,096 워드를 넘으므로 --max-words 65535를 전달합니다.

$ rand-guest/target/release/rand-guest build sbpf2rv/target/spl-token --max-words 65535
…
65096 words against a cap of 65535 (fits); 2 ecall(s) with a non-static a7
OK
wrote sbpf2rv/target/spl-token/image.bin and its .sha256 (65096 words, hc 8ca905ae3c62f503de7de86829040f323e098b53dba5fffe09b42f1aad16758b, program id 65d234ce9ca4d082f487c038abfa76e67f41a0d91e10b9ac388f1e1541d59184)

$ rand-guest/target/release/rand-guest info sbpf2rv/target/spl-token/image.bin
form: image container
text 64819 words at 0x10000; data 102 words (101 non-zero) at 0x4f4cc; prologue 277 words; program 65096 words from base_pc 0xfbac
hc 8ca905ae3c62f503de7de86829040f323e098b53dba5fffe09b42f1aad16758b
program id 65d234ce9ca4d082f487c038abfa76e67f41a0d91e10b9ac388f1e1541d59184
65096 words against a cap of 4096: does not fit

같은 툴체인으로 이 두 단계를 실행하면 누구나 이 hc를 얻습니다.

3. 둘 다 실행하고 비교

250의 Transfer는 두 개의 워드 목록을 사용합니다. 이 목록은 tests/parity.rs가 인터프리터 자체의 호출 인코딩으로 만든 것으로, 공개 워드 27,151개(워드로 인코딩된 ELF)와 비공개 워드 10,458개(계정, 이어서 Transfer 판별자와 금액)입니다. rand-guest run은 이를 --public과 --input으로 받습니다. 각 목록의 처음 몇 워드만 줄여서 보이면 다음과 같습니다.

$ rand-guest run sbpf2rv/target/spl-token/image.bin \
    --public 108600 1179403647 65794 0 0 17235971 1 2088 …   (27 151 words)
    --input  41825 4 0 65791 0 50529027 50529027 50529027 …  (10 458 words)
out[0] = 1
out[1] = 2892832079
out[2] = 376091303
out[3] = 1311040261
out[4] = 2015764099
out[5] = 3682593600
out[6] = 3311553006
out[7] = 141115266
cycles 765851
tier 20

같은 두 목록을 인터프리터 게스트에 넣은 결과:

$ rand-guest run guests-compiled/bin/sbpf.bin \
    --public 108600 1179403647 65794 0 0 17235971 1 2088 …   (27 151 words)
    --input  41825 4 0 65791 0 50529027 50529027 50529027 …  (10 458 words)
out[0] = 1
out[1] = 2892832079
out[2] = 376091303
out[3] = 1311040261
out[4] = 2015764099
out[5] = 3682593600
out[6] = 3311553006
out[7] = 141115266
cycles 694498
tier 20

여덟 개 워드가 모두 동일합니다. out[0] = 1은 상태(성공)입니다. out[1..8]은 인터프리터의 ABI가 입력과 출력에 대해 정의하는 다이제스트입니다.

4. 여덟 개 벡터를 한 번에 검사

parity 테스트는 변환된 이미지를 빌드하고, 모든 벡터를 두 이미지에서 rand-guest run으로 실행하며, 여덟 개 워드에 차이가 하나라도 있으면 실패합니다.

$ cd sbpf2rv && cargo +1.98.1 test --test parity the_spl_token -- --nocapture
…
translated SPL Token: 65096 program words (cap 65 535), hc 8ca905ae3c62f503de7de86829040f323e098b53dba5fffe09b42f1aad16758b
transfer 250: Ok(0), status 1; cycles translated 765851, interpreter guest 694498
transfer too much: Ok(1), status 0; cycles translated 748836, interpreter guest 679814
MintTo 250: Ok(0), status 1; cycles translated 708648, interpreter guest 634423
MintTo 250 signed by someone else: Ok(4), status 0; cycles translated 696694, interpreter guest 627241
Burn 250: Ok(0), status 1; cycles translated 710245, interpreter guest 637038
Burn 1 000 001 (balance 1 000 000): Ok(1), status 0; cycles translated 695434, interpreter guest 624781
transfer with two accounts: Ok(47244640256), status 0; cycles translated 645015, interpreter guest 569656
an account count above MAX_ACCOUNTS: Err(BadElf), status 2; cycles translated 303933, interpreter guest 304616
…
test the_spl_token_transfer_translates_and_matches_the_interpreter_word_for_word ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 8 filtered out; finished in 27.84s

5. 배포

rand program deploy sbpf2rv/target/spl-token/image.bin --public spl_token.so

이미지는 공개 테이프에서 ELF를 읽으므로, ELF는 배포 시점에 고정되는 프로그램의 공개 입력입니다. --public은 .so 파일 자체를 받으며, 지갑은 이를 4단계와 같은 27,151개의 워드로 인코딩합니다.

이를 위해서는 제네시스에서 max_program_words가 65,096 이상, max_program_public_words가 27,151 이상으로 설정된 체인이 필요합니다. 체인 12는 배포 한도가 4,096 워드였고 공개 입력을 받지 않았으므로, 지갑은 증명을 시작하기도 전에 이 이미지를 거부했습니다. v0.4 체인인 체인 13은 두 값을 65,535와 32,768로 설정했고, 이후의 모든 체인이 두 값을 유지합니다. 현재 운영 중인 체인 20(제네시스 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135)도 체인 13의 블록 및 호출 envelope 한도를 유지하며, 증명 크기 상한은 절반인 4 MiB(4,194,304바이트, 체인 17부터 적용, 체인 13~16은 8 MiB)로 낮췄습니다. 이제 크기 상한만이 유일한 관문은 아닙니다. 체인 20의 제네시스는 hardening_v6를 설정하며, 이에 따라 tier-14 호출 상한에서 한 번의 호출이 담을 수 있는 것보다 많은 프로그램 워드를 배포하면 유효하지 않은 배포가 됩니다(ProgramUncallable). 공개 입력이 없으면 최대 8,180 워드이고, 공개 입력이 함께 있으면 그보다 적습니다. 이 이미지는 27,151 워드의 공개 입력과 함께 65,096 워드이므로 체인 20은 배포를 거부하며, 호출도 거부할 것입니다. 모든 SPL 벡터가 tier 20에서 실행되어 tier-14 호출 상한을 넘기 때문입니다(한도). 체인 20에서는 블록 집계가 꺼져 있습니다(제네시스에 aggregation 섹션이 없습니다). 부트스트랩 노드는 검증자 실행에 나와 있습니다. 배포 수수료는 코드 워드와 공개 워드를 함께 계산합니다. 0.001 + 0.0001 × (65,096 + 27,151) = 9.2257 RAND이며, 아래 체인 13 배포가 지불한 수수료와 같습니다.

이 배포는 2026-09-19에 체인 13이 운영 체인이던 시기(프로덕션 FRI 프로파일, 제네시스 8123ccac1883a45750e4df6964fb7cd3f0b321798cde4c0ef406a0293939ece3)에 제네시스 지갑 shielded-1로 실행되었습니다. 이후의 모든 체인(14~19)은 체인 13의 프로그램 없이 새 체인으로 구성되었고 이 이미지는 그 어느 체인에도 다시 배포되지 않았으므로, 아래의 트랜잭션과 높이는 체인 13의 것이며 더 이상 탐색기에서 조회되지 않습니다. 프로그램 id는 base_pc, 워드, 공개 워드만의 해시이므로, 같은 이미지와 ELF는 배포를 허용하는 어느 체인에서나 같은 id를 받습니다. 진행 상황 줄을 생략(…)한 지갑의 출력은 다음과 같습니다.

$ rand program deploy <image.bin> --public spl_token.so
program id: 740236918310f8e52bb0c1ef49b2b0e0c018762e289666660685b0694c8dd00a (65096 words, hc ae05a98c03f5623c68e87dde320f0429538b093efeffa5db1a2fb4098b7516ad, public input 27151 words, digest ec57b10ed6f3a3d5fe87ec072f691322ad747377ba2f0a1788bcfe3e7334d67e)
…
proved in 94.3s: tier 14, 1418406 bytes
submitted deploy 626fc9387ecc1f6b39f44184807303ac2843029d8fb8e28338bf186445927530
…
program id740236918310f8e52bb0c1ef49b2b0e0c018762e289666660685b0694c8dd00a
image65,096 워드, rand-guest 표기로 hc 8ca905ae…, 지갑 표기로 ae05a98c…
public input27,151 워드(ELF), 다이제스트 ec57b10ed6f3a3d5fe87ec072f691322ad747377ba2f0a1788bcfe3e7334d67e
fee bundle (체인 13의 2-in-2-out 번들)tier 14, 1,418,406바이트, 94.3초에 증명
deploy tx (체인 13)626fc9387ecc1f6b39f44184807303ac2843029d8fb8e28338bf186445927530
anchored at height (체인 13)400
fee9.2257 RAND

체인 14부터 수수료를 지불하는 번들은 모든 트랜잭션이 담는 하나의 4-in-4-out hidden-asset 번들이며, 트랜잭션의 바인딩에 대해 증명됩니다. 이 번들은 여전히 tier 14이며, 체인 14와 15의 프로덕션 프로파일에서 약 100초, 약 1.42 MB입니다. 체인 17부터는 번들에 tier-10 인증 증명(분할 승인)도 포함되므로, 체인 18과 19에서 트랜잭션의 수수료 번들은 번들 증명 약 1.49 MB와 인증 증명 약 1.37 MB입니다.

프로그램 id는 ELF에 바인딩되므로, 순수 이미지에 대해 rand-guest info가 출력하는 id와 다릅니다. deploy가 출력한 id로 호출하십시오.

한도

  • 이 프로그램에 대한 호출은 증명된 적이 없고, 체인 20에 배포되어 있지 않으며, 체인 20은 배포와 호출을 모두 거부합니다. 위의 배포는 체인 13에서 이루어졌습니다. 체인 20의 hardening_v6에서는 tier-14 호출이 담을 수 있는 것보다 많은 워드의 배포가 유효하지 않으며(ProgramUncallable, 최대 8,180 워드), 이 이미지는 65,096 워드입니다. 입력 측면은 맞을 것입니다. ELF는 배포의 공개 입력이며 rand call이 직접 가져오고, 10,458개의 비공개 워드는 체인 20이 유지하는 입력 상한 16,071에 들어갑니다. 그러나 모든 SPL 벡터는 tier 20이고, fullnode v0.5.6(2026-09-25)부터 노드는 tier 14를 넘는 모든 호출 증명을 검증 키를 만들기 전에 거부합니다(CallTierTooHigh). 같은 검사는 호출이 선언할 수 있는 해시 테이블도 제한합니다. SHA-256 행 2¹³개(압축 128회)와 Keccak 행 2¹²개(순열 128회)입니다. tier-20 호출은 체인 밖에서만 증명할 수 있으며, tier-20 증명은 아직 존재하지 않습니다(다음 항목).
  • tier 20에서 만들어진 증명은 아직 없습니다. tier-20 증명은 48 GB 노트북에서 약 31 GB에서 중단되었고, 64 GB 드롭릿에서는 10분 41초 후 65.1 GB에서 OOM으로 종료되었습니다. 측정된 tier-16/18 스케일링(2 tier 증가당 메모리 약 ×3.9, 시간 약 ×4.1)으로 외삽하면 tier 20에는 약 330 GB와 약 3.6시간이 필요하며, 이는 DigitalOcean의 최대 메모리 드롭릿(m-32vcpu-256gb, 256 GB)보다 큽니다. 프로버는 단일 스레드로 실행되었고, 16-vCPU 장비에서 코어 하나가 99%를 사용했습니다. 증명 메모리를 참고하십시오.
  • CPI 없음. 프로그램 간 호출(Cross-program invocation)은 트랩됩니다.
  • sBPF v1만 지원합니다. v2 전용 opcode는 트랩됩니다.
  • Ed25519 및 secp256k1 syscall은 코프로세서 테이블이 생기기 전까지 트랩됩니다.
  • 체인은 ELF를 공개 입력으로만 기록합니다. --public으로 배포하면 ELF의 워드와 그 다이제스트가 저장되고, rand_getProgramPublic이 이를 제공합니다. 체인은 이미지가 ELF에서 어떻게 빌드되었는지는 기록하지 않습니다. 다른 사람이 hc를 다시 빌드할 수 있도록 빌드 입력을 공개하십시오.

fullnode 커밋 c0878c3f(chain 21) 기준으로 2026-10-12에 마지막으로 검증되었습니다, 그리고 circuits `7ef3220` (`sbpf2rv/README.md`, `docs/superpowers/specs/2026-09-18-sbpf-to-rv32-translator-design.md`); steps 1, 2 and the parity test re-run on 2026-09-19. 코드와 이 페이지의 내용이 다를 경우 코드가 옳습니다. 알려 주시기 바랍니다.