Przeczytaj tę stronę, zanim napiszesz program dla Rand. Mówi, na jakiej maszynie działa Twój kod, co odrzuca toolchain, jak program komunikuje się ze światem zewnętrznym i jaki plik wdrażasz. Kolejne strony z niej korzystają: Wdrażanie programu w Rust, Wdrażanie programu RISC-V oraz dwa transpilatory.
Maszyna
Programy Rand to RV32IM: 32-bitowy bazowy zestaw instrukcji całkowitoliczbowych RISC-V oraz rozszerzenie mnożenia i dzielenia. Działają wewnątrz Rand zkVM, który dowodzi każde uruchomienie wsadowym dowodem STARK z Plonky3. Weryfikator nigdy nie widzi żadnej instrukcji. Sprawdza dowód względem hc, czyli obwodowego skrótu Poseidon2 programu.
| grupa | instrukcje |
|---|---|
| górne / skoki | LUI AUIPC JAL JALR |
| rozgałęzienia | BEQ BNE BLT BGE BLTU BGEU |
| odczyty / zapisy | 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 |
| systemowe | tylko ECALL |
JALR zeruje bit 0 swojego celu, zgodnie z RV32I, a jego kodowanie musi mieć funct3 = 0. Obie reguły pojawiły się wraz z zestawem ograniczeń 7 (chain 16); wcześniej nieparzysty cel nie mógł zostać pobrany, a uruchomienia nie dało się udowodnić. Cel parzysty, ale niebędący wielokrotnością czterech, nadal nie może zostać pobrany.
Nie ma liczb zmiennoprzecinkowych, operacji atomowych (rozszerzenie A), instrukcji skompresowanych (rozszerzenie C) ani rejestrów 64-bitowych. Dzielenie 64-bitowe w C staje się wywołaniem programowej funkcji pomocniczej; zob. ścieżka C.
Co jest odrzucane
rand-guest check przepuszcza każde słowo tekstu przez własny dekoder maszyny. Program, który przejdzie kontrolę, nie może zawieść w obwodzie z powodu kodowania, numeru wywołania systemowego ani układu. Weryfikator odrzuca:
| wynik | co oznacza |
|---|---|
Compressed | 16-bitowe kodowanie RVC; maszyna dekoduje tylko słowa 32-bitowe |
Undecodable | kod operacji, funct lub wielkość przesunięcia, których maszyna nie implementuje |
Fence | FENCE lub FENCE.I; maszyna nie ma porządkowania pamięci |
Csr | dowolna instrukcja CSR; maszyna nie ma rejestrów CSR |
Ebreak | EBREAK; pułapki nie są modelowane |
Syscall | ecall, którego a7 jest ustawiane przez li tuż przed nim i nie nazywa żadnego wywołania systemowego |
Cap | więcej słów niż --max-words, liczonych tak, jak liczy je loader |
Layout | baza tekstu niewyrównana do słowa |
Czterosłowowy program z fence, ebreak i ecall o numerze 9, tak jak raportuje go rand-guest check (kod wyjścia 1):
$ rand-guest check bad.elf
note: the loader refuses this image (Decode { index: 0, word: 267386895, err: Opcode(15) }), so its data prologue could not be counted; the word count below is the text alone
0x00001000 0x0ff0000f Fence: FENCE/FENCE.I: the machine has no memory ordering instructions
0x00001004 0x00100073 Ebreak: EBREAK: traps are not modelled
0x0000100c 0x00000073 Syscall: ecall with a7 = 9, which is no syscall the machine implements
4 words against a cap of 4096 (fits); 0 ecall(s) with a non-static a7
REJECTED
ecall, którego a7 kontroler nie widzi (ustawianego czymkolwiek poza li lub po rozgałęzieniu), jest zliczany jako unresolved_ecalls, a nie odrzucany. Maszyna nadal przerywa działanie przy błędnym numerze w czasie wykonania.
Kontroler nie sprawdza wartości wskaźników, długości ani indeksów wejść, 64 KiB stosu względem RAM ani wyrównania adresów obliczanych w czasie wykonania. Niewyrównany dostęp jest naruszeniem ograniczeń, dlatego toolchain kompiluje z -unaligned-scalar-mem. rand-guest run zgłasza każdą pułapkę w czasie wykonania.
Wywołania systemowe
ECALL odczytuje numer wywołania systemowego z a7, pierwszy argument z a0, a drugi z a1. Wartość wraca w a0.
| # | wywołanie | działanie | gas | Rust (guest-sdk) | C (guest.h) |
|---|---|---|---|---|---|
| 0 | HALT | kończy uruchomienie | 1 | halt() | rand_halt() |
| 1 | WRITE_OUTPUT slot word | ustawia publiczne wyjście slot < 8, co najwyżej raz na slot; niezapisane sloty są zerowe | 1 | write_output(slot, word) | rand_write_output |
| 2 | READ_INPUT idx | zwraca prywatne słowo wejściowe idx, związane z solonym zobowiązaniem H_IN | 1 | read_input(idx) | rand_read_input |
| 3 | POSEIDON2 ptr n | haszuje n ≤ 4096 słów pod adresem słowa ptr i zapisuje 8-słowowy skrót w miejscu ptr..ptr+8; bez dopełnienia | 3 + 3·⌈n/4⌉ | unsafe poseidon2(ptr, n) | rand_poseidon2 |
| 4 | KECCAK ptr | jedna permutacja Keccak-f[1600] w miejscu na 50 słowach | 192 | keccak(state), keccak256(msg) | rand_keccak |
| 5 | SHA256 ptr | jedna kompresja SHA-256 w miejscu na 24 słowach | 64 | sha256_compress(buf), sha256(msg) | rand_sha256_compress |
| 6 | READ_PUBLIC idx | zwraca słowo idx segmentu publicznego, związane z niesolonym H_PUB | 1 | read_public(idx) | rand_read_public |
| 7 | POSEIDON2_LEN ptr n | POSEIDON2 z długością n związaną w gąbce, co najmniej jedna permutacja (zestaw ograniczeń 7) | 3 + 3·max(⌈n/4⌉, 1) | poseidon2_len(buf, n) | brak |
Każda inna instrukcja kosztuje 1 gas: gas to jeden na wiersz cpu, a więcej ważą tylko wywołania haszujące (zestaw ograniczeń 8, od chain 18). Wywołanie Poseidon2 to jego wiersz ecall, dwa wiersze zapisu zwrotnego i ⌈n/4⌉ wierszy absorpcji po 3 gas każdy.
POSEIDON2 nie dopełnia. Gąbka nadpisuje swoje pierwsze linie każdym czterosłowowym fragmentem, więc [a], [a, 0], [a, 0, 0] i [a, 0, 0, 0] dają jeden skrót. Program, który haszuje dane o zmiennej długości, musi umieścić długość w komunikacie, haszować kodowanie o stałej długości albo wywołać POSEIDON2_LEN, które wprowadza n do pojemności gąbki i daje inny skrót tych samych słów. W Rust SDK poseidon2 to unsafe fn operująca na surowym wskaźniku, a poseidon2_len to bezpieczne wywołanie na wycinku; keccak i sha256_compress przyjmują odpowiednio &mut [u32; 50] i &mut [u32; 24].
Jedna luka w toolchainie. W circuits aeacf31 maszyna, emulator i Rust SDK implementują wywołanie systemowe 7, ale rand-guest check go nie implementuje: jego lista wywołań kończy się na 6, więc zgłasza ecall z a7 = 7 jako wynik Syscall, a rand-guest build kończy się niezerowym kodem dla guesta wywołującego poseidon2_len (circuits issue #1). guest.h również nie ma dla niego opakowania. Dopóki to nie zostanie naprawione, guest zbudowany standardowym narzędziem używa POSEIDON2 na komunikatach o stałej długości.
Dowód publikuje dokładnie to: hc, H_IN, H_PUB, tier, zadeklarowany limit gas oraz osiem słów wyjściowych. W łańcuchu obowiązują dziś dwa ograniczenia:
- Segment publiczny jest ustalany przy wdrożeniu. W chain 20
rand program deploy --publicustawia publiczne słowa programu (co najwyżejmax_program_public_words, 32 768), a każde wywołanie jest sprawdzane względem nich. Nie ma publicznych słów własnych programu dla pojedynczego wywołania. Chain 20, podobnie jak chain 19, dołącza do segmentu każdego wywołania po tych słowach osiem słów, czyli powiązanie wywołania, które wiąże dowód z jego transakcją (w chain 20 zawiera ono także skrót genesis); guest odczytuje własne słowa pod tymi samymi indeksami co wcześniej.InvokeRPL-2 dołącza po powiązaniu zadeklarowane przejście, które program odczytuje odpublic_len + 8(Programy). W łańcuchu bez pól limitów, takim jak chain 12, segment publiczny jest pusty, aREAD_PUBLICnie ma czego czytać. - Prywatne słowa wejściowe wywołania są ograniczone przez zapieczętowaną kopertę.
max_call_envelope_bytesw chain 20, równe 65 536, pozwala na 16 071 słów; łańcuch bez tego pola pozwala na 4 295.
Wywołanie KECCAK lub SHA256 dodaje do dowodu tabelę, a nagłówek dowodu pokazuje wtedy, że wywołanie haszowało. Dowód z keccakiem ma około 3,3 MB: więcej niż domyślny limit dowodu 2 MiB, mniej niż 4 MiB w chain 20, a wywołanie może użyć co najwyżej 128 permutacji Keccak-f i 128 kompresji SHA-256 (Programy).
Pamięć
- 32 rejestry;
x0jest zerem. - RAM zaczyna się od zera. Każdy adres jest poniżej
2³⁰. - Domyślny skrypt linkera,
guest-sdk/guest.ld, umieszcza program pod0x1000, potem.rodata,.datai.bss, a następnie 64 KiB stosu pod__stack_top. - Program z segmentem danych potrzebuje miejsca poniżej swojego tekstu na prolog loadera (następna sekcja). Jego własny
.ldpodnosiORIGINdo0x10000, tak jak robią toevm.ldisbpf.ld.
Kontener obrazu
rand-guest build i rand-guest pack zapisują jeden format pliku, kontener obrazu. Wszystkie liczby to 32-bitowe słowa little-endian:
[0x444e4152 ("RAND"), 1, text_base, n_text, data_base, n_data] text… data…
Loader zamienia kontener w program { base_pc, words }:
- Zapisuje parę
li/swdla każdego niezerowego słowa danych. Słowa zerowe są pomijane, ponieważ RAM i tak odczytuje zero. - Umieszcza ten prolog bezpośrednio poniżej
text_base, więcbase_pc = text_base − 4 × prologue words. Prolog przechodzi dalej w własny_startprogramu. hci identyfikator programu są obliczane na całymwords, łącznie z prologiem. Zatemhcwiąże zarówno dane, jak i kod.
Kontener bez danych nie daje prologu i daje ten sam program oraz hc co płaski plik binarny bez nagłówka pod tą samą bazą.
rand program deploy w fullnode v0.3 i nowszych rozpoznaje kontener po pierwszym słowie. 0x444e4152 nigdy nie dekoduje się jako instrukcja, więc surowego .bin nie da się pomylić z kontenerem. Plik, który zaczyna się od magicznej wartości, ale jest źle sformowany, jest błędem, a nie powodem do cofnięcia się do wariantu bez nagłówka.
Dwie tożsamości
| nazwa | czym jest | kto z tego korzysta |
|---|---|---|
hc | skrót Poseidon2 obwodu dla (base_pc, len, words) | względem niego weryfikowany jest każdy dowód wywołania; rand_getProgram pokazuje go jako code_hash |
| program id | blake3("rand-program" ‖ base_pc ‖ words); wdrożenie z --public wiąże także słowa publiczne, pod znacznikiem rand-program-2 | klucz, pod którym wdrożony program jest przechowywany i wywoływany |
rand-guest build i rand-guest info wypisują oba. hc jest wiążący, ale nie ukrywający: każdy, kto ma kandydujący program, może go potwierdzić.
Limit programu
Wdrożenie może zawierać co najwyżej tyle słów, ile wynosi limit programu łańcucha.
| łańcuch | limit |
|---|---|
genesis bez max_program_words (chain 12) | 4,096 |
genesis z max_program_words | ta wartość, 1..=65535 |
| twardy limit zkVM, dowolny łańcuch | 65,535 |
Limit jest częścią skrótu genesis, więc nie może się zmienić w działającym łańcuchu. rand-guest sprawdza względem --max-words (domyślnie 4,096). rand program deploy pyta węzeł przez rand_estimateFee i odrzuca program przekraczający limit, zanim cokolwiek udowodni.
Chain 20, działający łańcuch, podnosi limit. Jego skrót genesis to 6210cf071a390d7ac61d8cbea5dd9d139d1a493a862b54a45a498f36af2d5135, a jego max_program_words wynosi 65,535. Zachowuje limity wprowadzone przez chain 13 (łańcuch v0.4), z wyjątkiem limitu dowodu, który łańcuchy od 17 do 20 zmniejszają o połowę. Jego pozostałe limity:
| limit | chain 20 |
|---|---|
max_proof_bytes | 4,194,304 (4 MiB); 8 MiB w łańcuchach od 13 do 16 |
max_block_bytes | 20,971,520 (20 MiB) |
max_call_envelope_bytes | 65,536 |
max_program_public_words | 32,768 |
W chain 20, podobnie jak w chain 19, limit słów nie jest limitem wiążącym. Jego genesis ustawia hardening_v6, zgodnie z którym wdrożenie jest odrzucane (ProgramUncallable), gdy żadne wywołanie nie mogłoby nigdy udowodnić programu. Wywołanie jest ograniczone do tieru 14, którego tabela Poseidon2 mieści 2,048 permutacji. Skrót programu zużywa jedną na cztery słowa, wiersz soli wejścia jedną, a osiem słów wiążących wywołanie dwie, co zostawia 2,045: 8,180 słów dla programu bez wejścia publicznego i mniej przy nim (8,168 przy dziewięciu słowach publicznych). Pula każdego węzła odrzuca takie wdrożenie w dowolnym łańcuchu.
Agregacja bloków jest w chain 20 wyłączona: jego genesis nie ma sekcji aggregation. Chain 20 działa od 2026-10-01 06:17 UTC na fullnode v0.6.8, jako hard fork wycięty ze stanu chain 19 na wysokości 7,258 (chain 19 zatrzymał się na 7,700); chain 19 (działający od 03:43 UTC tego samego dnia) był sam re-genesis chain 18 na wysokości 141,935. Programy nie przechodzą przez cięcie, więc żaden program wdrożony w wcześniejszym łańcuchu nie istnieje w chain 20. Chain 20 jest też pierwszym łańcuchem ze stanem programów RPL-2 (Programs). Działa w sieci przez te węzły startowe:
/ip4/164.90.239.200/tcp/30303/p2p/12D3KooWEQEbUwZRgqhDPXhW7vNBmcRcnhUDGUADe81VFdv3ALYe
/ip4/165.245.173.74/tcp/30303/p2p/12D3KooWJyc5oDHggAr89e9QrRFBy9Tg8TxXh18yzrb1SM6KKLYw
/ip4/188.166.235.187/tcp/30303/p2p/12D3KooWSyX84P1DAbWByLHpRBMLRWD53r3Jj4aqVt6tHoF8B2ZQ
| obraz | słowa | mieści się w 4,096 | mieści się w 65,535 | wdrażany w chain 20 (8,180 wywoływalnych) |
|---|---|---|---|---|
fib (Rust) | 26 | tak | tak | tak |
c-fib (C) | 25 | tak | tak | tak |
| ERC-20, przetłumaczony (etap drugi) | 11,686 | nie | tak | nie |
evm.bin, interpreter EVM | 18,009 | nie | tak | nie |
sbpf.bin, interpreter sBPF | 8,317 | nie | tak | nie |
| SPL Token, przetłumaczony | 65,096 | nie | tak, z zapasem 439 | nie |
Tiery
Tier dowodu t ogranicza przebieg do 2ᵗ − 1 wierszy: cykle plus wiersze skrótów (jeden na cztery słowa programu, jeden na cztery słowa wejścia oraz skrót wejścia publicznego). Tier wyznacza też osobny budżet Poseidon2. rand-guest run --tier t podaje oba:
tier 10: fits (cycles 146 of 1023, Poseidon2 permutations 10 of 128)
Tier jest jawny, podobnie jak limit gazu deklarowany przez dowód. W chain 20 opłata za wywołanie zależy od tego limitu i rozmiaru dowodu, nie od tieru: co najmniej 0.001 RAND + gas_price × gas_limit + byte_price × ⌈bytes / 1024⌉, według bieżących cen łańcucha, które zaczynają się od 100 i 800 jednostek 10⁻⁹ RAND, zmieniają się wraz z zapełnieniem bloków, a w chain 20 zatrzymują się na 10,000 i 80,000 jednostek (Fees). Limit nie może przekraczać pułapu nagłówka, (2ᵗ − 1) + 2ᵗ⁻² plus 191 na blok Keccak i 63 na blok SHA-256 deklarowane przez dowód: 1,279 w tierze 10 i 20,479 w tierze 14 bez tabeli skrótów. rand-guest run podaje cykle i permutacje, nie gaz. rand call najpierw uruchamia program i wypisuje zużyty gaz oraz deklarowany limit, domyślnie dokładny gaz zaokrąglony w górę do następnej wielokrotności 2ᵗ⁻², zanim zacznie dowodzić:
gas: 5636 (declaring 6144, tier 12)
Ta linia pochodzi z wywołania w chain 18 z 2026-09-30 (An anonymous vote). W łańcuchu bez sekcji gas w genesis, czyli chain 15 i wcześniejszych, opłata była harmonogramem tierów: 0.001 + (0.001 + 0.0001 × (t − 10) / 2) RAND. Pamięć potrzebna do dowodu rośnie gwałtownie wraz z tierem; zob. Validator and aggregator hardware.
Od fullnode v0.5.6 (2026-09-25) wywołanie może deklarować co najwyżej tier 14: węzeł odrzuca dowód wywołania z wyższym tierem z CallTierTooHigh, zanim zbuduje klucz weryfikatora, a blok go zawierający jest nieprawidłowy. Budżet tieru 14 to 16,383 wierszy i 2,048 permutacji Poseidon2, a skrót samego programu zużywa około jednej permutacji na cztery słowa, więc program powyżej 8,180 słów nie może być wywołany, a chain 20, podobnie jak chain 19, odrzuca jego wdrożenie. Write a program in Rust or C omawia ten budżet krok po kroku.