Rand Protocol
Dokumentation
Anleitungen

Wie die zkVM funktioniert

Wie Rand beweist, dass ein Programm korrekt ausgeführt wurde, ohne seine Eingabe offenzulegen - die RISC-V-Maschine, die in neun Tabellen aufgeteilte Ausführungsspur, die Busse, die sie verbinden, Regeln als Polynomidentitäten, Commitment, FRI, Fiat-Shamir, woher die Zero-Knowledge-Eigenschaft stammt, was ein Validator prüft und warum jeder FRI- und Hash-Parameter den Wert hat, den er hat.

Lesen Sie diese Seite, um die Maschine hinter jedem Beweis auf Rand zu verstehen: was sie beweist, wie der Lauf eines Programms zu etwas wird, das ein Validator in Millisekunden prüfen kann, und woher die Privatsphäre stammt. Was ein Beweis kostet und wie die Wallet ihn erzeugt, steht unter Proofs. Die Parameter und Hash-Konstruktionen finden Sie unter Cryptography. Den Befehlssatz, gegen den ein Programm geschrieben wird, beschreibt The RISC-V ISA.

Die eine Aussage, die jeder Beweis trifft

Die zkVM beweist für jedes Programm, das die Chain ausführt, genau eine Art von Aussage:

Ein Programm mit dem Digest hc, gestartet bei base_pc, ausgeführt auf einer privaten Eingabe, auf die H_IN festlegt, und einem öffentlichen Eingabesegment, auf das H_PUB festlegt, hat innerhalb von 2ᵗ − 1 Zyklen angehalten, höchstens GAS_LIMIT Gas verbraucht und diese acht Ausgabewörter geschrieben.

Ein abgeschirmter Transfer ist diese Aussage über ein einziges festes Programm, den Hidden-Asset-Bundle-Guest, dessen hc in der Genesis-Datei als hc_bundle festgeschrieben ist. Seit Chain 17 wird sie pro Transfer zweimal getroffen: Ein zweiter, kleiner Beweis über den Auth-Guest (hc_auth in der Genesis-Datei) zeigt, dass der Sender den Spend-Schlüssel besitzt (Proofs). Ein vertraulicher Aufruf ist dieselbe Aussage über ein bereitgestelltes Programm. Es gibt eine Maschine, einen Verifier und einen Satz von Constraints für alle. Ein Programm bereitzustellen ist deshalb eine Datenoperation (seine Wörter und hc speichern) und keine kryptografische: Es gibt keine Schaltung pro Programm und kein Trusted Setup.

Das Beweissystem ist ein STARK, gebaut mit Plonky3. Es benötigt nichts außer einer Hashfunktion: keine Setup-Zeremonie und keine elliptischen Kurven, sodass der Shor-Algorithmus nichts zu brechen findet.

Die Pipeline

 secret input ──► run the program ──► execution trace ──► polynomials ──► commit ──► FRI ──► proof
 (keys,           (RV32IM emulator)   (nine tables,      (one per       (8× code,   (80 queries,
  notes, paths)                        one row per step)  column)        Merkle)     20-bit grinding)

 every validator:  recompute the challenges from the Merkle roots, open 80 random positions,
                   check the rules there  →  ≈ 16 ms

Die obere Reihe läuft einmal auf dem Rechner des Senders, in etwa 100 Sekunden für ein Bundle und etwa 7 Sekunden für den danebenstehenden Autorisierungsbeweis. Die untere Reihe läuft auf jedem Validator, allein aus den öffentlichen Feldern und dem Beweis.

1. Das Programm ausführen und alles festhalten

Die Maschine ist ein 32-Bit-RISC-V-Prozessor: RV32IM ohne FENCE, die CSR-Befehle und EBREAK (The RISC-V ISA). Der Prover führt das Programm zunächst auf einem Emulator aus und hält pro Taktschritt eine Zeile fest. Diese Aufzeichnung ist die Ausführungsspur (Execution Trace). Wer sie besitzt, könnte den Lauf Schritt für Schritt wiederholen; der Rest des Verfahrens existiert, damit ein Verifier von der Richtigkeit der Spur überzeugt werden kann, ohne sie zu besitzen.

Ein Programm kommuniziert mit der Außenwelt über acht Syscalls und sonst nichts:

SyscallNr.Funktion
HALT0anhalten; die acht Ausgabewörter sind endgültig
WRITE_OUTPUT1eines der acht öffentlichen Ausgabewörter setzen
READ_INPUT2ein Wort der privaten Eingabe lesen
POSEIDON23einen Puffer im Speicher an Ort und Stelle hashen
KECCAK4eine Keccak-f[1600]-Permutation
SHA2565eine SHA-256-Kompression
READ_PUBLIC6ein Wort des öffentlichen Eingabesegments lesen
POSEIDON2_LEN7POSEIDON2 mit in den Hash eingebundener Nachrichtenlänge (Constraint-Satz 7)

Note-Commitments, Nullifier und Merkle-Pfade sind nicht in die Maschine eingebaut. Sie sind gewöhnlicher Guest-Code, der eine mit einem Domain-Tag versehene Nachricht im Speicher bereitstellt und POSEIDON2 aufruft. Dieser Syscall füllt nicht auf, daher hashen [a] und [a, 0] gleich. Die ausgelieferten Guests rufen ihn nur mit Nachrichten fester Länge auf. Ein neues Programm, das Daten variabler Länge hasht, verwendet POSEIDON2_LEN (The RISC-V ISA).

Es gibt keinen Abbruch

Der Prozessor kennt keinen Befehl, der sich weigert zu enden, daher kann ein Programm nicht stoppen, wenn eine Prüfung fehlschlägt. Der Bundle-Guest hält den Fehler stattdessen fest: Jede fehlgeschlagene Prüfung verknüpft per OR eine 1 in ein Register namens bad, und bad ist das letzte Wort, das in den Ausgabe-Digest gehasht wird:

D = H(64; anchor, nf₀..₃, cm₀..₃, fee, burn_a, burn_r, burn_asset, time, c, bad)

c ist das Autorisierungs-Commitment, das auch der Auth-Beweis veröffentlicht (seit Chain 17; der Digest der Chains 14 bis 16 enthielt kein c).

Die Chain übernimmt das Urbild von D nie vom Sender. Sie baut das Urbild aus den öffentlichen Feldern der Transaktion neu auf, setzt bad = 0 selbst, hasht und vergleicht. Ein Betrüger kann einen vollkommen gültigen Beweis für einen Lauf erzeugen, in dem eine Prüfung fehlschlug, doch dieser Lauf hat H(…, 1) ausgegeben, und die Chain sucht H(…, 0). Beide in Übereinstimmung zu bringen erfordert eine Hash-Kollision.

2. Die Spur in neun Tabellen aufteilen

Eine einzige Tabelle für einen ganzen Prozessor wäre unnötig breit, daher wird die Spur in spezialisierte Tabellen aufgeteilt, die gemeinsam als ein Stapel bewiesen werden:

TabelleZeilen bei Stufe tWas sie festhält
programdeklariert, ≥ 2⁷die Befehlswörter des Programms, in der Schaltung dekodiert; legt fest, was die CPU abrufen darf
cpu2ᵗeine Zeile pro Zyklus, davor Zeilen, die das Programm, die private Eingabe und das öffentliche Segment hashen; enthält die 35 öffentlichen Werte
memorydeklariert, ≥ 2ᵗ⁺²Register und RAM, sortiert nach (Raum, Adresse, Zeit)
alu2ᵗ⁺¹jede arithmetische, logische, Schiebe-, Vergleichs-, Multiplikations- und Divisionsoperation, auf Byte-Limbs
range, nibbleje 256feste Nachschlagetabellen: Bytes, Zweierpotenzen, 4-Bit-AND/OR/XOR
poseidon22ᵗ⁺²die Hash-Permutation, eine Zeile pro Runde
inputdeklariert, ≥ 2⁷die festgelegten Wörter der privaten Eingabe
publicdeklariert, ≥ 2⁷die Wörter des öffentlichen Segments (bei einem Bundle die Bindung an die Transaktion)

Zwei weitere Tabellen, keccak und sha256, sind nur in einem Beweis vorhanden, dessen Programm sie aufgerufen hat, jeweils mit mindestens 2⁷ Zeilen; der Bundle-Guest verwendet keine von beiden. Die Untergrenze von 2⁷ Zeilen für jede deklarierte Tabelle kam mit Constraint-Satz 7 (Abschnitt 9).

Die Stufe (Tier) t ist 10, 12, 14, 16, 18 oder 20 und bestimmt die Größe der Tabellen. Jedes Bundle wird auf Stufe 14 bewiesen: eine CPU-Tabelle mit 16.384 Zeilen, von denen der Guest etwa 12.400 nutzt. Der Auth-Guest läuft 250 Zyklen auf Stufe 10. Der Rest ist Auffüllung, sodass ein Beweis offenlegt, welche Stufe ein Programm brauchte, nie aber, wie viele Schritte es darin ausführte (Tiers).

Die Memory-Tabelle macht den RAM überprüfbar. Weil sie nach Adresse und dann nach Zeit sortiert ist, wird „ein Lesezugriff liefert den zuletzt an diese Adresse geschriebenen Wert“ zu einer Regel zwischen zwei benachbarten Zeilen.

3. Die Tabellen über Busse verbinden

Woher weiß der Verifier, dass die Summe, die die CPU verwendet hat, die Summe ist, die die ALU berechnet hat? Über Busse. Braucht die CPU eine Addition, sendet sie eine Nachricht (op, a, b, c); die ALU empfängt für jede Zeile, die sie berechnet, eine Nachricht. Die Maschine hat siebzehn Busse:

Buszwischen
PROGRAM, PROGRAM_WORDcpu ← program: Befehlsabrufe und die Wörter, die der Programm-Digest aufnimmt
MEMORYcpu ↔ memory: jedes Lesen und Schreiben
ALUcpu → alu
RANGE8, POW2, AND4, OR4, XOR4Byte-Bereichsprüfungen, Schiebe- und Bitoperationen gegen die festen Tabellen
POSEIDON2cpu → poseidon2: eine Nachricht pro Permutation
INPUT_DIGEST, INPUT_READcpu ← input: die Wörter, die der Eingabe-Digest aufnimmt, und die Lesezugriffe des Programms
PUBLIC_DIGEST, PUBLIC_READcpu ← public: dasselbe Paar für das öffentliche Segment
KECCAK, SHA256cpu → die optionalen Hash-Tabellen
BLINDjede Tabelle → die nächste Tabelle in einem festen Zyklus: je ein Zufallswert, in der ersten Zeile (Constraint-Satz 7)

Die Busse werden mit einem Logarithmische-Ableitung-Argument (LogUp) geprüft. Nachdem die Tabellen festgelegt sind, wird ein Zufallswert α gezogen, und jede Tabelle führt eine laufende Summe von ±1/(α − m) über die Nachrichten, die sie sendet oder empfängt. Die Summen heben sich genau dann exakt auf, wenn jede gesendete Nachricht empfangen wurde, also auf beiden Seiten dieselbe Multimenge steht. Eine gefälschte Nachricht hinterlässt einen Rest, den ein zufälliges α mit überwältigender Wahrscheinlichkeit aufdeckt. Der Memory-Bus verwendet einen nahen Verwandten, eine Multimengen-Permutationsprüfung.

Jede Tabelle veröffentlicht den Endwert ihrer laufenden Summe, ihren Endwert (Terminal), und der Verifier prüft, dass die Endwerte sich zu null addieren. Bis Constraint-Satz 6 war ein Endwert eine nachprüfbare Funktion des Inhalts seiner Tabelle. Seit Constraint-Satz 7 trägt jede Tabelle fünf zusätzliche Spalten und sendet in ihrer ersten Zeile einen Zufallswert und empfängt den ihres Nachbarn auf dem BLIND-Bus. Jeder Endwert verschiebt sich um ein gleichverteiltes Element des Challenge-Körpers, und die Summe bleibt null.

Die Digest- und Lese-Busse sind bewusst getrennte Paare. LogUp gleicht pro Nachricht aus, nicht pro Verbraucher; ein gemeinsamer Bus würde es einem Prover erlauben, ein Wort aus dem Digest wegzulassen, während ein Lesezugriff auf dieses Wort weiterhin gelingt.

4. Die Spur an die Außenwelt binden

Fünfunddreißig öffentliche Werte verlassen die Spur, alle in der CPU-Tabelle:

WerteBedeutung
1der Einstiegs-pc
1die Stufe
8die Ausgabewörter (bei einem Bundle der Digest D)
8hc, der Programm-Digest, innerhalb der Trace aus den Wörtern der Programmtabelle berechnet
8H_IN, der Digest der privaten Eingabe, über die Eingabe mit einem frischen 128-Bit-Salt an erster Stelle
8H_PUB, der Digest des öffentlichen Segments, ohne Salt
1GAS_LIMIT, die Gas-Obergrenze, die der Prover angibt (Constraint-Satz 8)

Weil hc im Schaltkreis berechnet wird, kann ein Beweis über ein anderes Programm nicht als Beweis über den Bundle-Guest durchgehen. Der Verifier sieht nie ein Instruktionswort; er vergleicht hc mit dem Digest, den er erwartet. Weil H_IN gesalzen ist, bindet er die private Eingabe, ohne sie preiszugeben, selbst gegenüber jemandem, der sie erraten könnte. Weil H_PUB nicht gesalzen ist, kann der Verifier ihn aus Wörtern neu berechnen, die er bereits hat. Bei einem Bundle sind diese Wörter die Transaktionsbindung, blake3("rand-tx-bind-1" ‖ chain_id ‖ tx) über die Transaktion mit geleerten Beweisbytes. Ein Beweis, der auf eine Transaktion mit anderem Ziel, anderem Memo, anderem Envelope oder anderer Chain-ID kopiert wurde, verifiziert daher nicht. Auf Chain 20 (Genesis binding_domain: 1) lautet sie blake3("rand-tx-bind-2" ‖ genesis_hash ‖ chain_id ‖ tx), sodass ein Beweis auch bei gleicher Chain-ID auf einer anderen Chain nicht verifiziert; die Wörter sind eine öffentliche Eingabe, im Schaltkreis hat sich dadurch nichts verändert. Der Auth-Beweis wird über dieselben acht Wörter erstellt. Ein Call-Beweis auf Chain 20 ist auf die gleiche Weise gebunden: Sein Segment besteht aus den öffentlichen Wörtern des Programms zur Deploy-Zeit, gefolgt von acht Wörtern einer Call-Bindung (rand-call-bind-2, vor Chain 20 rand-call-bind-1), sodass ein Call-Beweis, der unter ein anderes Fee-Bundle kopiert wurde, nicht verifiziert.

Gas ist das Maß der Maschine für das Gewicht eines Laufs und steckt seit Constraint-Satz 8 im Schaltkreis. Die CPU-Tabelle führt eine Spalte GAS, die bei 1 beginnt und pro Zeile 1 addiert, 3 für eine Poseidon2-Absorb-Zeile, 64 für eine SHA256-Zeile und 192 für eine KECCAK-Zeile. In der HALT-Zeile beweist der Schaltkreis GAS ≤ GAS_LIMIT über vier bereichsgeprüfte Limbs der Differenz, die nie geöffnet werden: Das Limit ist öffentlich, das verbrauchte Gas eines Laufs nicht. Der Verifier weist, bevor er einen Schlüssel aufbaut, ein Limit zurück, das über dem liegt, was der Header des Beweises selbst benötigen könnte:

gas_max(t, k, s) = (2ᵗ − 1) + 2ᵗ⁻² + 191·(2ᵏ / 32) + 63·(2ˢ / 64)

wobei k und s die angegebenen Log-Höhen der Tabellen keccak und sha256 sind und jeder Term für eine fehlende Tabelle null ist. Die Obergrenze eines hashfreien Tier-10-Headers beträgt 1.279, die eines Tier-14-Headers 20.479. Die Chain bepreist einen Call nach seinem angegebenen Limit (Gebühren).

5. Die Regeln in Polynomidentitäten überführen

Jede Spalte ist eine Liste von Zahlen im Goldilocks-Körper, also ganze Zahlen modulo p = 2⁶⁴ − 2³² + 1. Eine Liste von n Zahlen ist dasselbe wie ein Polynom vom Grad unter n: dasjenige, das durch sie hindurchgeht, wenn sie auf einer Untergruppe {1, ω, ω², …} des Körpers ausgelegt werden. Goldilocks besitzt solche Untergruppen für jede Zweierpotenz bis 2³², die Umwandlung ist also eine schnelle Fourier-Transformation.

Eine Regel der Maschine wird dann zu einer Gleichung zwischen Spaltenpolynomen. „Der Programmzähler rückt um vier vor, es sei denn, die Instruktion springt“ wird schematisch zu:

is_jump(x)·(pc(ωx) − target(x)) + (1 − is_jump(x))·(pc(ωx) − pc(x) − 4) = 0

für jedes x in der Untergruppe, wobei pc(ωx) „die nächste Zeile“ bedeutet. Der vollständige Satz solcher Regeln einer Tabelle ist ihre AIR (algebraische Zwischendarstellung, Algebraic Intermediate Representation). Die Regeln der CPU und der ALU erreichen Grad 8; die übrigen bleiben bei 2 bis 4.

Ein Polynom, das an jedem Punkt der Untergruppe null ist, ist durch Z(x) = xⁿ − 1 teilbar. Der Prover fasst alle Regeln mit zufälligen Gewichten zu einem Polynom C(x) zusammen und teilt durch Z(x). Gilt jede Regel in jeder Zeile, ist der Quotient ein echtes Polynom niedrigen Grades. Scheitert eine Regel in einer Zeile, ist er es nicht. Aus „War jeder Schritt eines Laufs von 16.000 Zyklen korrekt?“ ist „Liegt diese Funktion nahe an einem Polynom niedrigen Grades?“ geworden, und diese Frage lässt sich beantworten, indem man einige zufällig gewählte Werte betrachtet.

6. Festlegen, bevor die Fragen bekannt sind

Der Prover muss seine Polynome festlegen, bevor er die zufälligen Fragen des Verifiers sieht, sonst könnte er seine Antworten maßschneidern. Er wertet jede Spalte auf einer achtmal größeren Domäne als die Tabelle aus (Blow-up 8, Coderate 1/8) und veröffentlicht eine Merkle-Wurzel über die Auswertungen. Diese Redundanz ist ein Reed–Solomon-Code: Ein Polynom niedrigen Grades ist durch ein beliebiges Achtel seiner Werte festgelegt. Wer auch nur eine Zeile der Trace ändert, muss daher den größten Teil des erweiterten Codeworts ändern, um konsistent zu bleiben, und zufällige Stichproben treffen auf den Schaden.

Die Merkle-Bäume verwenden Poseidon2 über Goldilocks: Breite 8, S-Box x⁷, 4 volle Runden, 22 partielle Runden und 4 volle Runden, Digests aus vier Körperelementen (etwa 256 Bit). Jedes Blatt wird mit vier Elementen frischer Zufallsdaten gehasht, sodass eine Wurzel nichts über die darunterliegenden Werte verrät. Dieselbe Permutation ist innerhalb der Maschine die Note-Commitment- und Nullifier-Hashfunktion. Ihre 86 Rundenkonstanten stehen als Literaltabelle im Quellcode des Nodes (poseidon2_constants.rs), sodass kein Bibliotheks-Upgrade sie ändern kann.

7. Stichproben mit FRI

Der Verifier hält nun Merkle-Wurzeln. Zuerst bindet er den Quotienten an die Trace: Er wählt einen zufälligen Punkt z außerhalb der Domäne der Tabelle, verlangt den Wert jeder Spalte und des Quotienten an dieser Stelle und prüft C(z) = Q(z)·Z(z) direkt. Es bleibt zu bestätigen, dass die festgelegten Funktionen wirklich Polynome niedrigen Grades sind, und genau das testet FRI.

FRI arbeitet durch Faltung. Man zerlegt ein Polynom in gerade und ungerade Anteile, f(x) = fₑ(x²) + x·fₒ(x²). Für ein zufälliges β des Verifiers hat f′(y) = fₑ(y) + β·fₒ(y) den halben Grad auf einer halb so großen Domäne. Der Prover legt sich auf f′ fest, erhält ein neues β und faltet erneut (Rand faltet pro Runde um bis zu acht), bis eine Konstante übrig bleibt, die er im Klartext sendet. Eine Query wählt dann eine zufällige Position in der ersten Schicht und folgt ihr nach unten. Auf jeder Schicht prüft sie, dass die oben geöffneten Werte zu dem darunterliegenden falten. Eine Funktion, die weit von einem Polynom niedrigen Grades entfernt war, kann alle diese Zusagen nicht konsistent halten.

Rand führt 80 Queries aus. Nach dem bewiesenen Proximity-Gaps-Theorem lässt jede Query einem Betrüger höchstens eine Chance von 9/16, sodass 80 Queries (9/16)⁸⁰ ≈ 2⁻⁶⁶·⁴ ergeben. Unter der ethSTARK-Vermutung, auf die sich im Einsatz befindliche STARKs stützen, lässt jede Query 1/8, was weit kleiner ist.

8. Das Gespräch entfernen

Alles bisher war als Dialog beschrieben, doch eine Transaktion muss eine einzige Nachricht sein, die jeder Validator allein prüfen kann. Die Fiat–Shamir-Transformation entfernt den Verifier: Der Prover berechnet jede „zufällige“ Challenge als Hash von allem bisher Festgelegten, mit einem Poseidon2-Duplex-Sponge. Jede Änderung an einem Commitment ändert alle nachfolgenden Challenges, sodass der Prover seine Commitments nicht passend zu den Challenges wählen kann.

Zwei weitere Maßnahmen schließen die dabei verbleibenden Lücken:

  • Erweiterungskörper. Ein Körper mit 2⁶⁴ Elementen ist zu klein, um Challenges daraus zu ziehen: Ein Betrüger braucht eine Challenge, die auf einen der wenigen schlechten Werte eines Polynoms fällt, und hat damit eine Erfolgswahrscheinlichkeit von etwa N/2⁶⁴, gleich wie viele Queries laufen. Die Trace bleibt also in Goldilocks, aber jede Challenge wird aus der quadratischen Erweiterung gezogen, mit etwa 2¹²⁸ Elementen.
  • Grinding. Bevor die Query-Positionen abgeleitet werden, muss der Prover eine Nonce finden, deren Hash mit dem Transkript mit 20 Nullbits beginnt, etwa eine Million Hashes. Ein ehrlicher Prover zahlt das einmal. Ein Betrüger, der die Query-Positionen so lange neu auswürfelt, bis sie seine Fälschung verfehlen, zahlt es bei jedem Versuch. Dies ist der einzige Proof of Work in ganz Rand, und er hat nichts mit dem Konsens zu tun.

In der Summe: 66,4 Bit aus den Queries plus 20 aus dem Grinding ergeben 86 Bit bewiesen und etwa 100 Bit unter der Vermutung, bei der der Term des Erweiterungskörpers die Grenze bildet.

9. Woher die Zero-Knowledge-Eigenschaft kommt

Ein STARK ist für sich genommen nicht zero-knowledge: Die an den Query-Positionen geöffneten Werte sind Werte der Trace-Polynome und geben ohne Vorkehrungen Information preis. Die Konfiguration von Rand fügt vier Schichten hinzu:

  1. Jedes festgelegte Polynom wird randomisiert (das verbergende FRI von Plonky3 mischt vier zufällige Codewörter ein), sodass die 80 geöffneten Werte der Verteilung nach von jemandem reproduziert werden können, der die Trace nie gesehen hat;
  2. jedes Merkle-Blatt wird gesalzen, sodass Wurzeln und Authentifizierungspfade nichts verraten;
  3. die Zufälligkeit des Provers wird für jeden Beweis frisch vom Betriebssystem bezogen, sodass zwei Beweise derselben Zahlung unverbunden erscheinen;
  4. jede deklarierte Tabelle hat mindestens 2⁷ Zeilen, und jeder LogUp-Abschluss ist verblindet (Constraint-Satz 7). Ein Beweis öffnet jede Tabelle an 80 Query-Punkten und zwei Punkten außerhalb der Domäne, und das verbergende Commitment mischt so viele zufällige Zeilen ein, wie die Tabelle Zeilen hat; eine Tabelle ist also nur verborgen, solange sie mindestens 82 Zeilen hat. 128 ist die kleinste Zweierpotenz, die das erfüllt.

Ebene 4 ist eine Korrektur. Die Prüfung vom 2026-09-27 hat aus Produktionsbeweisen die privaten Eingaben von Aufrufen mit 31 oder weniger Eingabewörtern wiederhergestellt, die Abrufzahlen eines Programms aus seiner kurzen Programmtabelle abgelesen und die echten Eingabeslots eines Bundles anhand seiner ungeblendeten Terminals von den Platzhaltern unterschieden. Diese Beweise sind die, die auf Chain 15 und früher erzeugt wurden. Chain 16 (Constraint-Set 7, mit dem verzweigungsfreien Bundle-Guest) hat alle drei Lücken geschlossen, und die Chains 18 bis 20 behalten die Korrekturen bei.

Die Garantie ist statistische Zero-Knowledge-Eigenschaft im Random-Oracle-Modell. Was ein Beweis nicht verbirgt, lässt sich genau benennen: seine Stufe, seine deklarierten Tabellenhöhen und seine öffentlichen Werte, darunter das Gaslimit. Eine deklarierte Höhe von 0 für keccak oder sha256 bedeutet, dass der Lauf diesen Hash nie verwendet hat. Der Header eines Aufrufbeweises zeigt also, ob der Aufruf gehasht hat und, oberhalb der Untergrenze, wie oft, auf einen Faktor zwei genau. Bei einem Bundle sind die öffentlichen Werte und Felder der Anker, vier Nullifier, vier Commitments, die Gebühr, die Burns, das Burn-Asset, die Zeit, das Autorisierungs-Commitment und die Transaktionsbindung. Welches Asset ein Transfer bewegt, ist eine private Eingabe, daher sehen eine zUSD-Zahlung und eine RAND-Zahlung on-chain gleich aus. hc ist deterministisch, daher kann jeder erkennen, ob ein Beweis den Bundle-Guest betrifft (jeder wertbewegende Beweis tut das) oder, bei einem Aufruf, welches bereitgestellte Programm er ausgeführt hat.

10. Was ein Validator prüft

Machine::verify_public(hc, words, proof) läuft in dieser Reihenfolge, das Günstigste zuerst:

  1. Die vom Beweis deklarierte Stufe und die Tabellenhöhen sind begrenzt (check_declared_heights), bevor irgendetwas danach dimensioniert wird. Ein fehlerhafter Header kostet also einige Vergleiche statt eines mehrsekündigen Schlüsselaufbaus. Ein Bundle muss Stufe 14 ohne beide Hash-Tabellen deklarieren und ein Auth-Beweis Stufe 10.
  2. Es gibt 35 öffentliche Werte, jeder ein kanonisches Körperelement, mit pv[HC] = hc, pv[TIER] gleich der Stufe des Beweises, jedem Ausgabewort unter 2³² und GAS_LIMIT höchstens gleich gas_max des Headers.
  3. Der Batch-STARK wird gegen einen Verifier-Schlüssel verifiziert, der pro Form (tier, program, input, keccak, sha256, public heights) zwischengespeichert wird, nicht pro Programm.
  4. H_PUB entspricht dem Digest von words, der bei einem Bundle die Transaktionsbindung ist, die der Node selbst berechnet hat.

Vor alledem prüft der Node jeden eingereichten oder per Gossip verbreiteten Beweis auf eine kanonische Kodierung: Sein FRI-Faltungsplan muss derjenige sein, den ein ehrlicher Prover für die im Header deklarierten Grade erzeugt. Seit fullnode v0.6.8 (audit v6, ZKV-4) weist die Prüfung zuerst jeden deklarierten Grad über 25 Bit zurück (MAX_HONEST_DEGREE_BITS, die Obergrenze 2²⁴ der Speichertabelle plus die Zero-Knowledge-Verdopplung, das Meiste, was ein akzeptierter Beweis trägt). Vor dieser Schranke ließ ein Header, der einen riesigen Grad deklarierte, den Node einen enormen Faltungsplan in seiner Event-Loop neu ableiten und hielt sie so lange fest, wie der Absender wollte. Der Verifier hätte den Beweis ohnehin abgelehnt, die Schranke ändert also nur die Kosten des Neinsagens. Eine Panic innerhalb der Prüfung weist nun nur diese eine Transaktion zurück, statt den Node anzuhalten.

Danach berechnet das Ledger den Bundle-Digest D mit bad = 0 neu und vergleicht ihn mit den acht Ausgabewörtern des Beweises. Auf Chain 20 verlangt es außerdem, dass das GAS_LIMIT eines Bundle-Beweises genau dem bundle_gas_limit aus dem Genesis entspricht, 20.479, und das eines Auth-Beweises 1.279 beträgt, sodass jeder Transfer dieselben Werte deklariert. Mit einem warmen Schlüssel dauert die gesamte Prüfung etwa 16 ms.

Die Parameter und warum sie gewählt wurden

Jede Zahl unten ist Konsens: Der Verifier prüft sie, ein Beweis mit einem anderen Wert verifiziert nicht, und eine Änderung ist ein neues Constraint-Set und ein Hard Fork (Constraint-Sets). Das FRI-Profil wird durch die Genesis-Datei festgelegt (fri_profile: production auf Chain 20), nie durch den Beweis.

ParameterWertWarum dieser Wert
BasiskörperGoldilocks, p = 2⁶⁴ − 2³² + 1Eine 64-Bit-Primzahl, deren Arithmetik aus wenigen Maschinenbefehlen besteht und die multiplikative Untergruppen jeder Zweierpotenzgröße bis 2³² besitzt, sodass die Umwandlung einer Spalte in ein Polynom eine schnelle Fourier-Transformation ist. Die Trace liegt hier, weil dies der günstige Körper ist.
Challenge-Körperquadratische Erweiterung F_p² (≈ 2¹²⁸)Challenges, die aus Goldilocks selbst gezogen würden, hinterließen eine Soundness-Untergrenze von etwa N/2⁶⁴, die keine Anzahl von Queries senken kann. Die Erweiterung drückt diesen Term für Traces bis zu 2²⁵ Zeilen auf höchstens 2⁻¹⁰⁰. Nur die Challenges zahlen für den größeren Körper; die Trace nicht. Eine kubische Erweiterung (≈ 2¹⁹²) ist die benannte Reserve, falls je 128 Bit verlangt werden.
Blow-up8 (Rate ρ = 1/8, log_blowup = 3)Zwei Gründe zugleich. Die CPU- und ALU-Tabellen haben Regeln vom Grad 8, und mit dem Zero-Knowledge-Flag hat der Quotient den Grad 9, das Maximum, das ein Blow-up von 8 tragen kann; eine Regel höheren Grades würde Blow-up 16 erzwingen. Und die Fähigkeit jeder Query, einen Betrüger zu erwischen, hängt von der Rate ab: (1 + ρ)/2 = 9/16 pro Query bewiesen, ρ = 1/8 vermutet. Ein größerer Blow-up würde weniger Queries erkaufen, auf Kosten einer proportional größeren Low-Degree-Extension, die der Prover berechnen und im Speicher halten muss.
FRI-Queries80Die Zahl, die die bewiesene Soundness bei etwa 86 Bit hält. Sie wurde einmal auf 27 gesenkt (Constraint-Sets 3 und 4), was ein vermutetes 100-Bit-Ziel erfüllte (3·27 + 20 = 101), aber nur etwa 42 bewiesene Bit übrig ließ; das zk-Audit vom 2026-09-12 (Befund ZM1) stellte 80 wieder her. Queries machen den größten Teil der Größe eines Beweises aus, weshalb ein Bundle-Beweis ≈ 1.50 MB groß ist. Beweisbare 100 Bit würden 97 Queries erfordern.
Grinding20 Bit, in der Query-PhaseBevor die Query-Positionen gezogen werden, muss der Prover eine Nonce finden, deren Hash mit 20 Nullbits beginnt: etwa eine Million Hashes, einmal gezahlt von einem ehrlichen Prover und bei jedem Wiederholungsversuch von einem Betrüger, der Positionen neu würfelt. Es fügt günstig 20 Bit hinzu; mehr Grinding (34 Bit würden ebenfalls beweisbare 100 ergeben) macht jeden ehrlichen Beweis langsamer. Die Commit-Phase hat kein Grinding.
Folding-Aritätbis zu 8 pro Runde (max_log_arity = 3)Das Falten um acht statt um zwei ergibt ein Drittel so viele FRI-Schichten, sodass jede Query weniger Merkle-Pfade öffnet und der Beweis kleiner wird, bei etwas mehr Arbeit pro Schicht.
Endpolynomeine Konstante (log_final_poly_len = 0)FRI faltet bis ganz nach unten und sendet einen Wert im Klartext, die einfachste Abschlussprüfung für den Verifier.
Hash (im Circuit, Merkle, Transkript)Poseidon2, Breite 8, S-Box x⁷, 4 + 22 + 4 Runden, Rate 4, Kapazität 4Ein Hash, der als Körperarithmetik günstig sein soll, sodass das Hashing innerhalb eines Beweises (Commitments, Nullifier, Merkle-Pfade, der Programm-Digest) Zeilen kostet statt Tausender Bitoperationen. Der S-Box-Exponent darf keinen Faktor mit p − 1 = 2³²·3·5·17·257·65537 teilen, und 7 ist der kleinste, der funktioniert. Breite 8 bei Rate 4 ergibt einen Digest aus vier Elementen, etwa 256 Bit. Die 86 Rundenkonstanten sind eine Literaltabelle im Node (poseidon2_constants.rs).
Digest-Breite4 Körperelemente (≈ 256 Bit)Ausreichend für die Urbild-Rollen (Nullifier, Commitments: 128 Bit gegen Grover). Sie ist nicht ausreichend für 128-Bit-Quantenkollisionsresistenz im Transkript (2²⁵⁶/³ ≈ 2⁸⁵); der Entwurf des Whitepapers verlangt einen 384-Bit-Transkript-Hash, und die laufende Chain hat ihn nicht übernommen.
Hiding4 zufällige Codewörter in FRI; 4 Salt-Elemente pro Merkle-Blatt; frische OS-Zufälligkeit pro BeweisDas Minimum, das die 80 geöffneten Positionen und die Merkle-Pfade allein aus öffentlichen Werten simulierbar macht, sodass der Beweis statistisch Zero-Knowledge ist. Beweise derselben Zahlung, zweimal erzeugt, wirken unverbunden.
Tabellenuntergrenze2⁷ Zeilen für jede deklarierte Tabelle (Constraint-Set 7)Ein Beweis öffnet 80 Query-Punkte und 2 Out-of-Domain-Punkte jeder Tabelle, insgesamt 82, und eine Tabelle mit h Zeilen wird durch h Zufallszeilen verblindet. 64 Zeilen sind zu wenig, und 128 ist die nächste Zweierpotenz. Eine Query-Anzahl über 126 würde 2⁸ erfordern.
Terminal-Blindein Wert aus F_p² pro Tabelle, auf dem BLIND-Bus (Constraint-Set 7)Ein LogUp-Terminal wird aus der rohen Trace berechnet, sodass es ohne Blind ein ungesalzenes Commitment auf seine Tabelle ist. Der Blind ist ein Erweiterungskörperelement, weil eine einzelne Basiskörperkoordinate die Verschiebung auf eine dünne Menge beschränken würde, gegen die ein Kandidaten-Witness getestet werden könnte.
Gas-Gewichte1 pro Zeile; 3 pro Poseidon2-Absorb-Zeile; 64 pro SHA256-Zeile; 192 pro KECCAK-Zeile (Constraint-Set 8)Jedes Gewicht zählt die Zeilen, die die Zeile in andere Tabellen zieht: eine Absorb-Zeile eine Permutation mit 32 Zeilen, eine SHA256-Zeile 64 Zeilen mit 466 Spalten, eine KECCAK-Zeile 32 Zeilen mit 2,612 Spalten und 100 Speicherzeilen.
Maximaler Constraint-Grad8Durch den obigen Blow-up festgelegt; der Grad jeder Tabelle wird durch einen Test fixiert, damit eine neue Regel ihn nicht unbemerkt überschreiten kann.
Tiers10, 12, 14, 16, 18, 20Tabellen werden in sechs festen Stufen dimensioniert, jede viermal so groß wie die vorige, und ein Beweis verrät nur seinen Tier: einen groben, aufgefüllten Bereich der Lauflänge statt der Zyklenzahl. Jedes Bundle ist Tier 14 und jeder Auth-Beweis Tier 10; Aufrufe sind seit Fullnode v0.5.6 auf Tier 14 begrenzt.
Maximale Trace2²⁴ Zeilen (2²⁵ mit dem Zero-Knowledge-Bit)Die Größe, für die der Erweiterungskörperterm bewiesen ist; eine längere Trace wird abgelehnt, statt unter einer Soundness-Behauptung bewiesen zu werden, die nicht gilt.
Öffentliche Werte35 KörperelementeEinstiegs-pc, Tier, acht Ausgabewörter, die drei Digests hc, H_IN, H_PUB zu je acht Wörtern und das deklarierte Gas-Limit: die kleinste Aussage, die das Programm, die private Eingabe, die öffentliche Eingabe und eine Schranke für die Arbeit benennt. Bis Constraint-Set 7 waren es 34.
Obergrenze der Beweisgröße2 MiB Standard; 4 MiB auf Chain 20 (max_proof_bytes)2 MiB liegen über jedem Beweis, den die eingesetzten Guests ohne Hash-Tabelle erzeugen, und unter einem Beweis mit Keccak (≈ 3.3 MB); das Genesis von Chain 20 setzt 4,194,304, um beide zuzulassen, wie es die Chains 17 bis 19 taten. Die Chains 13 bis 16 setzten 8 MiB. Die Obergrenze wird geprüft, bevor ein Beweis dekodiert wird.

Was diese Zahlen zusammengenommen ergeben: 86 Bit bewiesene Soundness, etwa 100 unter der ethSTARK-Vermutung (bei der der Erweiterungskörper, nicht die Queries, die Grenze setzt) und etwa 85 Bit gegen einen Quantengegner, bestimmt durch den 256-Bit-Transkript-Hash. Das Whitepaper führt beide Soundness-Angaben bewusst nebeneinander: Jedes eingesetzte FRI-STARK stützt sich für seine letzten Bits auf die Vermutung, und ein Leser, der einen Einsatz dimensioniert, sollte wissen, welche Annahme welche Bits trägt.

Beweise über Beweise

Mit etwa 1.50 MB ist ein Bundle-Beweis schwer, der 1.37 MB große Auth-Beweis läuft daneben mit, und jeder Node speichert sie alle. Das Mittel, das gebaut ist, ist Rekursion: ein Beweis, dessen Aussage lautet „Ich habe diese Beweise verifiziert“. Der Fullnode führt dafür eine zweite, kleinere Maschine mit, die Recursion-VM, deren natives Wort ein Goldilocks-Element ist und deren Befehle die eigenen Operationen des Verifiers sind (Körperarithmetik, Poseidon2, Merkle- und FRI-Schritte). Ihr Aggregationsprogramm spielt die vollständige Verifikation von N Bundle-Beweisen nach und veröffentlicht einen Digest über deren öffentliche Werte, gebunden an die Chain, den Aggregator und eine Nonce. Die Verifikation eines Bundle-Beweises darin kostete 1,968,619 Zeilen unter Constraint-Set 6 und 2,044,506 unter Constraint-Set 7; unter Set 8 wurde sie nicht neu gemessen. Sie ist implementiert und auf jeder bisherigen Chain, Chain 20 eingeschlossen, per Genesis abgeschaltet. Das Aggregationsprogramm verifiziert nur Bundle-Beweise, sodass bei einer Chain mit geteilter Autorisierung der Auth-Beweis bestehen bliebe. Seine Produktionsbeweise benötigen zudem mehr Speicher als geplant: unter Constraint-Set 7 gemessen 133 GB und mehr (Upcoming: block aggregation).

Was das Verfahren nicht beansprucht

  • Die Quantenmarge beträgt etwa 85 Bit, nicht 128. Die Quanten-Kollisionssuche auf einen n-Bit-Hash kostet etwa 2ⁿ/³. Das Design des Whitepapers sieht einen 384-Bit-Transcript-Hash vor; die laufende Chain verwendet den 256-Bit-Digest von Poseidon2. Ihn zu verbreitern, bedeutet ein neues Constraint-Set und einen Hard Fork.
  • 100 Bit sind eine Vermutung; 86 sind bewiesen. Den bewiesenen Wert auf 100 anzuheben, würde 97 Queries kosten, also etwa ein Fünftel mehr Query-Daten pro Proof.
  • Die Poseidon2-Konstanten sind eine Zufallsziehung mit festem Seed, keine veröffentlichte Instanziierung. Ein Angriff darauf ist nicht bekannt; die Übernahme eines Standardsatzes würde jeden Hash der Chain verändern.
  • Manche Aussagen werden neben dem Proof geprüft, nicht in ihm. Der öffentliche Wert TIER wird vom Verifier mit der Form des Proofs verglichen, aber nicht vom Schaltkreis erzwungen (Audit ZL1), und die Taint-Disziplin des Bundle-Guests stützt sich auf ein strukturelles Argument, ein Harness mit abgeschwächtem Guest und Mutation-Fuzzing, nicht auf maschinelle Prüfung.
  • Der Header eines Proofs ist nicht verborgen. Der Tier und die beiden Höhen der Hash-Tabellen liegen offen, sodass ein Call-Proof zeigt, ob der Call KECCAK oder SHA256 verwendet hat. Eine Prover-Option, die beide Tabellen auffüllt, existiert, wird aber von keiner Wallet gesetzt: Sie bringt einen Tier-10-Proof von etwa 1,3 MB auf etwa 3,6 MB.
  • Der Syscall POSEIDON2 ist über Eingaben variabler Länge nicht injektiv. Er bleibt, wie er ist, weil jedes Commitment, jeder Nullifier, jede Root und jeder Programm-Digest von ihm abhängt. Kein ausgelieferter Guest verwendet bisher POSEIDON2_LEN.
  • Das Beweisen ist langsam und geschieht durch den Sender oder durch einen Prover, mit dem der Sender sich zusammenschließt: etwa 100 s und 5,7 GB auf einer Laptop-CPU für ein Bundle. Die GPU-Kernel liefen am 2026-09-28 erstmals auf einer GPU (einer NVIDIA H100) und stimmten mit der CPU-Referenz überein; die Wallet beweist weiterhin auf der CPU.

Weiterführende Literatur

  • Der Abschnitt „The Zero-Knowledge Scheme: How a Rand Proof Works“ des Whitepapers (randprotocol.tex) führt mit den Soundness-Theoremen durch dieselben sieben Bestandteile.
  • Das Implementierungs-Paper (randprotocol_implementation.tex) spezifiziert den Befehlssatz und die Syscalls, den Bundle-Guest, den Sponge und die Digests, jede Tabelle und jeden Bus, die Maschinenkonfiguration und die Recursion-VM.
  • Der Code: crates/randprotocol-zkvm/src/ im fullnode — emulator.rs, tables/, machine.rs, gas.rs, guests.rs, hash.rs, poseidon2_constants.rs.

Zuletzt geprüft gegen den fullnode-Commit c0878c3f (Chain 21) am 2026-10-12 und fullnode `90e7716` (v0.6.7 on chain 18, `crates/randprotocol-zkvm`, constraint set 8), fullnode `c9c9bd3c` (v0.6.8 on chain 20: ZKV-4 in `crates/randprotocol-zkvm/src/executor.rs`, `binding_domain` in `crates/randprotocol-core/src/types/binding.rs`; no constraint change), fullnode `c0878c3f` (v0.8.0 on chain 21: `docs/zkvm.md` §8a, constraint set 9 — the `MODQ` syscall and the optional `modq` table; `docs/shielded.md` §2, the multisig auth guest's pinned shape), circuits `aeacf31`, `research/docs/02-tables-and-buses.md`, `research/docs/03-privacy.md` (the FRI profile, the degree ceiling, the table floor and the blinded terminals), whitepapers `8f4275b` (`randprotocol.tex` §“The Zero-Knowledge Scheme”, `randprotocol_implementation.tex` §§4.10–4.15). Wo Code und diese Seite voneinander abweichen, hat der Code recht; sagen Sie uns Bescheid.