Technik › Netzwerk

Eine Transaktion Byte für Byte

Vollständige Zerlegung des Hex einer RingCT-Transaktion: jedes Feld, jede Funktion – mit echten und zufälligen Daten

Kurz gesagt

Eine Monero-Transaktion besteht aus drei aufeinanderfolgenden Byte-Blöcken: dem Präfix (Inputs, Outputs, Extra), der RingCT-Basis (Gebühr, verschlüsselte Beträge, Commitments) und dem prunable Teil (Bulletproofs+, CLSAG, Pseudo-Outputs). Unten findest du ein Tool, das jede v2-Transaktion Byte für Byte zerlegt, die Txid berechnet, die Bilanz prüft und – mit einem View Key – „deine“ Outputs findet und ihre Beträge entschlüsselt. Bei einer zufälligen Transaktion zeigt es alle Geheimnisse, sodass du siehst, wie jede Funktion auf echte Zahlen angewendet wird.

Wie man die Bytes liest

Moneros Serialisierung ist einfach – keine Header oder Feldnamen, nur Werte hintereinander. Es gibt zwei Arten von Bausteinen:

  • Varint – eine Ganzzahl variabler Länge: 7 Bit pro Byte, das oberste Bit bedeutet „es folgt noch ein Byte“. 0x02 = 2, 0x80 0x01 = 128, 0x80 0xb5 0x18 = 400000.
  • Key / Punkt / Skalar – genau 32 Bytes, ohne Längenangabe. Auch Arrays von Punkten, deren Länge aus dem Kontext bekannt ist (z. B. CLSAGs s, anhand der Ringgröße), kommen ohne Länge.

Der Parser muss das Format also im Voraus kennen: Der RingCT-Typ, der in der Mitte gelesen wird, bestimmt, wie alles danach zu lesen ist.

Präfix version · unlock_time Inputs: Ring (16 Offsets) + Key Image Outputs: Einmalschlüssel P + View Tag Extra: R, Payment ID… RingCT-Basis type = 6 Gebühr (Klartext) Beträge (8 B, verschl.) outPk = C prunable Bulletproofs+ CLSAG: s×16, c1, D pseudoOuts C̃ kann von einem Pruned Node verworfen werden Keccak Keccak Keccak txid = Keccak(h₁ ‖ h₂ ‖ h₃)

Welche Funktionen wo angewendet werden

FeldBerechnet vonFormelMehr
extra: RSenderR = r·GStealth-Adressen
output: PSenderP = Hs(8·r·A ‖ i)·G + BStealth-Adressen
View TagSenderKeccak("view_tag" ‖ 8rA ‖ i)[0]View Tags
ecdhInfoSenderv ⊕ Keccak("amount" ‖ Hs(8rA ‖ i))[0..8]Pedersen
outPkSenderC = y·G + v·H, y = Hs("commitment_mask" ‖ Hs(8rA ‖ i))Pedersen
RingWallet des Senders16 globale Indizes, Gammaverteilung über das AlterDecoys
Key ImageBesitzer des InputsI = x·Hp(P)Key Image
pseudoOutsSenderC̃ = y'·G + v·H, Masken so gewählt, dass Σ C̃ = Σ C + fee·HRingCT
CLSAGBesitzer des Inputsein Ring von Challenges über P_i und C_i − C̃Ringsignaturen
Bulletproofs+SenderBeweis, dass 0 ≤ v < 2⁶⁴ für alle OutputsRange Proofs

Was ein Node mit diesen Bytes macht

  1. Er parst sie; jedes überzählige oder fehlende Byte führt zur Ablehnung.
  2. Er prüft, dass die Key Images neu sind und in der richtigen Untergruppe liegen.
  3. Er rekonstruiert aus den Offsets die 16 globalen Indizes und lädt Schlüssel und Commitments dieser Outputs aus seiner Datenbank.
  4. Er prüft die Bilanz Σ C̃ = Σ outPk + fee·H – das macht auch das Tool unten.
  5. Er prüft Bulletproofs+ und das CLSAG jedes Inputs.

Was die Wallet des Empfängers macht

  1. Sie nimmt R aus Extra und berechnet das gemeinsame Geheimnis 8·a·R (eine Operation pro Transaktion).
  2. Für jeden Output: ein Hash → View Tag vergleichen; keine Übereinstimmung – Schluss.
  3. Übereinstimmung – Hs(8aR ‖ i)·G + B mit dem Output-Schlüssel vergleichen.
  4. Der Output gehört uns – Betrag entschlüsseln, Maske rekonstruieren und y·G + v·H gegen outPk prüfen.

Das Tool zeigt all diese Schritte mit konkreten Zahlen.

Im Mainnet geprüft

Der Parser wurde mit der JSON-Dekodierung von monerod für echte Transaktionen der Typen 5 und 6 sowie eine Coinbase-Transaktion abgeglichen: Jedes Feld und die Txid stimmen überein. Der Knopf „echte Tx aus dem Mainnet“ lädt eine davon – vergleiche die Txid mit einem beliebigen Block-Explorer.

Wie wird eine FCMP++-Transaktion aussehen?

Das Transaktionsformat von FCMP++ kann sich vor dem Hard Fork noch ändern, deshalb gibt es hier noch keinen Byte-Parser dafür. Aus der aktuellen Implementierung ist die Gesamtstruktur aber klar – und sie ähnelt der heutigen überraschend stark:

Präfix version · unlock_time Inputs: nur Key Image L (keine Ring-Offsets mehr) Outputs: Schlüssel O (Carrot) + 3-Byte-View-Tag Extra: ephemerer Schlüssel, … Basis neuer RingCT-Typ Gebühr Beträge + C Referenzblock (welche Baumwurzel) prunable Bulletproofs+ pro Input: Õ, Ĩ, R, C̃ pro Input: SA+L ein Mitgliedschafts- beweis für alle Inputs statt „16 Indizes + CLSAG“ pro Input – „ein rerandomisiertes Tupel + SA+L“ und ein gemeinsamer Mitgliedschaftsbeweis Aufbau gemäß aktueller Implementierung; Formatdetails können sich vor dem Hard Fork ändern
In RingCTIn FCMP++Funktionen
16 Offsets pro Inputein Verweis auf den Block, gegen dessen Baum der Beweis gebaut wirdCurve-Tree-Wurzel über alle Outputs
Key Image I = x·Hp(P)dasselbe L = x·Hp(O)Doppelausgaben werden wie bisher erkannt
Pseudo-Output C̃C̃ = C + r_c·G – Teil des rerandomisierten TupelsRerandomisierung
CLSAG pro InputSA+L pro Input (ein paar Punkte und Skalare)verallgemeinertes Schnorr
—ein Mitgliedschaftsbeweis für die ganze TransaktionGeneralized Bulletproofs + Divisoren
1-Byte-View-Tag, ECDH auf Ed255193-Byte-View-Tag, X25519, Janus-AnkerCarrot

Rerandomisierung mit echten Zahlen siehst du im Tool. Sobald das Format feststeht, kommt hier auch eine Byte-Zerlegung von FCMP++-Transaktionen dazu.