Rand Protocol
Dokumentation
Grundkonzepte

Transaktionen und Actions

Die Transaktion im Netzwerk, ihre ID, alle Actions, die das Ledger kennt, welche davon chain 20 zulässt, und die Reihenfolge, in der ein Node eine Einreichung prüft.

Verwenden Sie diese Seite, um eine Transaktion zu konstruieren, zu übermitteln oder zu dekodieren: die Wire-Form, die rand_sendTransaction entgegennimmt, die Berechnung der ID, die vollständige Liste der Aktionen mit ihren Bundle- und Gebührenregeln sowie die Reihenfolge der Zulassungsprüfung, die bestimmt, welchen Fehler Sie zuerst erhalten.

Die Transaktion

Transaction { chain_id: u64, bundle: Option<Bundle>, action: Action }

Bundle {                                        // the hidden-asset bundle (since chain 14)
  anchor: Word8, nullifiers: [Word8; 4], commitments: [Word8; 4],
  fee: u64, burn_a: u64, burn_r: u64, burn_asset: u32, time: u32,
  envelopes: [Envelope; 4], proof: Vec<u8>,     // postcard(Proof) of the hidden-asset guest
  auth_commit: Word8, auth_proof: Vec<u8>,      // since chain 17: split authorisation's c and its auth proof
}
Envelope { kem_ct: Vec<u8>, to_receiver: Vec<u8>, to_sender: Vec<u8>, body: Vec<u8> }

Word8 besteht aus acht Little-Endian-u32-Wörtern, dargestellt als 64 Hexadezimalzeichen in Kleinbuchstaben. Die Wire-Form ist bincode(Transaction) mit den Standardeinstellungen von bincode (Ganzzahlen fester Breite, 8-Byte-Längenpräfixe, u32-Enum-Tags); rand_sendTransaction nimmt sie als Hex entgegen. Es gibt keine Signatur über die Transaktion und kein Absenderfeld: Ein Bundle autorisiert sich selbst durch seine Beweise (den Bundle-Beweis und, seit Chain 17, den danebenliegenden Auth-Beweis), und eine Aktion, die signiert ist, trägt die Signatur in sich. Replay-Schutz bieten bei Bundles das Nullifier-Set, bei den signierten Staking-Aktionen eine Nonce pro Validator im Register, bei TokenMint und SetAuthority ein mint_nonce pro Token sowie bei den Governance-Aktionen der Bridge deren pause_nonce und list_nonce.

Eine Transaktion enthält höchstens ein Bundle, und jede Aktion, die nicht bundle-los ist, enthält genau eines. Die Slots 0–1 des Bundles tragen einen privaten Vermögenswert und die Slots 2–3 RAND, sodass ein Beweis einen Token bewegt und dessen RAND-Gebühr bezahlt; der Bridge-Burn mit zwei Bundles aus dem früheren Entwurf entfällt: Ein Burn ist ein Bundle (Notes).

Die rand-Wallet baut all dies über randprotocol_client::wallet::{send, submit}: Eingaben auswählen, den Anchor abrufen, die Witnesses aus der eigenen Kopie des Baums der Wallet berechnen, die Ausgaben bauen und alle vier Umschläge versiegeln, das Binding der Transaktion bilden, das Bundle und den Auth-Beweis beweisen, den veröffentlichten Digest des Bundle-Beweises prüfen, übermitteln.

Das Transaktions-Binding

Eine Transaktion hat keine Signatur, und der Beweis eines Bundles bindet nur den eigenen Digest des Bundles: Anchor, Nullifier, Commitments, Gebühr, die Burn-Felder, time. Bis Chain 13 verband nichts ein bewiesenes Bundle mit der Aktion, mit der es mitfuhr, oder mit seinen Umschlägen; wer eine Transaktion sah, konnte also alle Beweis-Bytes behalten und den Rest ändern: das Ziel eines BridgeBurn, den Validator eines Bond, einen Umschlag. Seit Chain 14 wird jeder Bundle-Beweis über das Binding der Transaktion erstellt und gegen dieses verifiziert:

binding(tx) = blake3("rand-tx-bind-1" || bincode(chain_id, bundle', action'))            chains 14 to 19
binding(tx) = blake3("rand-tx-bind-2" || bincode(genesis_hash, chain_id, bundle', action'))  chain 20
                                                                                  as 8 LE u32 words

wobei ' bedeutet, dass jede Bundle-Beweis-Bytefolge durch den leeren Vektor ersetzt wird: bundle.proof, seit Chain 17 bundle.auth_proof und der proof eines Aggregate. Alles Übrige ist enthalten: auth_commit, alle vier Umschläge, jedes Burn-Feld, ein Ziel, ein Validator, ein Empfänger, Signaturen, die Guardian-Attestierung samt ihren Dilithium2-Mitsignaturen und der eigene Beweis eines Call. Die acht Wörter reisen im Public-Input-Segment des Beweises, dessen Digest H_PUB die Schaltung bereits bindet; der Fix benötigte daher keine Änderung an Guest oder Schaltung. Der Knoten führt Machine::verify_public(hc_bundle, binding, proof) aus und weist einen Bundle-Beweis zurück, der eine andere öffentliche Höhe deklariert (public height not the transaction binding's). Transaction::binding ist die eine Funktion, die die Wallet vor dem Beweisen und das Ledger vor dem Verifizieren aufruft. Seit Chain 17 wird der Auth-Beweis über dieselben acht Wörter erstellt und gegen sie verifiziert, sodass auch er nicht auf eine andere Transaktion verschoben werden kann.

Chain 20 bindet den Genesis-Hash. Bis Chain 19 deckten das Binding und die signierten Nachrichten der bundle-losen Aktionen nur die Chain-ID ab; zwei Chains mit gleicher Chain-ID (ein Re-Cut, eine private Kopie) hätten daher die Beweise und Signaturen der jeweils anderen akzeptiert. Der Genesis von Chain 20 setzt binding_domain: 1 (v0.6.8, Audit v6 BIND-1; crates/randprotocol-core/src/types/binding.rs). Der Genesis-Hash steht dann am Anfang jeder Preimage unter einem um eine Version höheren Tag: rand-tx-bind-2 und rand-call-bind-2 für die beiden Bindings und ebenso für den Faucet-Mint (rand-mint-3), Unbond und Withdraw (rand-unbond-2, rand-withdraw-2), die RPL-Mint- und Authority-Nachrichten (rand-rpl-mint-2, rand-rpl-authority-2), die Aggregator-Aktionen und die Governance-Nachrichten der Bridge. Eine Validator-Registrierung auf einer solchen Chain ist stets die v2-Nachricht (Genesis, Chain, Adresse, Payout). Nichts, was für eine Chain bewiesen oder signiert wurde, verifiziert auf einer anderen, gleich welche Chain-ID sie trägt. Die Binding-Wörter sind ein öffentlicher Input, daher hat sich weder ein Guest noch ein Verifier-Schlüssel geändert. Eine Wallet vor v0.6.8 beweist weiterhin gegen die Chain-ID-Form und wird auf Chain 20 zurückgewiesen. rand_getLimits und rand_status liefern binding_domain (1 auf Chain 20, 0 ohne das Feld).

Unter dem Genesis-Flag hardening_v6 (Chains 16 bis 20) ist auch der eigene Beweis eines Call gebunden, durch einen zweiten Digest derselben Konstruktion unter eigener Domain, wobei neben den Beweisen des Bundles auch der Call-Beweis geleert wird:

call_binding(tx) = blake3("rand-call-bind-1" || bincode(chain_id, bundle', action''))   as 8 LE u32 words

Auf Chain 20 lautet der Tag rand-call-bind-2, und der Genesis-Hash steht wie oben am Anfang der Preimage. Der Beweis eines Invoke wird auf dieselbe Weise gebunden, wobei sein deklarierter Übergang in der Preimage bleibt, sodass der Beweis genau die Zustandsänderung, die Auszahlungen und die Empfänger festlegt, die die Transaktion trägt.

Der Call-Beweis trägt diese Wörter in seinem Public-Input-Segment, nach dem Public Input des Programms zur Deploy-Zeit, sofern es einen hat; eine an ein anderes Gebühren-Bundle angehängte Kopie verifiziert daher nicht. Die beiden Bindings sind ineinander verschachtelt: Die Wallet wählt die Notes des Gebühren-Bundles und baut die Transaktion mit leeren Beweisen, beweist den Call gegen das Call-Binding, fügt diesen Beweis ein und bildet erst dann das Binding der Transaktion und beweist das Bundle.

Die Folge für einen Client: Bauen Sie die gesamte Transaktion — Ausgaben, Umschläge, Aktion — vor dem Beweisen. Eine vor dem Fork gebaute Wallet beweist gegen das leere Public-Segment und wird zurückgewiesen. Die bundle-losen Aktionen haben keinen Beweis, der zu binden wäre, und stützen sich auf ihre eigenen Signaturen. Das Binding ist nicht die Transaktions-ID; diese bleibt unverändert.

Die Transaktions-ID

txid = blake3("rand-txid-3" || bincode(view))

wobei view die Transaktion ist, in der die proof-Bytes des Bundles durch blake3(proof) (proof_hash) und seine auth_proof-Bytes durch blake3(auth_proof) (auth_proof_hash, nach auth_commit, das unverändert eingeht) ersetzt sind; jedes andere Feld bleibt unverändert. Die Domain lautete bis Chain 16 rand-txid-2, ohne die beiden Auth-Felder; mit Chain 17 hat sich jede Transaktions-ID geändert. Dass der Beweis über seinen Digest gehasht wird, erlaubt es einem beschnittenen Bundle (Block-Aggregation, auf Chain 20 nicht aktiv), dieselbe ID wie seine Rohform zu behalten; ein beschnittenes Bundle behält seinen Auth-Beweis. Eine bundle-lose Transaktion hasht ihre Bytes unverändert. Dieselbe ID dient als Schlüssel für den Mempool, die Receipts und jede RPC-Abfrage.

Alle Aktionen

Action ist ein bincode-Enum, die Tags folgen der Reihenfolge der Deklaration:

TagAktionFelderBundleMindestgebühr
0None—erforderlichBUNDLE_BASE = 0,001 RAND
1Mintcm, pk, time, r, envelope, amount, minter: PublicKey, signaturemuss fehlen0 (vom Validator signiert; nur Faucet-Chains, amount ≤ 100 RAND)
2Deploybase_pc: u32, words: Vec<u32>, public: Vec<u32>erforderlichbase + 100.000 Einheiten je Programmwort und öffentlichem Wort
3Callprogram, proof, input_envelope: Option<CallEnvelope>erforderlichauf Chain 20 base + gas_price × das deklarierte GAS_LIMIT des Beweises + byte_price je KiB von Beweis und Input-Envelope; base + call_fee(tier) auf einer Chain ohne gas-Abschnitt
4Bondvalidator, amount, registration: Option<Registration>erforderlich, und burn_r == amountbase
5Unbondvalidator, amount, nonce, signaturemuss fehlen0
6Withdrawvalidator, amount, nonce, time, r, envelope, signaturemuss fehlen0 auf der Leitung; die base wird von amount abgezogen
7BridgeAttestattestation, recipient: ShieldedAddress, r, time, asset, envelope, pq_signatures: Vec<PqSignature>erforderlichbase
8BridgeBurnasset, amount, relayer_fee, to_chain: u16, token: [u8; 32], to: [u8; 32]erforderlich, mit burn_asset == asset und burn_a == amountBRIDGE_BURN_FEE = 0,01 RAND
9RegisterAggregatorregistrationerforderlich, burn_r = die Kautionbase
10UnbondAggregatoraggregator, nonce, signaturemuss fehlen0
11WithdrawAggregatoraggregator, nonce, time, r, envelope, signaturemuss fehlen0
12SlashAggregatorzwei signierte Aggregat-Headermuss fehlen0
13Aggregatecovers, proof, aggregator, nonce, time, r, envelope, signaturemuss fehlen0
14RegisterTokenname, symbol, decimals: u8, authority: MintAuthority, initial: Option<InitialMint>, salt: [u8; 32], index: u32erforderlichbase + die registration_fee des Registers (1 RAND auf Chain 20, verbrannt)
15TokenMintasset, amount, recipient: ShieldedAddress, r, time, envelope, nonce, signatureerforderlichbase
16SetAuthorityasset, new: Option<PublicKey>, nonce, signatureerforderlichbase
17TokenBurnasset, amounterforderlich, mit burn_asset == asset und burn_a == amountbase
18PauseMintsnonce, signaturemuss fehlen0
19UnpauseMintsnonce, pq_signaturesmuss fehlen0
20RegisterBridgedTokenname, symbol, salt, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signatureserforderlichbase + registration_fee
21ListBackingtoken_index, chain: u16, token: [u8; 32], decimals: u8, nonce, pq_signatureserforderlichbase
22RotatePqGuardiansnew_pq_guardians: Vec<PublicKey>, nonce, pq_signaturesmuss fehlen0
23RotatePauseKeynew_pause_key: PublicKey, nonce, pq_signaturesmuss fehlen0
24ClaimVestedentry: [u8; 32], amount, nonce, to: ShieldedAddress, time, r, envelope, signaturemuss fehlen0 auf der Leitung; die base wird von amount abgezogen
25RevokeVestingentry: [u8; 32], unvested, nonce, to: ShieldedAddress, time, r, envelope, signatures (seit v0.6.8 ein Schwellenwert der Widerrufsberechtigten des Eintrags, die an die im Genesis-Eintrag benannte Treasury zahlen; zuvor eine einzelne signature)muss fehlen0 auf der Leitung; die base wird von unvested abgezogen
26BondVestedentry: [u8; 32], validator, amount, registration: Option<Registration>, nonce, signaturemuss fehlen0
27UnbondVestedentry: [u8; 32], amount, nonce, signaturemuss fehlen0
28AdmitValidatorcandidate: PublicKey, signatures: Vec<(PublicKey, Signature)>muss fehlen0
29SlashEquivocationfirst, second: zwei signierte Block-Header eines Leaders für einen Viewmuss fehlen0
30RotatePqGuardiansV2new_pq_guardians: Vec<PublicKey>, possession: Vec<Vec<u8>>, nonce, pq_signaturesmuss fehlen0
31RotatePauseKeyV2new_pause_key: PublicKey, possession: Vec<u8>, nonce, pq_signaturesmuss fehlen0
32CancelRotationkind: u8, nonce, signaturemuss fehlen0
33Invokeprogram, proof, input_envelope: Option<CallEnvelope>, transition: Transitionerforderlich; burn_r und burn_a von burn_asset sind das, was in das Programm gelangtdie Mindestgebühr eines Calls + cell_fee je erzeugter Zelle
MintAuthority = None | Key(PublicKey) | Bridge { backings: Vec<Backing> } | Program(Hash)
InitialMint   { amount: u64, recipient: ShieldedAddress, r: Word8, time: u32, envelope: Envelope }
PqSignature   { index: u8, signature: Vec<u8> }   // one guardian's Dilithium2 co-signature, by set index

Auf Chain 20 sind die Tags 0 bis 8, 14 bis 21, 28 sowie 30 bis 33 nutzbar. Ihr Genesis hat einen bridge-Abschnitt mit rules_v2, einen tokens-Abschnitt, staking.admission_by_vote (Tag 28), bridge.rotation (Tags 30 bis 32) und program_state (Tag 33). Die Tags 22 und 23 werden dort abgelehnt: Unter bridge.rotation.needs_possession muss eine Rotation die …V2-Form sein, die die eigene Signatur jedes neuen Schlüssels trägt. Auf Chain 19, die keinen dieser Abschnitte hatte, waren die Tags 0 bis 8 und 14 bis 23 nutzbar (Chain 14 ohne rules_v2 lehnte 22 und 23 ab). SlashEquivocation benötigt einen staking.slashing-Abschnitt, und der von Chain 20 ist null. Die fünf Aggregationsaktionen benötigen einen aggregation-Abschnitt, den Chain 20 nicht hat (ein Build verweigert den Start mit einem Genesis, das einen solchen enthält, bis die Aggregation neu vermessen ist). Ein Node lehnt all diese Aktionen am Gate vor jeder anderen Prüfung ab, doch sie belegen weiterhin ihre bincode-Tags, sodass ein Encoder, der Tag-Nummern fest einprogrammiert, sie mitzählen muss. Die Aggregation ist unter Proofs als künftige Funktion dokumentiert. Die vier Genesis-Vesting-Aktionen (Tags 24 bis 27, im Build seit v0.5.11) benötigen einen vesting-Abschnitt, den bisher keine Chain hatte, Chain 20 eingeschlossen, und werden ohne ihn bei der Zulassung abgelehnt. Die Tags 28 bis 33 sind neu in v0.6.8 und werden zuletzt angehängt, sodass die Historie jeder früheren Chain weiterhin dekodiert werden kann.

Die vier RPL-Aktionen (Tags 14 bis 17) registrieren einen Token, prägen mehr von einem Key-autorisierten Token, übergeben oder verzichten auf dessen Mint-Berechtigung und verbrennen einen Bestand; ein RegisterToken, das eine Bridge- oder Program-Berechtigung benennt, wird abgelehnt. Es gibt keine Aktion für Token-Transfers: Ein Token-Transfer ist ein None, nicht von einer RAND-Zahlung zu unterscheiden. Siehe RPL tokens. Die vier Bridge-Governance-Aktionen (Tags 18 bis 21) sind die Mint-Pause, deren Aufhebung sowie die Listung eines überbrückten Tokens oder eines weiteren Backings nach dem Genesis. PauseMints wird vom Pause-Schlüssel des Genesis signiert, der nur pausieren kann; die anderen drei benötigen ein Quorum der Dilithium2-(PQ-)Guardians. Auch ein BridgeAttest trägt ein solches Quorum, neben seinem ECDSA-Quorum. Die Tags 22 und 23 ersetzen unter rules_v2 der Bridge die PQ-Guardian-Menge beziehungsweise den Pause-Schlüssel, jeweils unter einem PQ-Quorum der aktuellen Menge und dem rotation_nonce der Bridge. Die Tags 30 und 31 tun dasselbe unter bridge.rotation und tragen zusätzlich die eigene Dilithium2-Signatur jedes neuen Schlüssels über die Rotationsnachricht, einen Nachweis, dass der Schlüssel gehalten wird; auf Chain 20 wird eine Rotation als ausstehend verzeichnet und wird nach delay_secs wirksam, 86.400 s. CancelRotation (Tag 32), vom aktuellen Pause-Schlüssel signiert, verwirft eine ausstehende Rotation, bevor sie wirksam wird. Siehe Bridge.

AdmitValidator (Tag 28) ist die Abstimmung der Validator-Menge über die Aufnahme eines neuen Schlüssels: Signaturen von Validatoren, die mehr als zwei Drittel des Stakes halten, in aufsteigender Adressreihenfolge. Unter staking.admission_by_vote (Chain 20) wird ein Bond, der einen neuen Validator registrieren würde, abgelehnt, sofern dessen Adresse nicht auf diese Weise zugelassen wurde (Staking). Invoke (Tag 33, RPL-2) ist ein Call, dessen Beweis für genau einen deklarierten Zustandsübergang transition des Programms bürgt: gelesene und geschriebene Zellen, was die Burn-Felder des Bundles in den Vault des Programms einbringen, und die Notes, die der Vault auszahlt oder das Programm prägt. Das Ledger wendet den Übergang an, wenn jede deklarierte Lesung weiterhin gilt und beide Beweise verifiziert werden (Programs).

Ein Deploy und ein Call sind gewöhnliche abgeschirmte Transaktionen, deren Bundle ein Selbsttransfer von null ist: Es existiert, um die Mindestgebühr aus den eigenen Notes des Aufrufers zu zahlen, sodass die Chain nie erfährt, wem sie gehörten. RAND verlässt den Pool nur über burn_r — den Stake eines Bond, die Kaution eines RegisterAggregator, die Einzahlung eines Invoke in einen Programm-Vault — und ein Token nur über burn_a, wobei burn_asset ihn benennt, bei einem TokenBurn, einem BridgeBurn oder einem Invoke. Jede andere Aktion verbrennt nichts, und ein Bundle mit burn_asset = 0 und burn_a ungleich null wird überall abgelehnt.

Reihenfolge der Zulassung

Ledger::validate_inner prüft Billiges vor Teurem, in dieser Reihenfolge; der Mempool führt dieselbe Prüfung durch, bevor er weiterverbreitet, sodass eine fehlerhafte Transaktion nur einmal, am Rand, abgelehnt wird.

  1. Größenlimits: die gesamte Transaktion ≤ max_block_bytes der Chain; jeder der vier Umschläge des Bundles und jeder Note-Umschlag, den die Aktion trägt, genau der envelope_bytes-Wert aus dem Genesis (1.860 auf Chain 20; höchstens 2.048 Bytes auf einer Chain ohne dieses Feld); der Bundle-Proof, der Auth-Proof und ein Call-Proof jeweils ≤ max_proof_bytes (standardmäßig 2 MiB, 4 MiB im Genesis von Chain 20; auf den Chains 13 bis 16 waren es 8 MiB); ein Programm ≤ dem Programmlimit der Chain (standardmäßig 4.096 Wörter, 65.535 im Genesis von Chain 20); ein Call-Umschlag ≤ max_call_envelope_bytes (standardmäßig 18.432 Bytes, 65.536 auf Chain 20); eine Attestierung ≤ 16 KiB. Ein Bundle-Proof in der versiegelten (gekürzten) Markerform wird hier außerhalb der Synchronisierung im versiegelten Format abgelehnt (PrunedFormOutsideSync), noch bevor irgendeine Proof-Arbeit anfällt.
  2. Chain-ID stimmt überein.
  3. Form und Gebührenuntergrenze: Eine Aktion ohne Bundle trägt kein Bundle, jede andere muss genau eines tragen; check_burn_shape legt fest, welches der drei Burn-Felder die Aktion setzen darf: Ein TokenBurn oder BridgeBurn verbrennt sein eigenes asset und amount über burn_asset und burn_a, ein Bond oder RegisterAggregator verbrennt seinen Betrag über burn_r, und jede andere Aktion verbrennt nichts; fee ≥ fee_floor(action). Auf einer Chain mit einem gas-Abschnitt (Chain 20) ist die Untergrenze eines Call oder Invoke in diesem Schritt die Gas-Regel bei einem deklarierten Limit von 1, BUNDLE_BASE + gas_price + byte_price × ⌈bytes / 1024⌉ über Proof und Eingabe-Umschlag, die vor jeder Verifikation bekannt sind.
  4. Anker: eine der letzten 1.024 Block-End-Wurzeln auf Chain 20, deren Genesis proof_window_blocks: 1024 setzt (v0.6.8); eine der letzten 256 auf einer Chain ohne das Feld, wie bis Chain 19.
  5. Zeit: innerhalb von [height − 1024, height] auf Chain 20; [height − 256, height] ohne das Feld. Der eine Genesis-Wert legt beide Fenster fest.
  6. Nullifier und Commitments: Alle vier Nullifier sind verschieden und keiner ist ausgegeben; alle vier Commitments sind verschieden und keines ist bereits ein Blatt.
  7. Aktionsprüfungen: Der Faucet ist aktiviert, der Mint liegt unter dem Limit und ist von einem Validator signiert, nennt auf Chain 20 einen Empfänger auf der Allowlist staking.faucet_recipients des Genesis, passt in das faucet_budget_per_epoch der Epoche und ist von einem Validator auf der Liste staking.faucet_minters des Genesis signiert (seit Chain 16 eine Gültigkeitsregel); ein Mint oder Deposit ab 2⁶³ wird auf jeder Chain abgelehnt; das Programm lässt sich dekodieren; das Programm existiert; die Staking-Regeln (Registrierung genau dann vorhanden, wenn der Validator unbekannt ist, die Nonce, die Signatur, ausreichend Stake); die eigenen Regeln der Bridge für BridgeAttest und BridgeBurn sowie für die Pausen- und Listing-Aktionen; die Regeln von RPL für seine vier Aktionen, einschließlich der Registrierungsgebühr; auf Chain 20 das Quorum der Zulassungsabstimmung und, für ein Invoke, die Form des Übergangs, die deklarierten Lesezugriffe gegen die gespeicherten Zellen, die Deckung des Vaults für jede Auszahlung und die Zellgebühr. All das läuft vor dem Proof des Bundles, sodass eine Transaktion, die sich nicht anwenden lässt, keine STARK-Verifikation kostet. Unter hardening_v6 (Chains 16 bis 20) kommen hier zwei weitere Ablehnungen hinzu: ein Deploy eines Programms, das kein Call halten könnte (ProgramUncallable), und ein Bundle- oder Call-Proof, dessen Header oder Transkript ein Feld trägt, das der ehrliche Prover nicht schreiben würde (non-canonical proof: …), abgelehnt, bevor einer der beiden Proofs verifiziert wird. Seit v0.6.8 wird ein Proof, dessen Header einen Grad oberhalb der Obergrenze der höchsten Tabelle deklariert, zuerst unter demselben Urteil abgelehnt (Audit v6 ZKV-4), bevor die Prüfung ihn durchläuft.
  8. Bundle-Digest: Auf einer Chain mit getrennter Autorisierung (jede Chain seit 17) werden zuerst die Auth-Felder geprüft: Ein Bundle ohne Auth-Proof wird abgelehnt, das c, das der Auth-Proof veröffentlicht (gelesen, noch nicht verifiziert), muss auth_commit entsprechen, und auf Chain 20 muss der Auth-Proof ein GAS_LIMIT von genau 1.279 deklarieren. Dann berechnet das Ledger den Digest der veröffentlichten Felder des Bundles neu, darunter auth_commit, und er muss dem entsprechen, was der Proof veröffentlicht hat. Auf Chain 20 muss auch der Bundle-Proof ein GAS_LIMIT von genau dem bundle_gas_limit des Genesis deklarieren, 20.479.
  9. Bundle-Proof: Machine::verify_public gegen das im Genesis festgelegte hc_bundle und das Binding der Transaktion. Auf einer Chain mit getrennter Autorisierung wird als Nächstes der Auth-Proof verifiziert, gegen das im Genesis festgelegte hc_auth und dasselbe Binding.
  10. Call-Proof und seine Tier-Gebühr: Für ein Call oder ein Invoke wird zuerst der Header des Proofs festgelegt (Tier höchstens 14, eine keccak-Tabelle höchstens 2¹², eine sha256-Tabelle höchstens 2¹³, die Höhe des bereitgestellten Programms, unter hardening_v6 auf mindestens 2⁷ Zeilen angehoben, und auf Chain 20 ein deklariertes GAS_LIMIT, das die Obergrenze gas_max des Headers nicht überschreitet), dann der STARK des Calls gegen den code_hash des Programms und, unter hardening_v6, das Call-Binding der Transaktion. Die Gebühr wird zuletzt geprüft, weil sie sich erst erkennen lässt, wenn der Proof dekodiert ist: Auf Chain 20 gilt fee ≥ BUNDLE_BASE + gas_price × GAS_LIMIT + byte_price × ⌈bytes / 1024⌉ zu den geltenden Preisen, zuzüglich cell_fee je erzeugter Zelle bei einem Invoke (Gebühren); auf einer Chain ohne gas-Abschnitt gilt fee ≥ BUNDLE_BASE + call_fee(tier).

Fehler kommen von rand_sendTransaction als Code -32000 mit der Begründung zurück: nullifier already spent, anchor is not one of the last 1024 roots (256 auf einer Chain ohne proof_window_blocks), time 12 is outside [476, 1500], fee 1000000 below minimum 2000000, the bundle's digest is not what its proof published, invalid bundle proof: …, unknown program …, already in mempool, conflicts with a pending transaction over <nullifier>, faucet is disabled on this chain und seit Chain 14 the bundle burns asset 2 on an action that burns no token, a burn of 5 is not allowed on this action, the bundle burns 399, not the 400 the action declares, wrong mint nonce: expected 1, got 0, wrong token index: expected 3, got 2, seit Chain 17 this chain requires an auth proof on every bundle (genesis hc_auth) und the auth proof publishes a different commitment than the bundle's auth_commit sowie auf Chain 20 envelope is 1348 bytes, this chain requires exactly 1860 und bundle proof declares gas limit …, this chain requires exactly 20479. Annahme ist nicht Commitment: Rufen Sie rand_getTransaction so lange ab, bis es einen Block zurückgibt.

Ein Block, der eine ungültige Transaktion enthält, ist ungültig. Der Mempool indiziert jeden ausstehenden Nullifier und jedes ausstehende Commitment und lehnt einen Konflikt grundsätzlich ab, sodass zwei Bundles, die dieselbe Note ausgeben, nie in einem Block landen.

Größen

TeilBytes
Word832
ein Envelope1.892 kodiert (1.860 Chiffretext) seit Chain 18; 1.380 (1.348) bis Chain 17
Bundle ohne seine zwei Proofs7.936 (vier Nullifier, vier Commitments, vier Umschläge, auth_commit)
Transfer-Transaktion ohne ihre zwei Proofs7.949
Deploy-Transaktion ohne ihre zwei Proofs, Programm mit 100 Wörtern, ohne öffentliche Eingabe8.369
Call-Transaktion ohne ihre drei Proofs, ohne Eingabe-Umschlag7.990
Mint-Transaktion (ohne Bundle)5.761 (ein 1.312 Byte großer Dilithium2-Schlüssel und eine 2.420 Byte große Signatur)
ein Bundle-Proof, Tier 14, Produktionsprofil1.497.544 und 1.504.260 auf Chain 18 gelesen (ein Deploy bei Höhe 97.821 und ein Call bei 98.335, 2026-09-30); 1.422.116 und 1.423.170 auf Chain 14 gemessen (die zUSD-Registrierung und ein Backing-Listing, 2026-09-20)
ein Auth-Proof, Tier 10, Produktionsprofil1.370.827 und 1.367.303 in denselben zwei Transaktionen auf Chain 18

Die ersten sechs Zeilen sind die Kodierung seit Chain 18: Das Memo-Feld hat jeden Umschlag um 512 Bytes vergrößert, und die getrennte Autorisierung hat dem Bundle vor den eigenen Bytes des Auth-Proofs 40 Bytes hinzugefügt. Ein abgeschirmter Transfer umfasst daher seit Chain 18, Chain 20 eingeschlossen, auf dem Draht etwa 2,88 MB, im Wesentlichen ausschließlich Proof, gleich welchen Vermögenswert er bewegt: etwa 1,5 MB Bundle-Proof und 1,37 MB Auth-Proof. Bis Chain 15 waren es etwa 1,43 MB mit einem Proof. Blöcke sind auf 2.000 Transaktionen (MAX_BLOCK_TXS) und auf max_block_bytes der Chain an Transaktionsbytes begrenzt, beides Konsensregeln: standardmäßig 4 MiB (MAX_BLOCK_BYTES), was einen solchen Transfer fassen würde, und 20 MiB im Genesis von Chain 20, das Platz für sieben bietet. Ein Genesis mit hc_auth muss max_block_bytes auf mindestens das Dreifache von max_proof_bytes plus 1 MiB setzen, da eine Transaktion drei Proofs tragen kann (Bundle, Auth, Call); der Standardwert von 4 MiB wird dort daher abgelehnt. Die Aggregation auf Blockebene ist die geplante Abhilfe; siehe Proofs.

Eine Transaktion zurücklesen

rand_getTransaction(hash) gibt null zurück, bis die Transaktion committet ist, danach ihre Blockposition und nur die öffentlichen Felder:

{
  "height": 192, "index": 0, "block_hash": "63f6…08",
  "tx": {
    "hash": "4f2c…e7", "chain_id": 20,
    "bundle": {
      "anchor": "6b1d…c4",
      "nullifiers": ["8c04…d1", "5e77…20", "03aa…6f", "e19b…42"],
      "commitments": ["2a9f…07", "b310…88", "77c1…0e", "5d20…b3"],
      "fee": "1000000", "burn_a": "0", "burn_r": "0", "burn_asset": 0, "time": 5,
      "proof_len": 1497544, "envelope_len": [1860, 1860, 1860, 1860],
      "auth_commit": "0bab…e8a1", "auth_proof_bytes": 1370827
    },
    "action": { "kind": "none" }
  }
}

In einem Transfer gibt es kein from, kein to, keine Nonce, keinen Betrag und kein asset-Feld, und auch im gespeicherten Block steht nichts davon: Der Knoten hat nichts mehr zu schwärzen. auth_commit und auth_proof_bytes (die Länge des Auth-Proofs) gibt es seit chain 17. Das Bundle hat immer vier Slots, Dummys eingeschlossen, und sowohl ein Transfer von RAND als auch ein Transfer eines beliebigen RPL-Tokens sind "kind": "none". Jeder u64-Betrag ist seit chain 14 ein Dezimal-String (fee, burn_a, burn_r, der amount einer Action); Indizes, Nonces, Höhen und Längen bleiben JSON-Zahlen. Die übrigen Action-Arten geben ihre öffentlichen Felder aus – mint, deploy, call, bond, unbond, withdraw, bridge_attest, bridge_burn, register_token, token_mint, set_authority, token_burn, pause_mints, unpause_mints, register_bridged_token, list_backing, rotate_pq_guardians, rotate_pause_key, die vier Vesting-Arten claim_vested, revoke_vesting, bond_vested, unbond_vested und seit v0.6.8 admit_validator, slash_equivocation, rotate_pq_guardians_v2, rotate_pause_key_v2, cancel_rotation und invoke – wie im JSON-RPC-Referenzdokument aufgeführt.

Zuletzt geprüft gegen den fullnode-Commit c0878c3f (Chain 21) am 2026-10-12. Wo Code und diese Seite voneinander abweichen, hat der Code recht; sagen Sie uns Bescheid.