Auf dieser Seite bringen Sie ein Programm auf die Chain und rufen es auf: was ein Programm ist, wie man eines baut, was ein Aufruf veröffentlicht und was er verbirgt, wie man einen Beleg zurückliest und auf welche Grenzen Sie stoßen. Jedes Programm ist öffentlicher Code; jeder Aufruf ist ein Beweis. Beispielprogramme enthält vollständige Anwendungen, und randprotocol/example-programs enthält den Quellcode der auf Chain 20 deployten Programme, darunter die RPL-2-Programme (unten).
Was ein Programm ist
Ein Programm ist RV32IM-Maschinencode für die Rand zkVM: { base_pc: u32, words: Vec<u32> }, höchstens so lang wie das Programmlimit der Chain (65.535 Wörter auf Chain 20, wie auf jeder Chain seit Chain 13; 4.096 bei einem Genesis ohne max_program_words, etwa Chain 12), base_pc ein Vielfaches von 4, jedes Wort als Instruktion dekodierbar (ein Wort, das sich nicht dekodieren lässt, wird beim Deploy abgelehnt, nie zur Laufzeit). Chain 20 verweigert, wie Chain 19, außerdem das Deploy eines Programms, das kein Aufruf fassen könnte: mehr als 8.180 Wörter ohne öffentliches Segment, weniger mit einem (unten). Es ist inhaltsadressiert und unveränderlich:
program_id = blake3("rand-program" || base_pc || words) // the id, with no public input
program_id = blake3("rand-program-2", base_pc || u32_le(len(words)) || words || u32_le(len(public)) || public) // deployed with --public
code_hash = hc = Poseidon2 digest of (base_pc, len, words) // what calls are verified against
Ein Aufruf ist ein STARK-Beweis dafür, dass das Programm mit dem Digest hc auf privaten Eingaben lief, nicht mehr als ein deklariertes Gaslimit verbrauchte und acht Ausgabewörter veröffentlichte. Der Node verifiziert den Beweis, speichert einen Beleg, und mehr geschieht nicht: Ausgaben sind Daten, keine Effekte. Ein Aufruf kann kein RAND bewegen; den Wert bewegt der Aufrufer im Bundle, das den Aufruf bezahlt, und die Ausgaben eines Programms bedeuten, was Programm und Aufrufer untereinander als ihre Bedeutung vereinbaren. Auf Chain 20 ist ein Invoke die Ausnahme: Sein Beweis bürgt für einen deklarierten Zustandsübergang, den das Ledger anschließend anwendet (Programmzustand (RPL-2)).
Was der Guest kann:
| Gruppe | Instruktionen |
|---|---|
| Upper / Sprünge | LUI AUIPC JAL JALR |
| Verzweigungen | BEQ BNE BLT BGE BLTU BGEU |
| Laden / Speichern | 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 |
| System | ECALL; niemals FENCE, CSRs, EBREAK, die A- oder C-Erweiterung |
Syscalls, nach Nummer in a7:
| # | Syscall | Wirkung |
|---|---|---|
| 0 | HALT | beendet den Lauf |
| 1 | WRITE_OUTPUT slot word | legt die öffentliche Ausgabe slot < 8 fest; jeder Slot höchstens einmal, nicht beschriebene Slots sind null |
| 2 | READ_INPUT idx | liefert das private Eingabewort idx, gebunden an das gesalzene Commitment H_IN |
| 3 | POSEIDON2 ptr n | hasht n ≤ 4096 Wörter an Ort und Stelle mit dem Poseidon2-Sponge |
| 4 | KECCAK ptr | eine Keccak-f[1600]-Permutation an Ort und Stelle; sorgt dafür, dass der Beweis die keccak-Tabelle enthält |
| 5 | SHA256 ptr | eine SHA-256-Kompression an Ort und Stelle; sorgt dafür, dass der Beweis die sha256-Tabelle enthält |
| 6 | READ_PUBLIC idx | liefert das Wort idx des öffentlichen Segments; auf Chain 20 wird das öffentliche Segment beim Deploy festgelegt (rand program deploy --public); bei einem Genesis ohne max_program_public_words ist es leer, und der Syscall liest nichts |
| 7 | POSEIDON2_LEN ptr n | POSEIDON2, wobei die Nachrichtenlänge in den Sponge gebunden wird, sodass [a] und [a, 0] unterschiedlich gehasht werden (Constraint-Set 7, Chain 16 und später). rand-guest check bei Circuits aeacf31 weist ihn noch zurück (nächster Abschnitt) |
Speicher: 32 Register, x0 ist null; RAM, der bei null beginnt; jede Adresse unterhalb von 2³⁰; ein 64-KiB-Stack für kompilierte Guests. Ein mit rand-guest gebautes Programm trägt seine schreibgeschützten und initialisierten Daten im Image-Container, und ein Loader-Prolog, der Teil des Programms ist, schreibt diese Daten, bevor der eigene Code des Programms läuft (Die RISC-V ISA). Note-Commitments, Nullifier und Merkle-Prüfungen sind keine Syscalls, sondern Bibliotheksroutinen über POSEIDON2.
Seit Chain 18 (Constraint-Set 8) wird jeder Lauf in Gas gemessen. Eine gewöhnliche Instruktion sowie die Syscalls 0, 1, 2 und 6 kosten je 1 Gas, POSEIDON2 über n Wörter kostet 3 + 3 × ⌈n / 4⌉, eine KECCAK-Permutation kostet 192 und eine SHA256-Kompression 64. Beispielprogramme enthält die Tabelle und beschreibt, was ein Aufruf deklariert.
Ein Programm bauen
Drei Wege, alle erzeugen RV32IM-Wörter:
Der eingebaute Assembler. Das Wallet liefert fünf handassemblierte Guests mit und assembliert sie zu einer JSON-Datei { "base_pc", "words" }:
rand program build --guest private_payment --arg 1000 --out pp.json # prints the program id
rand program build --guest balance_check --arg 1000 --out bc.json
# guests: fib, memcpy, bubble_sort, balance_check, private_payment, public_echo, rpl2_counter (v0.6.8); --arg is repeatable
Kompilierte Programme mit rand-guest. Das rand-guest des Circuits-Repositorys baut ein no_std-Rust-Crate (riscv32im-unknown-none-elf, Rust 1.98.1) oder ein C-Verzeichnis (clang 23.1.1) zu einem Image-Container, prüft jedes Wort gegen den Dekoder der Maschine und gibt hc und die Programm-ID aus. Der Build ist hermetisch, sodass derselbe Quellcode auf jeder Maschine dasselbe hc ergibt. Für die Chains 18 bis 20 bauen Sie die Toolchain am Circuits-Commit aeacf31 (Branch feat/v067-circuits), den Fullnode v0.6.7 und v0.6.8 beide festschreiben. Bei diesem Commit listet rand-guest check nur die Syscalls 0 bis 6, sodass ein Guest, der guest_sdk::poseidon2_len (Syscall 7) aufruft, beim Build abgelehnt wird, obwohl die Maschine ihn ausführt (zkp-circuits Issue #1). Schritt für Schritt: Ein Rust-Programm deployen und Ein RISC-V-Programm deployen.
Übersetzte Programme. sbpf2rv übersetzt ein Solana-sBPF-ELF und evm2rv übersetzt EVM-Runtime-Bytecode in Programme, deren Ausgaben Wort für Wort denen der Interpreter-Guests von Rand entsprechen: Der Solana-Transpiler und Der Ethereum-Transpiler.
Das Wallet selbst hat keinen Compiler. rand program deploy akzeptiert eine .json, eine rohe .bin aus Little-Endian-Wörtern oder den Image-Container, den rand-guest schreibt (Fullnode v0.3 und später).
Deployen
rand program deploy pp.json # a bundle pays the floor; waits for the commit; prints the id
rand program show <id> # id, base_pc, words_len, code_hash, deployed_at, public_words_len, public_digest
Das testnet ist Chain 20, und Deployen, Aufrufen oder Invoken darauf erfordert das rand-Wallet aus Fullnode v0.6.8 oder später. Ein älteres Wallet kann dort überhaupt nicht senden: Das Genesis von Chain 20 setzt binding_domain: 1, sodass jede Beweisbindung und jede signierte Nachricht den Genesis-Hash enthält, während die Bindungen eines v0.6.7-Wallets nur die Chain-ID tragen. Der öffentliche Endpunkt https://rpc.randprotocol.org bedient Sync, Deploys und Aufrufe eines Wallets (Einrichtung).
Das Wallet gibt zuerst die Programm-ID, die Wortanzahl und hc aus und fragt den Node mit rand_estimateFee nach der Gebühr; dieser weist ein Programm über dem Limit der Chain ab, bevor irgendein Beweis erzeugt wird. Dann baut es ein Zero-Value-Self-Transfer-Bundle (das eine 4-in-4-out-Bundle mit verborgenem Asset, das jede Transaktion trägt; die Gebühr wird aus seinen RAND-Slots bezahlt), das 0.001 RAND + 100,000 units × words aus den eigenen Notes zahlt (Wörter des öffentlichen Segments zählen als Codewörter), hängt Action::Deploy { base_pc, words, public } an, beweist das Bundle gegen die Bindung der Transaktion und sendet es ab. Seit Chain 17 sind das zwei Beweise, und das Wallet gibt für jeden eine Zeile aus: eine Tier-10-Spend-Autorisierung (Sekunden, etwa 1,37 MB) und das Tier-14-Bundle (etwa zwei Minuten auf einem Laptop, etwa 1,5 MB). Vor Chain 17 war es der Bundle-Beweis allein, etwa 100 s und etwa 1,42 MB auf Chain 15. Die Deploy-Gebühr hat weder einen Gas- noch einen Byte-Term: Gas bepreist nur Aufrufe. Der Node prüft, dass sich jedes Wort dekodieren lässt, berechnet hc, speichert den Eintrag und beginnt, den Verifier-Schlüssel im Hintergrund zu bauen. Der Code ist öffentlich; wer dafür bezahlt hat, ist es nicht — rand_getProgram hat kein Feld deployer. Dieselben Wörter zweimal zu deployen ist idempotent.
→ {"method":"rand_getProgram","params":["675adeea…0065"]}
← { "id": "675adeea…0065", "base_pc": 0, "words_len": 42, "code_hash": "…", "deployed_at": 10,
"public_words_len": 0, "public_digest": null }
→ {"method":"rand_getProgramCode","params":["675adeea…0065"]}
← { "base_pc": 0, "words": [ … ] }
Aufrufen
rand call <program id> --input 400 --input 250 --input 300 --input 75
rand receipt <tx hash>
Das Wallet holt den Code und das öffentliche Segment vom Node und prüft, dass sie zur ID gehashed werden. Es führt den Aufruf im Emulator aus und gibt Gas und Gebühr aus, bevor es irgendetwas beweist:
gas: 5636 (declaring 6144, tier 12)
fee 0.006191437 RAND (incl. two price steps of headroom)
Anschließend versiegelt es das Eingabe-Transkript (unten), beweist den Aufruf lokal unter dem FRI-Profil der Chain mit den Eingaben als privatem Vektor, beweist das Bundle, das die Gebühr zahlt, sendet ab, wartet und gibt den Beleg aus. Auf Chain 20 ist der Aufrufbeweis an seine eigene Transaktion gebunden (eine Bindung, die den Genesis-Hash enthält), und das Bundle besteht aus zwei Beweisen, sodass ein Aufruf drei umfasst: den Aufruf, die Tier-10-Spend-Autorisierung und das Tier-14-Bundle. Die beiden obigen Zeilen stammen vom Aufruf der anonymen Abstimmung auf Chain 18 am 2026-09-30 (Eine anonyme Abstimmung).
Die Gebühr. Auf Chain 20 beträgt die Untergrenze BUNDLE_BASE + gas_price × declared gas limit + byte_price × ⌈bytes / 1024⌉. Dabei ist BUNDLE_BASE gleich 0.001 RAND, die Bytes sind der Call-Proof plus der Eingabe-Umschlag, und die Preise lagen anfangs bei 100 und 800 Einheiten (eine Einheit sind 10⁻⁹ RAND); sie bewegen sich mit der Blockauslastung. Chain 20 begrenzt sie auf 10,000 und 80,000 Einheiten, und nur die Bytes, die den Byte-Preis zahlen (Proofs und Umschläge der Aufrufe), zählen zu ihrer Last (byte_load: "paying"); auf den Chains 18 und 19 hatten die Preise keine Obergrenze, und die Bytes jeder Transaktion bewegten den Byte-Preis. Der pauschale Gebührenplan des Ledgers, 0.001 RAND + call_fee(tier), die Regel auf Chain 15, gilt dort nicht. Die Wallet legt die Gebühr fest, bevor der Proof existiert; deshalb setzt sie den Proof mit der 4-MiB-Obergrenze der Chain an und addiert zwei Preisstufen als Spielraum. Vier gemessene Aufrufe auf Chain 18 kosteten je 0.0057 bis 0.0075 RAND (die Rechnung). rand fee call <tier> --gas <limit> --bytes <B> fragt den Node selbst nach der Untergrenze. private_payment liest vier private Salden und veröffentlicht, wenn sie mindestens ihren Schwellenwert ergeben, den Überschuss: Ausgaben [1, 0, 25, 0, 0, 0, 0, 0]. Die Chain sieht den Proof, den Tier und diese acht Wörter; die Salden sieht sie nie.
Flags: --tier T erzwingt einen Tier; --gas-limit <N|max> deklariert statt des Standards ein exaktes Gas-Limit oder die Obergrenze des Tiers, wobei das exakte Gas auf ein Vielfaches von 2^(tier − 2) aufgerundet wird; --fee <RAND> setzt die Gebühr; --expect-public <FILE> verweigert vor dem Beweisen, sofern das öffentliche Segment des Programms nicht den Wörtern dieser Datei entspricht; --auditor <rand1…> versiegelt das Transkript zusätzlich für einen benannten Auditor; --print-call-key gibt den Schlüssel pro Aufruf aus; --no-envelope veröffentlicht kein Transkript; --cuda wählt das GPU-Backend (auf Hardware ungetestet und mit dem Versiegeln eines Umschlags unvereinbar). Chain 20 weist --cuda bei einem Aufruf rundheraus ab: Ein an seine Transaktion gebundener Call-Proof wird auf dem CPU-Backend erzeugt.
Belege
Pro festgeschriebenem Aufruf wird ein Beleg gespeichert und neu berechnet, wenn ein Node seine Chain synchronisiert oder verifiziert:
→ {"method":"rand_getReceipt","params":["b082…09b0"]}
← { "tx": "b082…09b0", "program": "675adeea…0065", "tier": 14,
"outputs": [1, 0, 25, 0, 0, 0, 0, 0], "height": 105, "index": 0, "h_in": "9c0e…7f", "h_pub": null }
h_in ist die gesalzene Commitment des Proofs auf die privaten Eingaben; für sich genommen legt sie nichts offen. h_pub ist der Digest des beim Deployment festgelegten öffentlichen Segments des Programms, also der public_digest des Programms, oder null bei einem Programm, das ohne ein solches deployt wurde. Es gibt kein Feld effect, und das deklarierte Gas-Limit steht nicht im Beleg. Der Explorer zeigt dasselbe Objekt für einen call als receipt unter GET /transactions/:hash, listet die Aufrufe eines Programms unter GET /programs/:id auf und filtert GET /transactions?kind=call&program=<id>.
Eingabe-Umschläge: einen Aufruf später offenlegen
Standardmäßig veröffentlicht rand call das Transkript (salt, inputs) des Aufrufs, versiegelt in einem Call-Input-Umschlag, sodass die Eingaben später ohne einen zweiten Proof gezeigt werden können:
| Teil | Bytes | Was es ist |
|---|---|---|
body | 12 + 16 + 4·n + 16 | salt ‖ inputs unter einem frischen Schlüssel pro Aufruf K_call |
to_sender | 60 | K_call, verpackt unter dem ausgehenden Viewing-Key des Aufrufers |
kem_ct | 1,088 oder 0 | eine ML-KEM-768-Kapselung an den Auditor, leer ohne Auditor |
to_auditor | 60 oder 0 | K_call, verpackt unter dem Geheimnis dieser Kapselung |
Die assoziierten Daten des Body sind H_IN; ein auf einen anderen Aufruf übertragener Umschlag lässt sich daher für niemanden öffnen. Die Chain prüft nur die Größe (höchstens max_call_envelope_bytes: 65,536 Bytes auf Chain 20, 18,432 bei einem Genesis ohne dieses Feld) und liefert ihn unverändert über rand_getCallEnvelope(tx) aus. Drei Schlüssel öffnen ihn, und keine weiteren: der Viewing-Key des Aufrufers (jeder Aufruf dieser Wallet), der Schlüssel pro Aufruf (genau dieser Aufruf), der Viewing-Key des Auditors (genau dieser Aufruf).
rand open-call <txhash> # as the caller
rand open-call <txhash> --as-auditor # as the auditor
rand open-call <txhash> --call-key <hex> # with the per-call key alone
open-call öffnet das Transkript, prüft input_digest(salt, inputs) == H_IN, führt das Programm mit den wiederhergestellten Eingaben erneut im Emulator aus, vergleicht die Ausgaben mit denen des Belegs — und endet mit einem Wert ungleich null, wenn eine der beiden Prüfungen fehlschlägt, sodass ein Skript eine Warnung nicht für ein Ja halten kann. --no-envelope ist unumkehrbar: Ist das Salt einmal weg, kann niemand, auch nicht der Aufrufer, diesen Aufruf öffnen.
Seit v0.6.8 erfolgt die erneute Ausführung über dasselbe öffentliche Segment, über das der Proof erzeugt wurde: die beim Deployment festgelegten öffentlichen Wörter des Programms und, unter hardening_v6, die Call-Bindung aus acht Wörtern danach (fullnode issue #116). In v0.6.7 funktionierte open-call nur für ein Programm, das ohne öffentliches Segment deployt wurde; bei einem Programm mit Segment gab es die Eingaben aus und brach dann mit re-running the program on these inputs failed: PublicIndex(0) ab, mit einem Exit-Code ungleich null und ohne eine der beiden Prüfungen auszuführen.
Programmzustand (RPL-2)
Chain 20 ist die erste Chain mit RPL-2, aktiviert durch den Abschnitt program_state ihres Genesis. Ein Programm erhält drei Dinge, die es zuvor nicht hatte (fullnode docs/program-state.md, crates/randprotocol-core/src/ledger/program_state.rs bei v0.6.8):
| Zellen | eine Abbildung von einem Schlüssel aus 8 Wörtern auf einen Wert aus 8 Wörtern, pro Programm. Eine fehlende Zelle liest sich als acht Nullen; das Schreiben von acht Nullen entfernt sie. Im State-Root festgeschrieben |
| ein Vault | ein öffentlicher Saldo pro Asset (0 ist RAND). Wert gelangt über das Bundle eines Invoke hinein und verlässt den Vault als Notes, die die Chain berechnet |
| ein eigener Token | ein mit rand token create --program <id> registrierter Token (Mint-Berechtigung Program(id), kein anfängliches Angebot) wird nur von den Invokes dieses Programms geprägt und verbrannt |
Zellen und Vault-Salden sind öffentlich, wie das Angebot eines Tokens. Nichts davon ist eine Note.
Invoke (Action 33) ist ein Aufruf, der zusätzlich eine Transition deklariert, und sein Call-Proof zeigt, dass das Programm genau diese Transition akzeptiert:
Invoke { program, proof, input_envelope, transition }
Transition { reads: [{ key, value }] // cells read, with the values read; keys strictly ascending
writes: [{ key, value }] // cells written; keys strictly ascending
inflow: none | deposit | burn
pays: [{ asset, amount, recipient, r, envelope }] // notes out of the vault
mints: [{ asset, amount, recipient, r, envelope }] } // new units of the program's own token
Was hineinkommt, ist das, was das Bundle verbrennt: burn_r ist in den Vault eingezahltes RAND; burn_a von burn_asset ist ein Token, der eingezahlt (Zufluss deposit) oder zerstört wird (Zufluss burn, nur der eigene Token des Programms). Was hinausgeht, ist pro Auszahlung eine von der Chain berechnete Note, erst Auszahlungen, dann Mints, mit öffentlichem Betrag und öffentlichem pk des Empfängers, wie bei einem Mint. Das Ledger wendet die Transition nur an, wenn das Programm existiert, die Form zulässig ist, jede Zelle in reads noch den deklarierten Wert hält, der Vault (nach der eigenen Einzahlung dieser Transaktion) jede Auszahlung deckt, jeder Mint den eigenen Token des Programms betrifft und die Proofs verifiziert werden. Ein Lesezugriff, der nicht mehr übereinstimmt, wird als StaleRead abgelehnt; die Wallet liest neu und beweist neu.
Wie sich ein Invoke von einem Call unterscheidet.
Call (RPL-1) | Invoke (RPL-2) | |
|---|---|---|
| öffentliches Segment, über das der Proof erzeugt wird | public ‖ call_binding | public ‖ call_binding ‖ context, die Transition als Wörter |
| was die Chain bei Erfolg tut | speichert einen Beleg; Ausgaben sind Daten | speichert den Beleg und wendet die Transition an: schreibt Zellen, bewegt den Vault, hängt Auszahlungs-Notes an |
| Wert | nur der eigene Transfer des Bundles | Einzahlungen in den Vault durch die Burns des Bundles; Auszahlungen und Mints aus ihm |
| hängt vom Zustand ab | nein | der Proof nicht (das Segment wird aus der Transaktion gebaut), die Leseprüfung aber schon, bei der Zulassung und im Pool |
| Gebühr | die Call-Untergrenze | die Call-Untergrenze plus cell_fee pro erstellter Zelle |
Ein Programm liest das Kontextwort i mit READ_PUBLIC(public_len + 8 + i). Wort 0 ist die Version (1), die Wörter 1 bis 4 sind die Anzahlen von Reads, Writes, Auszahlungen und Mints, dann burn_r, die Zuflussart, burn_asset und burn_a, dann jeder Read und Write als Schlüssel und Wert (je 8 Wörter), dann jede Auszahlung als Asset und Betrag. Empfänger stehen nicht im Kontext: Das Programm bestimmt die Beträge, die Bindung legt fest, wer bezahlt wird. Ein Programm muss jedes Kontextwort, das ihm gezeigt wird, einschränken und darf keinen Pfad mit Panic haben, denn der Panic-Handler von guest-sdk hält an, und ein angehaltener Lauf ist ein beweisbarer Lauf.
Limits von Chain 20 (rand_getLimits.program_state):
| Limit | Chain 20 |
|---|---|
cell_fee | 10,000,000 Einheiten, 0.01 RAND, pro erstellter Zelle (eine Zelle, die ungleich null geschrieben wird, wo zuvor keine gespeichert war); das Überschreiben oder Löschen einer Zelle ist kostenlos |
max_reads | 8 Zellen |
max_writes | 8 Zellen |
max_payouts | 4 Notes, Auszahlungen und Mints zusammen |
Die Segmentregel ist meist enger: Das gesamte Segment muss in die öffentliche Tabelle passen, über die ein Aufruf desselben Programms bewiesen wird, 127 Wörter bei einem Programm ohne öffentliche Eingabe. Das lässt 119 Kontextwörter, zum Beispiel drei Reads, drei Writes und vier Auszahlungen.
Befehle und RPC (Wallet v0.6.8):
rand program build --guest rpl2_counter --out counter.json # one cell, incremented by one per invoke
rand program invoke <id> --transition step.json [--input N | --inputs-file F.json]
rand program state <id> [--cell <64 hex>] # rand_getProgramCell / rand_getProgramCells
rand program vault <id> # rand_getProgramVault
program invoke prüft jeden Eintrag in reads gegen die aktuelle Zelle, bevor es beweist, und endet mit Exit-Code 3 bei einem veralteten Read, vor dem Beweisen oder bei der Aufnahme, sodass ein Skript neu liest und es erneut versucht. rand_estimateFee mit "kind": "invoke" nennt die Gebühr. Das Format der Transitionsdatei steht in docs/cli.md des Fullnode. Vollständige RPL-2-Programme, die auf Chain 20 deployt sind (amm, stableswap, orderbook, stablecoin, lending, perp, crowdfund, stoploss), und RPL-1-Programme (sealed-auction, ballot, eligibility, private-join, credit-score) finden sich in randprotocol/example-programs.
Was öffentlich ist und was verborgen bleibt
| öffentlich | verborgen |
|---|---|
die Programm-ID und ihr Code; die Stufe (Tier); das deklarierte Gaslimit; die acht Ausgaben; H_IN; die Gebühr und die öffentlichen Felder des Bundles; die Größe des Envelopes; ob der Aufruf Keccak oder SHA-256 verwendet hat, und ungefähr wie oft (der Proof-Header deklariert die Höhe jeder Hash-Tabelle) | die privaten Eingaben; Register, Speicher, Verzweigungen; die tatsächliche Zyklenzahl und das exakte Gas; das Salt von H_IN; wer aufgerufen hat und womit bezahlt wurde |
Zwei Proofs desselben Laufs sind unterschiedliche Bytes. Auf Chain 15 und früher reichte das nicht aus: Ein Aufruf mit 31 oder weniger privaten Wörtern bewies eine Tabelle, die zu klein war, um etwas zu verbergen, und seine Eingaben ließen sich aus dem Proof rekonstruieren. Der Prover setzt für diese Tabellen seit v0.6 eine Untergrenze von 128 Zeilen an, und Constraint-Set 7 (Chain 16 und später) verschleiert die Summen je Tabelle, die weiterhin Informationen preisgaben. hc ist bindend, aber nicht verbergend: Wer Kandidatenprogramme aufzählen kann, kann bestätigen, welches davon deployt wurde.
Was noch nicht möglich ist
So, wie der Fullnode es angibt:
- Keine programmübergreifenden Aufrufe und kein Zustand außerhalb von RPL-2. Ein
Callist ein Proof über einen Lauf; von Aufruf zu Aufruf wird nichts übernommen, außer dem, was der Aufrufer selbst wieder einspeist. Auf Chain 20 liest und schreibt einInvokedie eigenen Zellen und den eigenen Vault des Programms (oben); keine Chain vor 20 hat Programmzustand, und ein Invoke kann die Zellen eines anderen Programms nicht berühren. - Keine programmgesteuerten Überweisungen außerhalb eines Vaults. Effect-Kind 1, die vom Programm bezahlte Überweisung an ein Konto, wurde zusammen mit den Konten gestrichen, und die Ausgaben eines
Callsind Aussagen. Auf Chain 20 zahlt einInvokeaus dem eigenen Vault des Programms aus, höchstens vier Notes, mit öffentlichen Beträgen und Empfängern. - Kein Compiler in der Wallet. Programme sind die eingebauten, assemblierten Guests oder Images, die mit
rand-guestim circuits-Repository gebaut wurden. - Die öffentliche Eingabe wird beim Deploy festgelegt, nicht pro Aufruf. Constraint-Set 6 hat
READ_PUBLICundH_PUBhinzugefügt. Auf Chain 20 darf ein Deploy bis zu 32,768 öffentliche Wörter enthalten, die Programm-ID bindet sie, und der Proof jedes Aufrufs wird gegen ihren Digest geprüft. Es gibt keine öffentlichen Wörter pro Aufruf. - Hash-Syscalls erzeugen große Proofs. Ein Proof mit Keccak ist etwa 3.2 MB groß und liegt damit ohne Weiteres über dem Standardlimit von 2 MiB; ein Proof mit SHA-256 fügt etwa 400 KB hinzu. Das Limit von Chain 20 beträgt 4,194,304 Bytes (4 MiB, wie auf den Chains 17 bis 19; auf den Chains 13 bis 16 waren es 8 MiB), womit beide zulässig sind: Auf Chain 18 maß ein Aufruf auf Tier 12 mit der Keccak-Tabelle 3.35 MB und ein Aufruf auf Tier 14 mit der SHA-256-Tabelle 1.91 MB (Example programs). Ein Aufruf ist zudem auf 128 Keccak-f-Permutationen und 128 SHA-256-Kompressionen begrenzt (siehe unten), und jedes KiB Proof wird zum Bytepreis berechnet.
- EVM- und Solana-Programme lassen sich auf Chain 20 nicht deployen, und ihre Aufrufe liegen über der Tier-Obergrenze für Aufrufe. Die Interpreter-Guests (
evm.bin, 18,009 Wörter;sbpf.bin, 8,317) und die übersetzten Images (der ERC-20 mit 11,686 Wörtern; SPL Token mit 65,096) liegen über dem Standardlimit von 4,096 Wörtern und unter den 65,535, die Chain 13 festgelegt und jede Chain seitdem beibehalten hat. Beide übersetzten Images wurden am 2026-09-19 auf Chain 13 deployt, SPL Token mit seinem ELF als öffentlicher Eingabe. Die Chains 14 und 15 wurden neu aufgesetzt, und auf keiner von beiden wurde eines der Images erneut deployt. Chain 20 lehnt wie Chain 19 das Deploy aller vier ab: Nach der Regelhardening_v6wird ein Programm, das kein Aufruf auf Tier 14 aufnehmen könnte, also mehr als 8,180 Wörter ohne öffentliches Segment und weniger mit einem, beim Deploy abgelehnt (ProgramUncallable), damit keine Deploy-Gebühr für ein Programm anfällt, das niemand aufrufen kann. Ein ERC-20-Aufruf wird auf Tier 16 oder 18 bewiesen: Das Tier-16-approvewurde am 2026-09-19 auf Chain 13 mit einem 3,412,405 Byte großen Produktions-Proof aufgerufen (Ethereum transpiler), und Chain 20 lehnt jeden Aufruf über Tier 14 ab (nächster Punkt), sodass dort heute kein ERC-20-Aufruf zulässig ist. SPL-Token-Aufrufe liegen auf Tier 20, was etwa 330 GB benötigt; ein Proof für einen SPL-Aufruf existiert nicht (proving memory). EVM-Opcodes mit Block-Kontext lösen einen Trap aus, und es gibt keinen On-Chain-EVM-Zustand. - Ein Aufruf wird auf Tier 14 oder darunter bewiesen. Seit Fullnode v0.5.6 (2026-09-25) wird ein Aufruf-Proof über Tier 14 abgelehnt (
CallTierTooHigh), ebenso einer mit mehr als 128 Keccak-f-Permutationen oder 128 SHA-256-Kompressionen. Das Tier-16-approvedes ERC-20 von Chain 13 würde auf Chain 20 abgelehnt, wie es auf den Chains 15, 18 und 19 abgelehnt worden wäre, und ebenso jeder ERC-20-Aufruf auf Tier 18 oder SPL-Token-Aufruf auf Tier 20; diese werden weiterhin off-chain bewiesen (the budget). - GPU-Proving hat nie auf echter Hardware gelaufen.
--cudaerfordert einen Build mit--features cudaund committetes PTX; beides gibt es auf der Fleet nicht. Auf Chain 20 wird ein Aufruf-Proof unabhängig vom Build auf dem CPU-Backend erzeugt. hcist nicht verbergend, wie oben, und eine Witness-Anfrage verrät dem Node weiterhin, welches Blatt eine Wallet ausgibt.