Te cztery programy to kompletne zastosowania poufnego wywołania: problem, interfejs, gość w Rust, narzędzia po stronie hosta oraz wdrożenie i wywołanie w działającej sieci testnet. Są dłuższe niż samouczki, a każdy opiera się na innej części maszyny. Napisz program w Rust lub C to przewodnik po modelu, z którego korzystają wszystkie. Przeczytaj go najpierw, jeśli read_input, segment publiczny lub osiem slotów wyjściowych są dla Ciebie nowe.
Wszystko tutaj uruchomiono po raz pierwszy w chain 18 z fullnode v0.6.7, 2026-09-30, a każde wyjście na tych stronach pochodzi z tych uruchomień. Programy nie przechodzą przez zmianę łańcucha, więc 2026-10-01 wszystkie cztery wdrożono ponownie, bez zmian, w chain 19, a potem w chain 20 (fullnode v0.6.8), za każdym razem powtarzając wywołania. Identyfikator programu jest skrótem kodu i segmentu publicznego, a nie łańcucha, więc każdy identyfikator jest taki sam; skróty transakcji w zapisach przebiegu pochodzą z chain 18, a poniższa tabela odsyła do transakcji z chain 20. W chain 20 program może też przechowywać stan między wywołaniami (RPL-2); te cztery z tego nie korzystają, a programy, które to robią, opisują Stan programu oraz repozytorium example-programs.
Cztery programy
| program | co dowodzi wywołanie | opiera się na | słowa | poziom |
|---|---|---|---|---|
| Anonimowe głosowanie | wywołujący należy do opublikowanej listy, nie mówiąc do której, i nie głosował wcześniej | Keccak-256, segment publiczny | 427 | 12 |
| Dowód zobowiązań | prywatne salda klientów sumują się do nie więcej niż opublikowana rezerwa, pod korzeniem, który każdy klient może sprawdzić | SHA-256, drzewo budowane wewnątrz programu | 994 | 14 |
| Księga poza łańcuchem | partia przelewów przenosi księgę z jednego stanu do następnego, przy czym każdy nadawca jest uprawniony i nic nie powstaje z niczego | Poseidon2, stan przenoszony między wywołaniami | 773 | 14 |
| Prywatna decyzja kredytowa | opublikowany model zatwierdza lub odrzuca prywatne dane wnioskodawcy, za które poręczył atestator | arytmetyka całkowitoliczbowa, model w segmencie publicznym | 462 | 12 |
Wszystkie cztery działają w chain 20. Każde wywołanie w tabeli powtarza, z tymi samymi danymi wejściowymi, wywołanie z chain 18 pokazane na stronie danego programu:
| program | identyfikator programu | wdrożony w chain 20 w | pierwsze wywołanie w chain 20 |
|---|---|---|---|
| anonimowe głosowanie | ad2f719f12d01c9f3c0962092b9898b75b0914bbb817031d51be13e54c05b970 | f6ae5528…0f82 | 3a304e7a…427b |
| dowód zobowiązań | fce8474dfdf80b9733e04fb72d4d8d19c40ba5ab646b3d867322daf4b9b599c0 | a8bc281b…17d9 | 6116c15f…d62a |
| księga poza łańcuchem | c4ddd388f2d94299610c7c3409dfdfb4dcb4ae3e0bc9ec78d0c45f51d8231cab | 72cc95f2…3d51 | 9c9998bb…66eb |
| prywatna decyzja kredytowa | 7bbec14289f4446231b3a9a5ac18cb0613a373ca8a14fe4f046f3fde7ae6c73c | 413e6658…e42b | d3ceb306…124a |
Eksplorator wyświetla każdy program i jego wywołania pod adresem randscan.org/programs. Łańcuch testnet jest co jakiś czas zastępowany nowym, a programy nie przechodzą dalej: po następnej zmianie tych identyfikatorów już nie będzie i programy trzeba będzie wdrożyć ponownie.
Konfiguracja
Toolchain. Goście budują się za pomocą rand-guest z repozytorium circuits, w commicie, który fullnode v0.6.7 i v0.6.8 oba wendorują:
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"
Commit ma znaczenie z dwóch powodów. hc jest funkcją źródła i toolchainu, więc gość zbudowany ponownie w innym commicie guest-sdk może dostać inny hc. Ponadto sygnatury SDK się zmieniły: w aeacf31 funkcje keccak i sha256_compress przyjmują referencje do tablic, tam gdzie starsze commity przyjmowały surowe wskaźniki.
Źródła. Wszyscy czterej goście, ich skrypty hosta i dane demonstracyjne to jedno pobranie. Rozpakuj je wewnątrz checkoutu, ponieważ rand-guest build znajduje guest-sdk, szukając w górę od katalogu gościa:
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
Pliki można też przeglądać pojedynczo pod adresem https://randprotocol.org/examples/programs/, na przykład allowlist/src/main.rs.
Portfel. Wdrażanie i wywoływanie wymagają portfela rand z fullnode v0.6.8 (cargo build --release -p randprotocol-client w checkoucie fullnode na tagu v0.6.8 albo rand dołączony do wydania v0.6.8), klucza i RAND. Starszy portfel nie może wykonywać transakcji w chain 20: chain 20 wiąże każdy dowód i każdą podpisaną wiadomość ze swoim genesis, a portfel v0.6.7 tego nie robi.
RPC. Publiczny punkt końcowy, https://rpc.randprotocol.org, wystarcza do wszystkiego na tych stronach: czytania programów i potwierdzeń, synchronizacji portfela, wdrażania i wywoływania. Pierwsza synchronizacja portfela czyta każdy nagłówek bloku i każdy blok zawierający note, co przy wysokości chain 18 z 2026-09-30 dawało 645 żądań i trwało około trzech minut przez ten punkt końcowy. Punkt końcowy dopuszcza dla każdego adresu 1,200 odczytów i 6 wysłań na minutę. Własny węzeł (Uruchamianie węzła) nie ma takich limitów.
export RAND_RPC=https://rpc.randprotocol.org
export RAND_KEY=$HOME/wallet.key.json
W chain 18 jeden z głosów policzonych na stronie anonimowego głosowania, fdbcc960…fc2b, został dowiedziony na laptopie i wysłany wyłącznie przez publiczny punkt końcowy. W chain 19 i 20 tą drogą poszło za każdym razem wszystkie trzynaście transakcji: cztery wdrożenia i dziewięć wywołań.
Co chain 18 zmienił dla programu
Wywołanie deklaruje limit gas i za niego płaci. Zestaw ograniczeń 8 mierzy każde uruchomienie. Zwykły cykl to 1 gas. Wyjątkiem są syscalle skrótów:
| instrukcja | gas |
|---|---|
dowolna instrukcja RV32IM, HALT, WRITE_OUTPUT, READ_INPUT, READ_PUBLIC | 1 |
POSEIDON2 na n słowach | 3 + 3 × ⌈n / 4⌉ |
SHA256, jedna kompresja | 64 |
KECCAK, jedna permutacja | 192 |
Dowód niesie jedną nową wartość publiczną, zadeklarowany limit, a obwód wymusza, by gas uruchomienia był nie większy od niego. Dokładny gas pozostaje prywatny. rand call najpierw uruchamia program w emulatorze i wypisuje oba:
gas: 5636 (declaring 6144, tier 12)
Domyślnie portfel zaokrągla dokładny gas w górę do wielokrotności 2^(tier − 2), czyli 1,024 na poziomie 12 i 4,096 na poziomie 14, więc zadeklarowany limit niewiele mówi o uruchomieniu. --gas-limit max deklaruje pułap poziomu i nie mówi nic poza samym poziomem. --gas-limit N deklaruje dokładną wartość.
Opłata zależy od zadeklarowanego limitu i od bajtów.
floor = 0.001 RAND + gas price × declared limit + byte price × ⌈bytes / 1024⌉
Ceny zaczynały od 100 jednostek za gas i 800 jednostek za KiB (jednostka to 10⁻⁹ RAND) i z każdym blokiem przesuwają się nieco w stronę wartości docelowej. rand_getLimits podaje bieżące. Portfel dodaje zapas w wysokości dwóch kroków ceny, czyli współczynnik 1.125², ponieważ wywołanie trafia do łańcucha dwa lub trzy bloki po wycenie.
Jeden szczegół rozstrzyga o większości opłaty. Od chain 18 dowód wywołania jest związany z własną transakcją, więc portfel musi ustalić opłatę, zanim dowód powstanie, i wycenia dowód według limitu łańcucha wynoszącego 4 MiB zamiast jego rzeczywistego rozmiaru. Dla anonimowego głosowania daje to (1,000,000 + 100 × 6,144 + 800 × 4,097) × 1.125² = 6,191,437 jednostek, czyli 0.0062 RAND, z czego gas stanowi około jednej ósmej. Cztery wywołania na tych stronach kosztowały od 0.0057 do 0.0075 RAND każde. --fee nadpisuje wartość domyślną.
Każdy pakiet to dwa dowody. Chain 18 stosuje podzieloną autoryzację: pakiet, który płaci opłatę, niesie mały dowód autoryzacji poziomu 10 obok dowodu pakietu poziomu 14. Portfel tworzy oba i wypisuje po jednym wierszu dla każdego. Wywołanie to zatem trzy dowody: wywołania, autoryzacji i pakietu. Na laptopie z Apple Silicon cztery wywołania tutaj zajęły od trzech do pięciu i pół minuty każde, przy szczytowym zużyciu pamięci od 6.1 do 6.6 GB. Dowód pakietu to za każdym razem około dwóch minut z tego czasu. Dowód wywołania poziomu 12 dodaje pół minuty, a poziomu 14 dwie minuty.
Limity, w które musi się zmieścić wywołanie.
| limit | chain 18 do 20 |
|---|---|
| poziom | co najwyżej 14: 16,383 cykli, wraz z wierszami skrótów |
| permutacje Poseidon2 | 2,048 na poziomie 14, wraz ze skrótami programu, danych wejściowych i danych publicznych |
| permutacje Keccak | 128 |
| kompresje SHA-256 | 128 |
| dowód wywołania | 4,194,304 bajtów (4 MiB) |
| program | 8,180 słów bez segmentu publicznego, kilka mniej z nim: chain 20, tak jak chain 18 i 19, odrzuca wdrożenie, którego nie zmieściłoby żadne wywołanie poziomu 14 (ProgramUncallable), niezależnie od tego, na co pozwala max_program_words (65,535) |
| segment publiczny | 32,768 słów, ustalone przy wdrożeniu |
| dane prywatne z zapieczętowanym transkryptem | 16,071 słów |
rand-guest run --tier 14 sprawdza dwa pierwsze limity przed wdrożeniem. Tablica skrótów powiększa dowód. Dowód księgi poziomu 14, bez tablicy skrótów, ma 1.50 MB. Tablica SHA-256 podnosi dowód zobowiązań do 1.91 MB, a tablica Keccak podnosi dowód głosowania do 3.35 MB, czyli prawie do limitu 4 MiB.
Konwencje wspólne dla wszystkich czterech
- Slot 0 to status, a 0 nigdy nie jest jego prawidłową wartością. Każdy wynik, także porażka, ma numer od 1 w górę, a porażka kończy działanie czysto, tak aby dało się ją udowodnić. Wyzerowanego w całości receipt nie da się pomylić z wynikiem.
- Slot 7 pozostaje nietknięty. Guest napisany w Rust, który wpada w panikę, zapisuje tam
0xdeadbeef(3 735 928 559). Czytelnik sprawdza najpierw slot 7. - Sloty od 2 do 6 niosą 160-bitową wartość: nullifier, korzeń, powiązanie, zobowiązanie. Pełny 256-bitowy skrót to osiem słów i nie zostawiłby miejsca na status. Obcięcie do pięciu słów zachowuje 80 bitów odporności na kolizje, a każda strona mówi, co ta odporność chroni.
- Kolejność bajtów wynika z funkcji skrótu. Wartości Keccak to słowa little-endian, a wartości SHA-256 to słowa big-endian, ponieważ tak każdy syscall odczytuje swój blok. Skrypt hosta i guest, które różnią się w tej kwestii, dają skróty, które po prostu nigdy się nie zgadzają.
- Wszystko, co zmienia się między instancjami, jest w segmencie publicznym; wszystko, co zmienia się między wywołaniami, jest wejściem prywatnym. Korzeń głosowania, kwota rezerw i wagi modelu są ustalone przy wdrożeniu i związane z identyfikatorem programu. Wywołanie nie niesie własnych słów publicznych.
- Słowa wejściowe w wierszu poleceń są dziesiętne. Strony zamieniają plik świadka we flagi za pomocą
$(sed 's/[0-9][0-9]*/--input &/g' witness.txt).
Wywołaj program samodzielnie
Gdy źródła są rozpakowane, toolchain zbudowany, a portfel v0.6.8 zasilony, głosowanie jako jeden z członków demonstracyjnych to cztery polecenia:
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)
Wywołanie nie wymaga obrazu: rand call pobiera z węzła kod programu i jego segment publiczny, sprawdza, czy ich skrót odpowiada identyfikatorowi, i generuje nad nimi dowód.
Odczyt wyniku nie wymaga ani portfela, ani węzła. Każdy receipt to jedno żądanie do publicznego punktu końcowego:
$ 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 eksploratora podaje wywołania programu w jednym żądaniu: https://randscan.org/api/v1/programs/<program id> zwraca call_count i recent_calls (Explorer API).
Czego żaden z nich nie robi
Program w Rand to dowód dotyczący obliczenia, a łańcuch nie działa na podstawie żadnego z jego wyników. Żaden z tych programów nie przenosi RAND ani tokena, nie przechowuje salda i nie pamięta poprzedniego wywołania. Głosy zlicza ten, kto czyta receipty. Zobowiązania są porównywane z rezerwami, które wymagają własnych dowodów. Salda księgi to liczby w drzewie poza łańcuchem. Decyzja kredytowa to bit, który pożyczkodawca sam wybiera, czy uszanować. Każda strona kończy się listą tego, co jej dowód pozostawia osobom z niego korzystającym.