Technique › Réseau

Une transaction octet par octet

L’analyse complète du hex d’une transaction RingCT : chaque champ, chaque fonction — sur des données réelles et aléatoires

En bref

Une transaction Monero, ce sont trois blocs d’octets consécutifs : le préfixe (entrées, sorties, extra), la base RingCT (frais, montants chiffrés, engagements) et la partie élagable (Bulletproofs+, CLSAG, pseudo-sorties). Ci-dessous, un outil analyse n’importe quelle transaction v2 octet par octet, calcule le txid, vérifie l’équilibre et — avec une clé de visualisation — trouve « vos » sorties et déchiffre leurs montants. Pour une transaction aléatoire, il montre tous les secrets, pour que vous voyiez comment chaque fonction s’applique à des nombres concrets.

Comment lire les octets

La sérialisation de Monero est simple — pas d’en-têtes ni de noms de champs, juste des valeurs les unes après les autres. Deux types de briques :

  • varint — un entier de longueur variable : 7 bits par octet, le bit de poids fort signifie « un autre octet suit ». 0x02 = 2, 0x80 0x01 = 128, 0x80 0xb5 0x18 = 400000.
  • clé / point / scalaire — exactement 32 octets, sans longueur. Les tableaux de points dont la longueur est connue par le contexte (par exemple les s de CLSAG, selon la taille de l’anneau) n’ont pas non plus de longueur.

Le parseur doit donc connaître le format à l’avance : le type RingCT, lu au milieu, décide de la façon de lire tout ce qui suit.

préfixe version · unlock_time entrées : anneau (16 décalages) + key image sorties : clé unique P + view tag extra : R, payment ID… base RingCT type = 6 frais (en clair) montants (8 o, chiffr.) outPk = C élagable Bulletproofs+ CLSAG : s×16, c1, D pseudoOuts C̃ peut être supprimée par un nœud élagué Keccak Keccak Keccak txid = Keccak(h₁ ‖ h₂ ‖ h₃)

Quelles fonctions s’appliquent où

ChampCalculé parFormuleEn savoir plus
extra: Rl’expéditeurR = r·Gstealth addresses
output: Pl’expéditeurP = Hs(8·r·A ‖ i)·G + Bstealth addresses
view tagl’expéditeurKeccak("view_tag" ‖ 8rA ‖ i)[0]view tags
ecdhInfol’expéditeurv ⊕ Keccak("amount" ‖ Hs(8rA ‖ i))[0..8]Pedersen
outPkl’expéditeurC = y·G + v·H, y = Hs("commitment_mask" ‖ Hs(8rA ‖ i))Pedersen
ringle portefeuille de l’expéditeur16 indices globaux, loi gamma sur l’âgeleurres
key imagele propriétaire de l’entréeI = x·Hp(P)key image
pseudoOutsl’expéditeurC̃ = y'·G + v·H, masques choisis pour que Σ C̃ = Σ C + fee·HRingCT
CLSAGle propriétaire de l’entréeun anneau de défis sur P_i et C_i − C̃signatures en anneau
Bulletproofs+l’expéditeurpreuve que 0 ≤ v < 2⁶⁴ pour toutes les sortiespreuves d’intervalle

Ce qu’un nœud fait de ces octets

  1. Il les analyse ; tout octet en trop ou manquant entraîne un rejet.
  2. Il vérifie que les key images sont nouvelles et appartiennent au bon sous-groupe.
  3. Il reconstitue les 16 indices globaux à partir des décalages et charge depuis sa base les clés et engagements de ces sorties.
  4. Il vérifie l’équilibre Σ C̃ = Σ outPk + fee·H — l’outil ci-dessous le fait aussi.
  5. Il vérifie Bulletproofs+ et le CLSAG de chaque entrée.

Ce que fait le portefeuille du destinataire

  1. Il prend R dans extra et calcule le secret partagé 8·a·R (une opération par transaction).
  2. Pour chaque sortie : un hachage → comparaison du view tag ; pas de correspondance — on s’arrête.
  3. Correspondance — on compare Hs(8aR ‖ i)·G + B avec la clé de la sortie.
  4. La sortie est à nous — on déchiffre le montant, on reconstitue le masque et on vérifie y·G + v·H par rapport à outPk.

L’outil montre toutes ces étapes avec des nombres concrets.

Vérifié sur le mainnet

Le parseur a été vérifié par rapport au décodage JSON de monerod sur de vraies transactions de type 5 et de type 6 ainsi que sur une transaction coinbase : chaque champ et le txid correspondent. Le bouton « vraie tx du mainnet » en charge une — comparez le txid avec n’importe quel explorateur de blocs.

À quoi ressemblera une transaction FCMP++ ?

Le format des transactions FCMP++ peut encore changer avant le hard fork, il n’y a donc pas encore de parseur d’octets pour lui ici. Mais d’après l’implémentation actuelle, la structure générale est claire — et étonnamment proche de celle d’aujourd’hui :

préfixe version · unlock_time entrées : key image L seule (plus de décalages d’anneau) sorties : clé O (Carrot) + view tag de 3 octets extra : clé éphémère, … base nouveau type RingCT frais montants + C bloc de référence (quelle racine d’arbre) élagable Bulletproofs+ par entrée : Õ, Ĩ, R, C̃ par entrée : SA+L une preuve d’appartenance pour toutes les entrées au lieu de « 16 indices + CLSAG » par entrée — « un tuple rerandomisé + SA+L » et une preuve d’appartenance commune structure selon l’implémentation actuelle ; les détails du format peuvent changer avant le hard fork
Dans RingCTDans FCMP++Fonctions
16 décalages par entréeune référence au bloc dont l’arbre sert de base à la preuveracine du Curve Tree de toutes les sorties
key image I = x·Hp(P)la même L = x·Hp(O)doubles dépenses détectées comme avant
pseudo-sortie C̃C̃ = C + r_c·G — partie du tuple rerandomisérerandomisation
CLSAG par entréeSA+L par entrée (quelques points et scalaires)Schnorr généralisé
—une seule preuve d’appartenance pour toute la transactionGeneralized Bulletproofs + diviseurs
view tag de 1 octet, ECDH sur Ed25519view tag de 3 octets, X25519, ancre JanusCarrot

Voyez la rerandomisation sur des nombres concrets dans l’outil. Une fois le format définitif, une analyse octet par octet des transactions FCMP++ sera aussi ajoutée ici.