Halaman ini mencakup semua yang Anda perlukan untuk menjalankan program Anda sendiri di Rand: apa saja yang dapat menjadi program, cara merancang apa yang dibaca dan diterbitkannya, serta satu contoh lengkap yang ditulis dua kali, sekali dalam Rust dan sekali dalam C. Halaman ini kemudian membahas pengujian, anggaran pembuktian, deployment, pemanggilan, dan cara siapa pun dapat memeriksa hasilnya. Setiap keluaran rand-guest di bawah ini berasal dari eksekusi nyata dan ditempelkan persis seperti yang tercetak. Eksekusi dilakukan pada 2026-09-25 pada commit circuits 7ef3220. Testnet kini berada di chain 20, yang toolchain-nya, seperti pada chain 18 dan 19, dibangun pada aeacf31 (cabang feat/v067-circuits; fullnode v0.6.8 menyematkan commit yang sama), dan keluarannya belum dijalankan ulang di sana. Halaman ini menyebutkan di mana SDK atau chain berbeda.
Dua tutorial singkat adalah jalur cepatnya: Deploy a Rust program dan Deploy a RISC-V program. Halaman ini mengasumsikan Anda telah menyelesaikan langkah 1 tutorial Rust: checkout randprotocol/zkp-circuits pada aeacf31, Rust 1.98.1 dengan target riscv32im-unknown-none-elf, biner rand-guest, dan untuk C, clang 23.1.1 serta llvm-tools. The RISC-V ISA adalah rujukan untuk mesinnya sendiri. Example programs melanjutkan dari sini dengan program yang lebih besar dan apa yang diubah oleh pengukuran gas chain 18. Halaman ini membahas program RPL-1, yang dijalankan oleh Call tanpa status; chain 20 juga memiliki status program RPL-2, cell, dan vault yang diubah oleh Invoke (Programs), dengan contoh lengkap di randprotocol/example-programs.
1. Apa yang dapat menjadi program
Program adalah komputasi apa pun yang sesuai dengan bentuk berikut:
- Program membaca kata 32-bit sebagai masukan.
- Program menerbitkan paling banyak delapan kata 32-bit sebagai keluaran.
- Program bersifat deterministik: masukan yang sama selalu menghasilkan keluaran yang sama.
- Program berhenti dalam anggaran tier-nya (bagian 7).
Di dalam batas-batas itu, Anda dapat menulis apa saja. Anda mendapatkan perulangan, rekursi, struct, array, aritmetika 64-bit, crate, dan heap jika Anda menyediakan allocator. Namun mesinnya adalah RV32IM murni, dan tidak ada sistem operasi di bawahnya:
| yang tersedia | yang tidak tersedia |
|---|---|
instruksi bilangan bulat RV32IM, termasuk MUL/DIV/REM | perangkat keras floating-point (float perangkat lunak Rust berfungsi, tetapi lambat) |
hingga 1 MiB RAM mulai dari 0x1000 pada linker script bawaan, dengan stack 64 KiB | heap, kecuali Anda menyediakan allocator (bagian 6) |
| Poseidon2, Keccak-f, dan SHA-256 sebagai syscall | berkas, jaringan, jam, keacakan, thread, std, libc |
crate Rust no_std dan C yang berdiri sendiri | status yang bertahan dari satu pemanggilan ke pemanggilan berikutnya |
| delapan kata keluaran publik | cara memindahkan RAND atau token dari dalam program |
Baris terakhir adalah yang terpenting saat Anda merancang program. Keluaran adalah data, bukan efek. Sebuah pemanggilan membuktikan bahwa program dengan digest tertentu dijalankan pada masukan privat tertentu dan menerbitkan delapan kata. Chain memverifikasi bukti tersebut dan menyimpan delapan kata itu dalam sebuah kuitansi. Chain tidak mengubah saldo apa pun dan tidak memicu apa pun. Makna kata-kata itu disepakati antara program, pemanggilnya, dan siapa pun yang membaca kuitansi. Programs mencantumkan hal-hal yang belum didukung chain.
2. Rancang antarmuka terlebih dahulu
Sebuah program memiliki tiga kanal. Tentukan apa yang masuk ke masing-masing kanal sebelum Anda menulis kode apa pun.
| kanal | ditetapkan saat | dibaca dengan | siapa yang melihat | batas pada chain 20 |
|---|---|---|---|---|
| masukan privat | setiap pemanggilan | read_input(i) / rand_read_input(i) | hanya pemanggil, dan siapa pun yang diberi kunci pemanggilan tersebut | 16.071 kata (batas amplop masukan tersegel) |
| segmen publik | sekali, saat deploy | read_public(i) / rand_read_public(i) | semua orang; id program mengikatnya | 32.768 kata |
| keluaran | oleh eksekusi | write_output(slot, w) / rand_write_output | semua orang, dalam kuitansi | 8 kata |
Masukan privat tidak membuktikan apa pun tentang dunia dengan sendirinya
Masukan privat terikat pada bukti melalui komitmen bergaram H_IN, sehingga tidak dapat berubah setelah pembuktian. Namun program tidak memiliki cara untuk mengetahui bahwa masukan itu benar. Pemanggil dapat mengetikkan angka apa pun. Agar masukan privat bermakna, kaitkan dengan sesuatu yang publik di dalam program:
- sebuah komitmen yang diterbitkan sebelumnya (dalam segmen publik, atau diberikan kepada verifier di luar jalur) yang harus dibuka oleh masukan privat, seperti pada lelang di bawah;
- sebuah akar Merkle yang di bawahnya sebuah daun privat harus dibuktikan (Poseidon2 adalah syscall untuk ini);
- sebuah digest yang sudah dimiliki verifier, yang dihitung ulang oleh program dan diterbitkan sebagai keluaran.
Inilah keterampilan desain utama untuk program rahasia. Bukti memberi tahu verifier apa yang dihitung. Pengikatan pada data publik memberi tahu verifier atas apa.
Menyandikan data sebagai kata
Semuanya berpindah sebagai u32:
| data | penyandian |
|---|---|
| string byte | sebuah kata panjang, lalu byte-byte itu empat per kata, little-endian, kata terakhir diisi nol (bentuk yang digunakan wallet untuk masukan publik berupa ELF) |
u64 atau jumlah | dua kata, rendah lalu tinggi |
| bilangan bulat bertanda | komplemen dua dalam satu kata |
| hasil yang besar | digest Poseidon2-nya: 8 kata, yang mengisi seluruh keluaran; atau terbitkan beberapa kata ringkasan dan satu komitmen yang lebih pendek |
Alat baris perintah menerima kata masukan dalam desimal: rand-guest run --input dan rand call --input tidak menerima 0x…. Berkas segmen publik untuk rand program deploy --public menerima desimal maupun heksadesimal 0x.
Bentuk keluarannya
- Slot 0 adalah kata status. Beri setiap hasil sebuah nomor, termasuk kegagalan: pembukaan yang tidak cocok, terlalu banyak item, masukan di luar rentang. Pemeriksaan yang gagal tetap harus berhenti dengan bersih bersama sebuah status, sehingga pemanggil mendapatkan bukti yang menyatakan apa yang salah.
- Setiap slot ditulis paling banyak sekali. Penulisan kedua ke slot yang sama adalah trap (
DoubleWrite(0)di emulator), dan eksekusi yang mengalami trap tidak dapat dibuktikan. Slot yang tidak pernah Anda tulis terbaca sebagai nol. - Dalam Rust, biarkan slot 7 kosong. Guest Rust yang mengalami panic menulis
0xdeadbeef(3.735.928.559) ke slot 7 lalu berhenti. Keluaran yang telah ditulisnya sebelum panic tetap berada di tempatnya, sehingga pembaca harus memeriksa slot 7 sebelum memercayai yang lain (bagian 6). - Membaca melewati masukan yang diberikan adalah trap (
InputIndex(5)di emulator). Baca jumlahnya terlebih dahulu dan periksa.
3. Contoh kerja: lelang tawaran tersegel
Seorang penjual ingin menjalankan lelang harga kedua tanpa menerbitkan tawaran yang kalah:
- Setiap penawar memilih sebuah tawaran dan salt acak 128-bit, lalu memberikan komitmen kepada penjual: Poseidon2 atas
("BID1", indeks penawar, tawaran, salt). - Penjual men-deploy program lelang dengan harga cadangan dan setiap komitmen sebagai segmen publik-nya. Id program kini menamai lelang ini dan tidak ada yang lain.
- Setiap penawar mengirimkan tawaran dan salt-nya kepada penjual secara privat.
- Penjual memanggil program dengan setiap tawaran dan salt sebagai masukan privat. Program memeriksa setiap pembukaan terhadap komitmennya, memilih pemenang, dan menerbitkan
[status, pemenang, harga, jumlah penawar].
Chain mengetahui siapa yang menang dan pada harga berapa. Chain tidak pernah melihat tawaran yang kalah. Penjual yang mengubah tawaran apa pun mendapatkan BAD_OPENING alih-alih sebuah hasil, karena komitmen-komitmen itu tetap dalam id program.
Semuanya berada di dalam checkout circuits, karena rand-guest build menemukan guest-sdk dengan mencari ke atas dari direktori guest:
circuits/
guest-sdk/
rand-guest/
my-guests/
commit/ a Rust guest that computes one commitment
auction-core/ the auction rule as a plain no_std library, tested on the host
auction/ the Rust guest
auction-c/ the same guest in C
auction-c-test/ a host test for the C rule, outside the guest's directory
Emulator sebagai kalkulator hash
Para penawar membutuhkan Poseidon2 atas field Goldilocks, persis seperti yang dihitung oleh mesin. Alat yang paling sederhana dan benar adalah guest kecil yang menghitungnya, dijalankan pada emulator. Sebuah digest berjumlah delapan kata, tepat sebanyak slot keluaran.
my-guests/commit/Cargo.toml:
[package]
name = "commit"
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/commit/src/main.rs:
#![no_std]
#![no_main]
use guest_sdk::{halt, poseidon2, read_input, write_output};
/// Inputs: a bidder's index, bid and four salt words.
/// Outputs: the 8-word commitment Poseidon2("BID1", index, bid, salt[0..4]).
#[no_mangle]
pub extern "C" fn main() -> ! {
let mut buf = [0u32; 8];
buf[0] = 0x4249_4431; // "BID1"
for i in 0..6 {
buf[1 + i] = read_input(i as u32);
}
unsafe { poseidon2(buf.as_mut_ptr(), 7) };
for (slot, word) in buf.iter().enumerate() {
write_output(slot as u32, *word);
}
halt();
}
poseidon2(ptr, n) melakukan hash n kata di tempat dan menulis digest 8 kata di atas ptr..ptr+8, sehingga buffer harus memuat sedikitnya delapan kata meskipun hanya tujuh yang di-hash. Tag "BID1" menjaga digest ini tetap terpisah dari penggunaan Poseidon2 lainnya.
Pada aeacf31, poseidon2 adalah unsafe fn, karena menerima pointer mentah, sehingga pemanggilannya berada di dalam blok unsafe. Pada 7ef3220, tempat keluaran di bawah dihasilkan, fungsi itu aman dan baris yang sama ditulis tanpa blok tersebut. Sponge tidak melakukan padding: [a] dan [a, 0] menghasilkan hash yang sama. Hal itu tidak berbahaya di sini, karena setiap pesan terdiri dari tujuh kata dan diawali sebuah tag. Untuk pesan yang panjangnya dapat bervariasi, SDK pada aeacf31 memiliki poseidon2_len(buf, n), yang mengikat panjangnya (syscall 7), tetapi rand-guest check pada commit tersebut menolak syscall 7, sehingga guest semacam itu belum dapat dibangun (zkp-circuits issue #1). Letakkan panjangnya di dalam pesan.
$ rand-guest build my-guests/commit --out commit.bin
76 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote commit.bin and its .sha256 (76 words, hc 41007891b5c393296fdd76539c93d0b06678bc8a0bd52cfc3b6886dcf0381343, program id 11dabdf740989d43599b296c43342bf3ce890e980f5cc558d15ba8ca9e8e9438)
$ rand-guest run commit.bin --input 0 --input 400 --input 2654435769 --input 2135587861 --input 4087136352 --input 1559071514
out[0] = 3502385358
out[1] = 1187922325
out[2] = 2500412136
out[3] = 2452821097
out[4] = 2045657999
out[5] = 1866863173
out[6] = 678782893
out[7] = 3524569252
cycles 77
tier 10
Baris cargo dihilangkan dari keluaran build di sini dan seterusnya. Peringatan unaligned-scalar-mem yang dicetak cargo memang diharapkan (Deploy a Rust program).
Empat penawar, dengan tawaran dan salt berikut:
| penawar | penawaran | salt (empat kata) |
|---|---|---|
| 0 | 400 | 2654435769 2135587861 4087136352 1559071514 |
| 1 | 250 | 461845907 3432918353 3864292196 2246822507 |
| 2 | 900 | 3266489909 668265263 374761393 3550635116 |
| 3 | 300 | 4252018373 3042594569 1759714724 625341585 |
Menjalankan commit.bin satu kali untuk setiap penawar, lalu menulis harga cadangan (100), jumlah (4), dan keempat digest secara berurutan, menghasilkan segmen publik auction-public.txt sebanyak 34 kata:
100 4 3502385358 1187922325 2500412136 2452821097 2045657999 1866863173 678782893 3524569252 3413403069 1515911705 3633767467 1218237979 474331206 896634170 2889783686 541804293 3111308722 99534202 1917186958 1017842926 3082732585 4215265588 467906960 2929273893 588931287 1295911076 647129560 1384102451 625140897 4060943258 2970944086 698558155
Dalam lelang sungguhan, setiap penawar menjalankan commit.bin di mesinnya sendiri, dan salt berasal dari sumber acak yang sebenarnya.
4. Program dalam Rust
Aturan sebagai pustaka yang dapat diuji
Simpan logika yang tidak memerlukan syscall dalam crate no_std tersendiri. guest-sdk menolak membangun untuk target selain riscv32, sehingga crate guest tidak dapat menjalankan cargo test di laptop Anda. Crate pustaka tanpa dependensi guest-sdk dapat melakukannya.
my-guests/auction-core/Cargo.toml:
[package]
name = "auction-core"
version = "0.1.0"
edition = "2021"
[workspace]
my-guests/auction-core/src/lib.rs:
//! The auction's rule, with no syscalls, so it builds and tests on the host.
#![no_std]
#[derive(Debug, PartialEq, Eq)]
pub enum Outcome {
/// The highest bidder wins and pays the second-highest bid, or the reserve if higher.
Sold { winner: u32, price: u32 },
/// No bid reached the reserve.
NoSale,
}
/// A second-price (Vickrey) auction. Ties go to the lower index.
pub fn settle(reserve: u32, bids: &[u32]) -> Outcome {
let mut best: Option<(u32, u32)> = None; // (index, bid)
let mut second = 0u32;
for (i, &bid) in bids.iter().enumerate() {
match best {
Some((_, top)) if bid <= top => second = second.max(bid),
Some((_, top)) => {
second = top;
best = Some((i as u32, bid));
}
None => best = Some((i as u32, bid)),
}
}
match best {
Some((winner, top)) if top >= reserve => Outcome::Sold { winner, price: second.max(reserve) },
_ => Outcome::NoSale,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn highest_bid_pays_the_second() {
assert_eq!(settle(100, &[400, 250, 900, 300]), Outcome::Sold { winner: 2, price: 400 });
}
#[test]
fn the_reserve_sets_a_floor() {
assert_eq!(settle(500, &[400, 250, 900, 300]), Outcome::Sold { winner: 2, price: 500 });
assert_eq!(settle(1000, &[400, 250, 900, 300]), Outcome::NoSale);
}
#[test]
fn a_tie_goes_to_the_lower_index() {
assert_eq!(settle(0, &[700, 700]), Outcome::Sold { winner: 0, price: 700 });
}
#[test]
fn no_bids_is_no_sale() {
assert_eq!(settle(0, &[]), Outcome::NoSale);
}
}
$ cd my-guests/auction-core && cargo +1.98.1 test
running 4 tests
test tests::a_tie_goes_to_the_lower_index ... ok
test tests::highest_bid_pays_the_second ... ok
test tests::no_bids_is_no_sale ... ok
test tests::the_reserve_sets_a_floor ... ok
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
Guest
my-guests/auction/Cargo.toml bergantung pada kedua crate melalui path:
[package]
name = "auction"
version = "0.1.0"
edition = "2021"
[dependencies]
guest-sdk = { path = "../../guest-sdk" }
auction-core = { path = "../auction-core" }
[profile.release]
opt-level = 3
lto = true
panic = "abort"
codegen-units = 1
[workspace]
my-guests/auction/src/main.rs:
#![no_std]
#![no_main]
use auction_core::{settle, Outcome};
use guest_sdk::{halt, poseidon2, read_input, read_public, write_output};
const MAX_BIDDERS: usize = 64;
const TAG: u32 = 0x4249_4431; // "BID1", the same tag the commit guest uses
// Output slot 0: what happened.
const SOLD: u32 = 1;
const NO_SALE: u32 = 2;
const BAD_OPENING: u32 = 3;
const TOO_MANY: u32 = 4;
/// Public (fixed at deploy): reserve, n, then n 8-word commitments.
/// Private (per call): for each bidder, the bid and four salt words.
/// Outputs: [status, winner, price, n, 0, 0, 0, 0]; a panic sets slot 7 to 0xdeadbeef.
#[no_mangle]
pub extern "C" fn main() -> ! {
let reserve = read_public(0);
let n = read_public(1) as usize;
if n > MAX_BIDDERS {
finish(TOO_MANY, 0, 0, n as u32);
}
let mut bids = [0u32; MAX_BIDDERS];
for i in 0..n {
let base = (5 * i) as u32;
let bid = read_input(base);
let mut buf = [TAG, i as u32, bid, read_input(base + 1), read_input(base + 2),
read_input(base + 3), read_input(base + 4), 0];
unsafe { poseidon2(buf.as_mut_ptr(), 7) };
let committed = 2 + 8 * i as u32;
for w in 0..8 {
if buf[w] != read_public(committed + w as u32) {
finish(BAD_OPENING, i as u32, 0, n as u32);
}
}
bids[i] = bid;
}
match settle(reserve, &bids[..n]) {
Outcome::Sold { winner, price } => finish(SOLD, winner, price, n as u32),
Outcome::NoSale => finish(NO_SALE, 0, 0, n as u32),
}
}
fn finish(status: u32, winner: u32, price: u32, n: u32) -> ! {
write_output(0, status);
write_output(1, winner);
write_output(2, price);
write_output(3, n);
halt();
}
Setiap jalur keluar melewati finish, yang menulis setiap slot satu kali lalu menghentikan eksekusi. Batas perulangan berasal dari segmen publik, yang ditetapkan saat deploy. Dengan demikian, pemanggil tidak dapat membuat program membaca lebih sedikit penawaran daripada yang telah di-commit.
Build dan eksekusi
$ rand-guest build my-guests/auction --out auction.bin
258 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote auction.bin and its .sha256 (258 words, hc aa04e4c451e986f086e9fcff53b213eb52e593317c32a979d5e24991164f050d, program id 148ab0c4b9ad5115caab4a2ab433e3e13b5e8fbecbd9d8a9d6ba20c64f942c73)
rand-guest run menerima segmen publik sebagai flag --public yang diulang. Di shell:
PUB=$(for w in $(cat auction-public.txt); do printf -- "--public %s " "$w"; done)
BIDS="--input 400 --input 2654435769 --input 2135587861 --input 4087136352 --input 1559071514
--input 250 --input 461845907 --input 3432918353 --input 3864292196 --input 2246822507
--input 900 --input 3266489909 --input 668265263 --input 374761393 --input 3550635116
--input 300 --input 4252018373 --input 3042594569 --input 1759714724 --input 625341585"
rand-guest run auction.bin $PUB $BIDS --tier 10
out[0] = 1
out[1] = 2
out[2] = 400
out[3] = 4
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 641
tier 10
tier 10: fits (cycles 721 of 1023, Poseidon2 permutations 88 of 128)
Terjual (1) kepada penawar 2 pada harga 400, yaitu penawaran tertinggi kedua, dengan empat penawar terhitung. Dua eksekusi yang seharusnya gagal:
| perubahan | keluaran 0–3 | arti |
|---|---|---|
| penawaran 250 milik penawar 1 diganti menjadi 260 | 3 1 0 0 | BAD_OPENING pada penawar 1; tidak ada hal lain yang dipublikasikan |
| harga cadangan 1.000 alih-alih 100 (segmen publik yang berbeda, sehingga program id yang berbeda) | 2 0 0 4 | NO_SALE: tidak ada penawaran yang mencapai harga cadangan |
5. Program yang sama dalam C
Direktori guest C berisi file .c dan header Anda sendiri. rand-guest build --lang c menyediakan guest.h, _start, dan runtime kecil (Deploy a RISC-V program). Perintah ini mengompilasi setiap file .c di direktori, sehingga file uji host harus ditempatkan di tempat lain.
my-guests/auction-c/auction.h:
#ifndef AUCTION_H
#define AUCTION_H
#include <stdint.h>
/* Outcome of a second-price auction. sold is 0 when no bid reached the reserve. */
struct outcome {
uint32_t sold;
uint32_t winner;
uint32_t price;
};
struct outcome settle(uint32_t reserve, const uint32_t *bids, uint32_t n);
#endif
my-guests/auction-c/settle.c:
#include "auction.h"
/* A second-price (Vickrey) auction. Ties go to the lower index. */
struct outcome settle(uint32_t reserve, const uint32_t *bids, uint32_t n) {
struct outcome o = {0, 0, 0};
if (n == 0) {
return o;
}
uint32_t winner = 0, top = bids[0], second = 0;
for (uint32_t i = 1; i < n; i++) {
if (bids[i] > top) {
second = top;
top = bids[i];
winner = i;
} else if (bids[i] > second) {
second = bids[i];
}
}
if (top >= reserve) {
o.sold = 1;
o.winner = winner;
o.price = second > reserve ? second : reserve;
}
return o;
}
my-guests/auction-c/main.c:
#include "guest.h"
#include "auction.h"
#define MAX_BIDDERS 64
#define TAG 0x42494431u /* "BID1", the same tag the commit guest uses */
enum { SOLD = 1, NO_SALE = 2, BAD_OPENING = 3, TOO_MANY = 4 };
static uint32_t bids[MAX_BIDDERS];
static void finish(uint32_t status, uint32_t winner, uint32_t price, uint32_t n) {
rand_write_output(0, status);
rand_write_output(1, winner);
rand_write_output(2, price);
rand_write_output(3, n);
rand_halt();
}
/* Public (fixed at deploy): reserve, n, then n 8-word commitments.
* Private (per call): for each bidder, the bid and four salt words.
* Outputs: [status, winner, price, n, 0, 0, 0, 0]. */
void main(void) {
uint32_t reserve = rand_read_public(0);
uint32_t n = rand_read_public(1);
if (n > MAX_BIDDERS) {
finish(TOO_MANY, 0, 0, n);
}
for (uint32_t i = 0; i < n; i++) {
uint32_t base = 5 * i;
uint32_t buf[8] = {TAG, i, rand_read_input(base), rand_read_input(base + 1),
rand_read_input(base + 2), rand_read_input(base + 3),
rand_read_input(base + 4), 0};
bids[i] = buf[2];
rand_poseidon2(buf, 7);
for (uint32_t w = 0; w < 8; w++) {
if (buf[w] != rand_read_public(2 + 8 * i + w)) {
finish(BAD_OPENING, i, 0, n);
}
}
}
struct outcome o = settle(reserve, bids, n);
if (o.sold) {
finish(SOLD, o.winner, o.price, n);
}
finish(NO_SALE, 0, 0, n);
}
bids adalah static yang diinisialisasi dengan nol, sehingga ditempatkan di .bss. Hal itu tidak menambah kata pada image, karena RAM sudah terbaca nol.
Menguji aturan di host
settle.c tidak menggunakan syscall, sehingga compiler biasa Anda dapat membangunnya. my-guests/auction-c-test/test_settle.c:
#include <assert.h>
#include <stdio.h>
#include "../auction-c/auction.h"
int main(void) {
uint32_t bids[] = {400, 250, 900, 300};
struct outcome o = settle(100, bids, 4);
assert(o.sold && o.winner == 2 && o.price == 400);
o = settle(500, bids, 4);
assert(o.sold && o.price == 500);
o = settle(1000, bids, 4);
assert(!o.sold);
uint32_t tie[] = {700, 700};
o = settle(0, tie, 2);
assert(o.winner == 0 && o.price == 700);
puts("settle: 4 checks passed");
return 0;
}
$ cc -Wall -o test_settle my-guests/auction-c-test/test_settle.c my-guests/auction-c/settle.c && ./test_settle
settle: 4 checks passed
Build dan eksekusi
$ rand-guest build --lang c my-guests/auction-c --out auction-c.bin
200 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
OK
wrote auction-c.bin and its .sha256 (200 words, hc 68605aa8d28a863a4a52a25de07ba159327a0fd0145a9dcd5e5aa6e79ab6bea0, program id 5667c343b8848805a908c9009d426164f39d2f927b2c780529a054f0d66aafc1)
$ rand-guest run auction-c.bin $PUB $BIDS --tier 10
out[0] = 1
out[1] = 2
out[2] = 400
out[3] = 4
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 0
cycles 576
tier 10
tier 10: fits (cycles 641 of 1023, Poseidon2 permutations 73 of 128)
Keluarannya sama dengan guest Rust. Dengan empat penawar, image C lebih kecil 58 kata dan lebih cepat 65 siklus; bagian 7 menunjukkan keseimbangan berbalik pada 64 penawar. Keduanya adalah program yang berbeda dengan hc dan program id yang berbeda. Deploy yang mana pun yang source-nya Anda publikasikan.
6. Catatan bahasa
Rust
Crate. #![no_std], #![no_main], dan #[no_mangle] pub extern "C" fn main() -> ! yang diakhiri dengan halt(). Profil release menetapkan panic = "abort". Tabel [workspace] yang kosong menjaga crate tetap di luar workspace induk mana pun. Jangan sertakan .cargo/config.toml maupun flag compiler Anda sendiri: rand-guest build menolak yang pertama dan menetapkan seluruh yang kedua (Deploy a Rust program).
Panic. Panic handler milik guest-sdk menulis 0xdeadbeef ke slot 7 lalu menghentikan eksekusi. Eksekusi tersebut tetap merupakan eksekusi yang valid dan dapat dibuktikan. Program ini membaca indeks 999 dari Vec berisi enam elemen setelah menulis slot 0 dan 1:
$ rand-guest run heap.bin --input 6 --input 1 --input 2 --input 3 --input 4 --input 5 --input 6
out[0] = 4
out[1] = 1080033280
out[2] = 0
out[3] = 0
out[4] = 0
out[5] = 0
out[6] = 0
out[7] = 3735928559
cycles 1326
tier 12
Slot 0 dan 1 berisi nilai yang sebenarnya, dan slot 7 menyatakan bahwa eksekusi mengalami panic. Pemanggil yang mengirimkan eksekusi semacam itu memperoleh receipt di chain. Pihak yang membaca receipt harus menolak slot 7 = 3.735.928.559, atau program tidak boleh pernah mengalami panic. Utamakan get(), aritmetika dengan pemeriksaan, dan kode status yang eksplisit pada setiap jalur.
Heap. Secara bawaan tidak ada allocator. alloc berfungsi setelah Anda menyediakannya. Bump allocator di atas arena berukuran tetap cocok untuk program yang berjalan sekali lalu keluar:
extern crate alloc;
use alloc::vec::Vec;
use core::alloc::{GlobalAlloc, Layout};
use core::cell::UnsafeCell;
/// A bump allocator over a fixed arena: allocation moves a pointer forward, free does nothing.
/// One run is one short-lived process, so nothing needs to be reclaimed.
struct Bump {
arena: UnsafeCell<[u8; ARENA]>,
next: UnsafeCell<usize>,
}
const ARENA: usize = 256 * 1024;
unsafe impl Sync for Bump {}
unsafe impl GlobalAlloc for Bump {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let next = &mut *self.next.get();
let start = (*next + layout.align() - 1) & !(layout.align() - 1);
let end = start + layout.size();
if end > ARENA {
return core::ptr::null_mut();
}
*next = end;
(self.arena.get() as *mut u8).add(start)
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}
#[global_allocator]
static HEAP: Bump = Bump { arena: UnsafeCell::new([0; ARENA]), next: UnsafeCell::new(0) };
Arena tersebut bernilai nol, sehingga ditempatkan di .bss dan tidak menambah kata pada image. Linker script bawaan menyediakan RAM sebesar 1 MiB, dan arena, static Anda, serta stack 64 KiB harus muat di dalamnya. Dengan allocator ini, Vec, collect(), dan iter().sum() semuanya berhasil dibangun dan lolos check. Namun alloc beserta dependensinya menambah kode: program uji ini berukuran 1.341 kata, dibandingkan 258 kata untuk program lelang. Bagian 7 menjelaskan mengapa ukuran program membebani anggaran pembuktian.
Floating point. Target ini tidak memiliki instruksi float, dan Rust menurunkan f32/f64 menjadi rutin perangkat lunak. Rutin tersebut berfungsi: rata-rata dari 9, 2, 7, 4, dan 5 yang dihitung dalam f64 menghasilkan bit dari 5.4f32. Namun masing-masing memakan banyak siklus. Gunakan bilangan bulat atau fixed point bila memungkinkan.
Crate. Crate no_std dari crates.io dibangun seperti dependensi lainnya. CRC-32 dari hello, rand oleh crate crc, yang dijalankan di emulator, mencetak 2124296908, nilai yang sama dengan hasil zlib.crc32 milik Python. Crate yang memerlukan std tidak dapat dibangun untuk target ini. rand-guest build tidak meneruskan --locked, sehingga commit Cargo.lock Anda: tanpanya, build ulang beberapa bulan kemudian dapat me-resolve crate yang lebih baru dan menghasilkan hc yang berbeda.
Jebakan unimp: temuan Csr dari kode pustaka. Sebagian kode core dikompilasi menjadi kata unimp (0xc0001073, csrrw x0, cycle, x0) pada jalur yang dianggap LLVM tidak dapat dijangkau. Mesin ini tidak memiliki CSR, sehingga check menolak image tersebut:
0x00002250 0xc0001073 Csr: CSR instruction: the machine has no CSRs
2613 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED
wrote heap.bin (the loader refuses it: no hc)
Penyebabnya di sini adalah slice::sort_unstable pada Vec yang panjangnya baru diketahui saat run time. sort() yang stabil menghasilkan hal yang sama, sedangkan sort_unstable pada array tetap berisi 16 elemen lolos. Untuk menemukan sumber temuan semacam itu, disassemble ELF guest dan lihat fungsi di sekitar alamat yang dicetak oleh check:
llvm-objdump -d -C my-guests/heap/target/riscv32im-unknown-none-elf/release/heap | grep -B12 unimp
Kemudian ganti pemanggilan tersebut dengan kode Anda sendiri (untuk program ini, insertion sort yang pendek). halt() milik guest-sdk sendiri diakhiri dengan loop {} alih-alih janji noreturn karena alasan yang sama.
C
- Titik masuknya adalah
void main(void), dan diakhiri denganrand_halt()._startbawaan menetapkan stack dan memanggilnya. - Tidak ada libc. Tidak ada
printf,malloc, atau<string.h>. Runtime menyediakanmemcpy,memmove,memset, danmemcmp(clang menghasilkan pemanggilan ke fungsi-fungsi tersebut untuk penyalinan struct dan inisialisasi besar) serta empat fungsi bantu pembagian 64-bit. Semuanya berupa perulangan byte, sehingga salin dan bagilah dalam satuan kata pada kode yang sering dijalankan. Untuk heap, tulis bump allocator di atas arraystatic, seperti pada Rust. - Perilaku tak terdefinisi tidak ditangkap. Tidak ada panic handler. Penulisan di luar batas merusak memori secara diam-diam, dan load atau store yang tidak selaras menyebabkan trap, yang berarti eksekusi tidak dapat dibuktikan. Periksa indeks terhadap batas Anda sendiri, seperti
main.cmemeriksanterhadapMAX_BIDDERS. - Aturan direktori. Jangan menambahkan
guest.hataustart.SAnda sendiri;build --lang cmenolak keduanya. Simpan file uji di luar direktori guest, karena setiap file.cdi dalamnya dikompilasi ke dalam image. - clang yang dipatok.
rand-guestmenolak clang selain versi 23.1.1.RAND_GUEST_CLANG_UNPINNED=1mengesampingkan hal itu, disertai peringatan bahwahcAnda tidak akan cocok dengan build orang lain.
7. Apakah akan terbukti? Anggarannya
Prover membulatkan setiap eksekusi ke sebuah tier t. Sebuah tier memiliki dua anggaran yang independen, dan rand-guest run --tier t melaporkan keduanya:
- baris: paling banyak
2ᵗ − 1, yang menghitung siklus yang dieksekusi ditambah baris digest; - permutasi Poseidon2: paling banyak
2ᵗ⁻³.
| tier | baris | permutasi Poseidon2 | batas atas gas tanpa tabel hash | pemanggilan di chain 20 |
|---|---|---|---|---|
| 10 | 1.023 | 128 | 1.279 | diterima |
| 12 | 4.095 | 512 | 5.119 | diterima |
| 14 | 16.383 | 2.048 | 20.479 | diterima |
| 16 | 65.535 | 8.192 | 81.919 | ditolak sejak v0.5.6 |
| 18, 20 | 2¹⁸ − 1, 2²⁰ − 1 | 2¹⁵, 2¹⁷ | 327.679, 1.310.719 | ditolak sejak v0.5.6 |
Biaya mengikuti gas dan byte, bukan tier. Pada chain 15, sebuah pemanggilan membayar 0.001 + call_fee(tier) yang flat: 0,002 RAND pada tier 10, 0,0021 pada tier 12, dan 0,0022 pada tier 14. Sejak chain 18, setiap eksekusi diukur. Satu baris biasa bernilai 1 gas, satu baris absorb Poseidon2 bernilai 3, satu permutasi Keccak bernilai 192, dan satu kompresi SHA-256 bernilai 64. Proof mempublikasikan batas gas yang dideklarasikan, circuit menegakkan bahwa gas eksekusi tidak melebihinya, dan gas yang sebenarnya tetap privat. Batas atas yang dapat dideklarasikan sebuah tier tanpa tabel hash adalah (2ᵗ − 1) + 2ᵗ⁻², yaitu kolom di atas. Biaya minimumnya adalah
floor = BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉
dengan BUNDLE_BASE 0,001 RAND, byte yang dimaksud adalah proof pemanggilan ditambah envelope input, serta harga yang bermula dari 100 unit per gas dan 800 unit per KiB (satu unit adalah 10⁻⁹ RAND) dan bergeser sedikit pada setiap blok. Pada chain 20, harga tersebut tidak dapat naik melebihi 10.000 dan 80.000 unit, dan hanya byte proof dan envelope dari pemanggilan yang menggerakkan harga byte; pada chain 18 dan 19 tidak ada batas atas. Bagian 9 menunjukkan apa yang dibayar wallet secara bawaan, dan Example programs memuat empat pemanggilan terukur di chain 18, masing-masing sebesar 0,0057 hingga 0,0075 RAND.
Tier 14 adalah tingkat tertinggi yang dapat digunakan sebuah pemanggilan. Sejak fullnode v0.5.6 (2026-09-25), node menolak bukti pemanggilan apa pun di atas tier 14 dengan CallTierTooHigh, sebelum node membangun verifier key. Aturan yang sama membuat blok yang memuat pemanggilan semacam itu menjadi tidak valid. Batas ini melindungi validator dengan memori 2–4 GB: verifier key tier 20 membutuhkan waktu berminggu menit dan memori berGB-GB untuk dibangun. rand call tidak menolak pemanggilan semacam itu sebelum membuktikannya, meskipun pada chain 20 perintah ini mencetak tier terlebih dahulu. Jalankan rand-guest run … --tier 14 sebelum Anda memanggil, dan jika hasil eksekusi tidak muat, susun ulang program tersebut. Sebelum deploy, rand program deploy --input <word> … (atau --check-call untuk pemanggilan tanpa input) menjalankan pemanggilan yang Anda maksud melalui emulator dan menolak melakukan deploy kecuali hasilnya berada pada tier 14 atau lebih rendah.
Apa yang menghabiskan anggaran
Baris digest dan permutasi merupakan hal yang tak terduga. Sebelum kode Anda berjalan, mesin meng-hash program, input privat, dan segmen publik menjadi hc, H_IN, dan H_PUB. Masing-masing memakan sekitar satu permutasi dan satu baris per empat word. Selain itu, setiap pemanggilan POSEIDON2 yang Anda lakukan memakan sekitar satu permutasi per empat word yang di-hash. Untuk lelang pada tier 10:
| item | word | permutasi |
|---|---|---|
program (hc) | 258 | sekitar 65 |
input privat (H_IN) | 20 | sekitar 5 |
segmen publik (H_PUB) | 34 | sekitar 9 |
| empat pemeriksaan komitmen, masing-masing 7 word | 4 × 7 | 8 |
total, sebagaimana dilaporkan run | 88 dari 128 |
Baris: 641 siklus ditambah baris digest menjadi 721 dari 1.023.
Pada chain 20, seperti pada chain 19, hitungan milik wallet sendiri sedikit lebih tinggi daripada hitungan rand-guest run. Bukti pemanggilan di sana terikat pada transaksinya (pada chain 20 pengikatan itu juga mencakup hash genesis, dengan delapan word yang sama), dan pengikatan tersebut dibawa sebagai delapan word tambahan pada segmen publik: dua baris digest dan dua permutasi lebih banyak untuk program dengan segmen publik, masing-masing satu lebih banyak untuk program tanpa segmen publik.
Hal ini membuat ukuran program menjadi bagian dari anggaran. Program uji heap hanya menjalankan 1.183 siklus, tetapi 1.341 word-nya memakan 340 permutasi dan mendorongnya ke tier 12. Pada 2.048 permutasi milik tier 14, program yang jauh di atas sekitar 8.000 word sama sekali tidak dapat dipanggil, apa pun yang dikerjakannya. Pada chain 20, batas pastinya adalah 8.180 word untuk program tanpa segmen publik dan pemanggilan tanpa input, dan lebih kecil jika ada salah satunya. Batas program pada chain 20 adalah 65.535 word, sama seperti pada chain 15, tetapi chain 19 dan 20 menolak deploy program yang melebihi batas tersebut (ProgramUncallable), sedangkan chain 15 tetap mengambil biaya deploy untuk program yang tidak dapat dipanggil siapa pun. Itulah sebabnya ERC-20 hasil terjemahan milik chain 13 (11.686 word, dipanggil pada tier 16) tidak dapat di-deploy maupun dipanggil pada chain 20. Jaga agar program tetap kecil: opt-level = "s" pada Rust, -Os (sudah diatur) pada C, sedikit dependensi, dan tanpa alloc kecuali Anda membutuhkannya.
Lelang ini bertumbuh secara linear: setiap penawar tambahan menambah 5 word input, 8 word publik, 2 permutasi untuk pemeriksaan, dan sekitar seratus siklus. Dengan 64 penawar (penawaran acak, 320 word input, 514 word publik), kedua build memilih pemenang dan harga yang sama, dan keduanya membutuhkan tier 14:
auction.bin tier 14: fits (cycles 6557 of 16383, Poseidon2 permutations 403 of 2048)
auction-c.bin tier 14: fits (cycles 8571 of 16383, Poseidon2 permutations 388 of 2048)
Permutasinya akan muat dalam 512 milik tier 12, tetapi barisnya tidak muat dalam 4.095 miliknya. Siklus, bukan hashing, yang menentukan tier di sini. Pada 64 penawar, build Rust lebih cepat. opt-level = 3 miliknya mengungguli -Os tetap milik build C begitu loop mendominasi.
Keccak dan SHA-256
keccak dan sha256_compress masing-masing memakan satu baris, tetapi bukti yang menggunakan salah satunya membawa tabel tambahan. Pada aeacf31 keduanya menerima referensi array, keccak(&mut state) atas [u32; 50] dan sha256_compress(&mut buf) atas [u32; 24], sedangkan 7ef3220 menerima raw pointer. Sejak v0.5.6 sebuah pemanggilan boleh menggunakan paling banyak 128 permutasi Keccak-f dan paling banyak 128 kompresi SHA-256. Pada chain 20, satu permutasi Keccak juga memakan 192 gas dan satu kompresi SHA-256 memakan 64.
Tabel tersebut membuat bukti lebih besar, dan chain 20 membatasi bukti pemanggilan pada 4.194.304 byte (4 MiB, seperti pada chain 17 sampai 19; batasnya 8 MiB pada chain 13 sampai 16). Pada chain 18, pemanggilan tier 12 dengan tabel Keccak berukuran 3,35 MB, hampir mencapai batas, dan pemanggilan tier 14 dengan tabel SHA-256 berukuran 1,91 MB, dibandingkan 1,50 MB tanpa keduanya (Example programs). Setiap KiB dikenai biaya sesuai harga per byte. Pada chain 15, hanya byte di atas 2 MiB + 18.432 yang gratis yang dikenai biaya, sebesar 1.000 unit per KiB (Fees). Gunakan Poseidon2 kecuali Anda harus menyamai hash eksternal.
Memori dan waktu pembuktian
Waktu pembuktian dan memori bertumbuh tajam seiring tier. Pemanggilan tier 10 dibuktikan dalam hitungan detik pada laptop. Pada chain 20, setiap deploy dan setiap pemanggilan juga membawa dua bukti sisi bundle: otorisasi pengeluaran tier 10 (hitungan detik) dan bundle tier 14 yang membayar biaya (sekitar dua menit pada laptop). Keempat pemanggilan yang diukur pada chain 18 masing-masing memakan waktu antara tiga dan lima setengah menit dan mencapai puncak 6,1 sampai 6,6 GB (Example programs). Pada chain 15, bundle berupa satu bukti tier 14 sekitar 100 detik dan sekitar 5,7 GB. Validator and aggregator hardware memuat angka-angka yang diukur.
8. Deploy
Testnet adalah chain 20. Deploy membutuhkan wallet rand dari fullnode v0.6.8 atau lebih baru, sebuah kunci, dan sejumlah RAND (Introduction; faucet chain 20, seperti faucet chain 19, hanya mencetak koin ke 16 wallet yang masuk daftar izin genesisnya). Wallet v0.6.7 tidak dapat mengirim pada chain 20: genesis chain 20 menetapkan binding_domain: 1, sehingga setiap pengikatan bukti dan setiap pesan yang ditandatangani menyertakan hash genesis, yang tidak dimasukkan oleh wallet v0.6.7. Endpoint publik https://rpc.randprotocol.org sudah cukup untuk menyinkronkan wallet, melakukan deploy, dan memanggil (setup). Lelang di-deploy beserta segmen publiknya:
rand fee deploy 258 --public-words 34 # 0.001 + 0.0001 × (258 + 34) = 0.0302 RAND
rand program deploy auction.bin --public auction-public.txt
program deploy mencetak id program, hc, jumlah word, serta jumlah word dan digest dari input publik. Perintah ini memeriksa program terhadap batas chain dan input publik terhadap max_program_public_words (32.768 pada chain 20) sebelum membuktikan apa pun. Setelah itu perintah ini membuktikan bundle transfer-ke-diri-sendiri bernilai nol yang membayar biaya, mengirimkannya, dan menunggu commit. Pada chain 20, seperti sejak chain 17, itu berarti dua bukti, masing-masing dengan baris keluarannya sendiri: otorisasi pengeluaran tier 10 (hitungan detik, sekitar 1,37 MB) dan bundle tier 14 (sekitar dua menit, sekitar 1,5 MB). Pada chain 15, itu berupa satu bukti tier 14 sekitar 100 detik dan sekitar 1,42 MB. Biaya deploy tidak memiliki komponen gas. Lelang ini belum pernah di-deploy pada chain 18 sampai 20; An anonymous vote menunjukkan deploy dengan segmen publik pada chain 18 sebagaimana dicetak oleh wallet.
Id program berubah dengan --public. rand-guest mencetak 148ab0c4…2c73 untuk kodenya saja. Deploy dengan segmen publik disimpan di bawah blake3("rand-program-2", base_pc ‖ len ‖ words ‖ len ‖ public), yang juga mengikat cadangan dan keempat komitmen. Gunakan id yang dicetak program deploy. Lelang kedua dengan kode yang sama tetapi komitmen berbeda adalah id program yang berbeda.
rand program show <auction id> # base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest
code_hash adalah hc yang dicetak rand-guest, dengan penulisan yang berbeda. rand-guest mencetak masing-masing dari delapan word digest dalam big-endian, sedangkan wallet, rand program show, dan rand_getProgram mencetak byte setiap word dalam little-endian, sehingga setiap kelompok delapan digit heksadesimal dibalik urutan byte-nya. Untuk build di atas, aa04e4c4…050d (Rust) muncul sebagai c4e404aa…4f16, dan 68605aa8…bea0 (C) sebagai a85a6068…b69a.
9. Call
Penjual memanggil dengan seluruh 20 word privat:
rand call <auction id> --expect-public auction-public.txt \
--input 400 --input 2654435769 --input 2135587861 --input 4087136352 --input 1559071514 \
--input 250 --input 461845907 --input 3432918353 --input 3864292196 --input 2246822507 \
--input 900 --input 3266489909 --input 668265263 --input 374761393 --input 3550635116 \
--input 300 --input 4252018373 --input 3042594569 --input 1759714724 --input 625341585
Wallet mengambil kode dan segmen publik program, lalu memeriksa bahwa keduanya menghasilkan hash yang sama dengan id. --expect-public juga menolak, sebelum membuktikan, jika segmen publik bukan word-word dari berkas tersebut. Wallet kemudian menjalankan pemanggilan di emulator dan mencetak gas yang tepat, batas yang akan dideklarasikannya, tier (yang terkecil yang muat untuk eksekusi tersebut, di sini tier 10), dan biayanya:
gas: <exact> (declaring <limit>, tier <t>)
fee <amount> RAND (incl. two price steps of headroom)
Wallet menyegel 20 word input dalam sebuah amplop yang hanya dapat dibuka oleh pemanggil, membuktikan eksekusi secara lokal, membayar biaya melalui sebuah bundle, mengirimkan, menunggu, dan mencetak tanda terima. Pada chain 20, itu berarti tiga bukti: pemanggilan, otorisasi pengeluaran tier 10, dan bundle tier 14.
Biaya pemanggilan. Pada chain 15, pemanggilan ini membayar 0.001 + call_fee(10) = 0,002 RAND. Pada chain 20, wallet membayar batas bawah dari bagian 7, yang dihitung sebelum bukti ada, dengan tiga nilai bawaan:
- Batas yang dinyatakan adalah gas yang tepat, dibulatkan ke atas menjadi kelipatan
2^(tier − 2): 256 pada tier 10, 1.024 pada tier 12, 4.096 pada tier 14.--gas-limit maxmenyatakan batas atas tier, dan--gas-limit Nmenyatakan angka yang tepat; - bukti dihargai pada batas maksimum chain sebesar 4.194.304 byte, bukan pada ukuran sebenarnya, karena bukti panggilan pada chain 20 terikat pada transaksinya dan biaya harus ditetapkan lebih dahulu;
- ruang cadangan sebesar dua langkah harga, yaitu faktor 1,125², karena panggilan masuk dua atau tiga blok setelah harganya dihitung.
--fee menimpa nilai bawaan, dan rand fee call <tier> --gas <limit> --bytes <B> meminta node menghitung batas minimumnya sendiri. Panggilan untuk lelang belum dilakukan pada chain 18 sampai 20, sehingga baris gas dan biayanya tidak ditampilkan di sini. Pemungutan suara anonim memiliki panggilan lengkap pada chain 18 sebagaimana dicetak oleh wallet, dengan biaya 0,0062 RAND.
Tanda terima memuat tier, H_IN, H_PUB, dan keluaran [1, 2, 400, 4, 0, 0, 0, 0]: kata-kata yang sama dengan yang dicetak emulator, karena bukti tersebut berasal dari eksekusi yang sama. Keempat penawaran beserta salt-nya tidak berada di chain. --auditor <rand1…> juga menyegel masukan untuk auditor yang ditunjuk, dan --no-envelope sama sekali tidak menerbitkan transkrip, yang tidak dapat dibatalkan (Program).
10. Memeriksa hasil
Seorang penawar, atau siapa pun, dapat memeriksa lelang tanpa harus memercayai penjual:
- Kode. Bangun ulang kode sumber yang dipublikasikan dengan
rand-guest buildpada commit circuits yang sama, Rust 1.98.1 (atau clang 23.1.1). Bandingkanhcdengancode_hashdarirand program showataurand_getProgram, dengan memperhitungkan penulisan dengan urutan byte terbalik (bagian 8). Build ini dapat direproduksi, sehingga keduanya sama di mesin mana pun. - Segmen publik. Ambil dengan
rand_getProgramPublic, atau jalankanrand call --expect-public. Periksa bahwa komitmen Anda sendiri berada pada indeks Anda. - Tanda terima.
rand receipt <tx hash>ataurand_getReceipt: status1, pemenang dan harganya, slot 7 bukan0xdeadbeef, danprogramsama dengan id lelang. - Masukan, jika Anda berhak atasnya.
rand open-call <tx hash>milik penjual (atau--as-auditormilik auditor) membuka masukan tersegel, memeriksanya terhadapH_IN, menjalankan ulang program, dan keluar dengan kode bukan nol jika ada ketidakcocokan. Gunakan wallet v0.6.8: sejak v0.6.8 eksekusi ulang dilakukan atas segmen publik program dan pengikatan panggilan, sehingga berfungsi untuk lelang (fullnode issue #116). Pada v0.6.7,open-callberhenti denganre-running the program on these inputs failed: PublicIndex(0)untuk setiap program yang di-deploy dengan segmen publik.
Bukti tersebut menjamin bahwa aturan diterapkan pada pembukaan komitmen penawaran. Bukti ini tidak memindahkan uang. Pemenang membayar penjual dengan rand send biasa, dan tanda terima menjadi acuan kedua belah pihak.
Pemecahan masalah
| gejala | penyebab dan perbaikan |
|---|---|
build gagal pada pemanggilan poseidon2(…), keccak(…) atau sha256_compress(…) yang ditulis untuk commit yang lebih lama | pada aeacf31 poseidon2 adalah unsafe fn dan dua lainnya menerima &mut [u32; 50] dan &mut [u32; 24] (bagian 3 dan 7) |
check melaporkan Syscall dengan a7 = 7 | guest memanggil poseidon2_len; rand-guest check pada aeacf31 tidak mencantumkan syscall 7 (zkp-circuits issue #1) |
check melaporkan Csr pada 0xc0001073 | kode pustaka dikompilasi menjadi unimp; temukan fungsinya dengan llvm-objdump dan ganti pemanggilannya (bagian 6) |
run menyatakan eksekusi tidak muat di tier mana pun, atau membutuhkan lebih dari tier 14 | terlalu banyak baris atau permutasi Poseidon2; perkecil program, kurangi hashing, atau pecah pekerjaan menjadi beberapa panggilan dengan membawa digest di antaranya |
| slot 7 = 3735928559 | guest Rust mengalami panic; keluaran sebelumnya tidak dapat diandalkan |
trap: DoubleWrite(n) | slot keluaran n ditulis dua kali; arahkan setiap jalur keluar melalui satu fungsi |
trap: InputIndex(i) | program membaca masukan privat yang tidak diberikan oleh pemanggil; baca dan periksa jumlahnya terlebih dahulu |
trap: … pada load atau store | alamat yang tidak sejajar atau di luar jangkauan; pada C, indeks array melampaui batasnya |
invalid value '0x…' for '--input' | kata masukan berupa desimal pada baris perintah |
panggilan ditolak dengan CallTierTooHigh | bukti berada di atas tier 14; periksa dengan rand-guest run --tier 14 sebelum memanggil |
hc hasil build ulang berbeda dari yang di-deploy | commit circuits, toolchain, atau versi dependensi berbeda; kunci ketiganya dan commit Cargo.lock. Jika hanya penulisannya yang berbeda, setiap kelompok delapan digit heksadesimal urutan byte-nya terbalik (bagian 8) |
rand open-call berhenti dengan PublicIndex(0) | program memiliki segmen publik dan wallet-nya v0.6.7, yang eksekusi ulangnya tidak meneruskan apa pun (fullnode issue #116); gunakan v0.6.8 |
… is not inside a circuits checkout | pindahkan direktori guest ke dalam checkout (Deploy program Rust) |