Files
khadhroony-bot3/migration/khadhroony-bot2-reference/docs/SPL_TOKEN_2022_SOURCE_AUDIT.md
2026-07-23 16:37:12 +02:00

15 KiB

Audit des sources Token-2022 résolues

Ancrage reproductible

L'interface Cargo résolue spl-token-2022-interface 3.1.1 publie le commit e18f9c6f9bf6044b934f48e3090e8e59e4820f02, tagué interface@v3.1.1 le 26 juin 2026. L'audit est effectué sur ce commit détaché, jamais sur la branche main mouvante.

Le Cargo.lock du workspace opérateur, SHA-256 248c02af8c78d3bb6ad747460fee0011f8141feef83364cf697e35e44bd4ceb9, confirme que kb_decoder_spl_token_2022 0.4.5 consomme directement les interfaces Token-2022 et registre ElGamal. Pour spl-token-2022-interface 3.1.1, il résout exactement :

  • solana-zk-elgamal-proof-interface 0.1.3 ;
  • spl-token-confidential-transfer-proof-extraction 0.6.1 ;
  • spl-token-group-interface 0.7.2 ;
  • spl-token-metadata-interface 1.0.1 ;
  • spl-type-length-value 0.9.1.

La sortie cargo tree -p spl-token-2022-interface -e features prouve en particulier le chemin de consommation direct de Token Metadata 1.0.1. Cette version diffère du 1.0.0 figé dans le Cargo.lock du processor source audité : les discriminants, layouts et builders devront donc être comparés différentiellement avant le décodeur, sans supposer leur égalité.

La comparaison officielle clôt cette dérive : interface@v1.0.1, publiée le 29 juin 2026 au commit court 7038969, contient le correctif fusionné fc97c60 de la pull request 87. Le diff de production ajoute 78 lignes dans interface/src/instruction.rs sans suppression. Les discriminants, structures de payload et builders existants ne changent pas ; unpack() valide désormais les longueurs Borsh u32 LE avant try_from_slice pour :

  • Initialize : name, symbol, uri ;
  • UpdateField : la chaîne de Field::Key lorsque le tag vaut 3, puis value ;
  • RemoveKey : key, après l'octet booléen idempotent.

UpdateAuthority et Emit ne transportent pas de chaîne et restent inchangés. Le décodeur devra appliquer la garde 1.0.1 avant toute allocation, même si le processor source figé résolvait encore 1.0.0.

Au même commit :

  • program/Cargo.toml déclare le processor spl-token-2022 11.0.0 ;
  • sa dépendance path demande l'interface 3.1.0, satisfaite par le package workspace 3.1.1 ;
  • le Cargo.lock source résout spl-token-group-interface 0.7.2, spl-token-metadata-interface 1.0.0 et spl-elgamal-registry-interface 0.2.1 ;
  • l'égalité du binaire déployé avec cette source reste non prouvée sur Localnet, Devnet et Mainnet.

Dispatch réellement implémenté

Processor::_process_inner applique cet ordre :

  1. PodTokenInstruction, discriminant u8 ;
  2. si ce décodage échoue, TokenMetadataInstruction, discriminant SPL de huit octets ;
  3. si ce décodage échoue, TokenGroupInstruction, discriminant SPL de huit octets ;
  4. sinon InvalidInstruction.

La surface routable contient donc :

Famille Enveloppes ou formes
Tags Token-2022 u8 48
Familles d'extensions parmi ces tags 15
Sous-instructions de ces familles 58
Formes Token-2022 hors enveloppes 33
Instructions Token Metadata exécutées par Token-2022 5
Instructions Token Group exécutées par Token-2022 4
Formes feuilles routables par le processor 100

Les 48 tags ne constituent donc pas, seuls, une déclaration de couverture maximale. Les quinze tags d'extension sont des enveloppes et doivent être remplacés par leurs 58 sous-instructions dans le comptage des formes feuilles.

Interfaces incorporées au processor

Les neuf discriminants ci-dessous sont exécutés par Token-2022 lorsqu'ils ciblent le Program ID TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb :

Interface Instruction Discriminant hexadécimal
Token Metadata 1.0.0 initialize d2e11ea258b84d8d
Token Metadata 1.0.0 update_field dde9312db5cadcc8
Token Metadata 1.0.0 remove_key ea122038598d25b5
Token Metadata 1.0.0 update_authority d7e4a6e45464567b
Token Metadata 1.0.0 emit faa6b4fa0d0cb846
Token Group 0.7.2 initialize_group 79716c2736330004
Token Group 0.7.2 update_group_max_size 6c25ab8ff81e126e
Token Group 0.7.2 update_group_authority a1695801edddd8cb
Token Group 0.7.2 initialize_member 9820deb0dfed7486

Le propriétaire du décodage dépend toujours du Program ID exécutable : une CPI vers un programme tiers implémentant la même interface appartient au décodeur de ce programme tiers. Un TLV pointeur reste un lien et ne transfère pas la propriété du fait métier.

Wire public des instructions hors enveloppes

Les 33 tags qui ne sont pas des enveloppes d'extension ont maintenant une ligne machine-readable dans baseInstructionWireAudit. Les tailles incluent toujours le tag u8. L'interface 3.1.1 encode les options d'instruction de manière compacte, distincte du COption à quatre octets des états de compte : 0 tient sur un octet ; 1 est suivi de 32 octets pour une clé publique ou de huit octets pour un u64.

Ce point donne notamment les tailles exactes suivantes :

  • InitializeMint et InitializeMint2 : 35 octets sans freeze authority, 67 avec ;
  • SetAuthority : 3 octets sans nouvelle autorité, 35 avec ;
  • InitializeMintCloseAuthority : 2 octets sans autorité, 34 avec ;
  • UnwrapLamports : 2 octets sans montant, 10 avec.

GetAccountDataSize et Reallocate consomment tout le suffixe par éléments ExtensionType u16 LE ; un suffixe impair ou un type inconnu est rejeté par l'unpacker d'interface. UiAmountToAmount consomme tout le suffixe comme UTF-8, y compris une chaîne vide au seul niveau de l'interface. Batch conserve tout le suffixe comme wire de records, dont les bornes et la validation processor restent à auditer.

La distinction critique est conservée dans la matrice : TokenInstruction::unpack() appelle unpack_with_rest() puis ignore le reste retourné. Pour les formes à préfixe fixe ou sans payload, un succès de cet unpacker public ne prouve donc pas la consommation de tout le buffer, ni l'acceptation du suffixe par le runtime. Le futur décodeur devra préserver et diagnostiquer tout suffixe borné jusqu'à ce que le chemin exact du processor figé en établisse la règle variante par variante.

Règles de suffixe du processor figé

La comparaison a ensuite été menée sur les trois fichiers exacts du commit, avec leurs empreintes SHA-256 enregistrées dans la matrice :

  • interface/src/instruction.rs : 672b3890…b3a287 ;
  • program/src/pod_instruction.rs : 7b546580…49c7e5 ;
  • program/src/processor.rs : 9d11b834…3032d.

Les 33 formes hors enveloppes forment une partition complète et sans doublon :

Règle processor Tags Nombre
longueur totale Pod exacte 2,3,4,7,8,12,13,14,15,16,18,19,23,35 14
préfixe Pod + option compacte, suffixe accepté 0,6,20,25,45 5
tag seul, suffixe non inspecté et accepté 1,5,9,10,11,17,22,31,32,38 10
reste entier interprété comme payload 21,24,29 3
flux de records Batch 255 1

decode_instruction_data<T>() exige exactement 1 + size_of::<T>() octets. À l'inverse, les helpers privés des options compactes lisent la structure Pod, puis 0, ou 1 suivi de la valeur, sans vérifier l'épuisement du buffer. Pour les dix branches sans données, le processor n'appelle aucun décodeur après la lecture du premier octet. Ces quinze formes acceptent donc réellement un suffixe, même si celui-ci ne porte aucune sémantique publiée. Le décodeur devra le préserver sous forme bornée et le diagnostiquer explicitement, sans créer de champ métier.

Pour GetAccountDataSize et Reallocate, chaque morceau de deux octets doit former un ExtensionType connu ; un dernier morceau incomplet ou un type inconnu échoue. Pour UiAmountToAmount, tout le reste doit être UTF-8, la chaîne vide restant admise à ce niveau.

Batch ne peut pas être vide : le plus petit wire total contient le tag externe, un header de deux octets et au moins un discriminant interne, soit quatre octets. Chaque record encode en u8 son nombre de comptes et la longueur totale de son instruction interne. Un header ou payload tronqué échoue ; une longueur interne nulle échoue au dispatch ; un Batch interne est rejeté par _process_inner. Les fallbacks Token Metadata et Token Group restent en revanche joignables dans un record lorsque leur wire tient dans les 255 octets. Le processor n'impose pas de nombre maximal explicite de records au-delà des limites de transaction et de calcul : le décodeur devra donc poser sa propre borne.

Comptes publiés par les builders de base

La matrice contient désormais une ligne ordonnée pour chacune des 33 formes hors enveloppes. Elle enregistre les metas exactes des 32 helpers Rust publiés : rôle, writable, signer, compte optionnel et queue multisig. Batch reste la seule forme sans helper Rust dédié pour construire ses metas ; TokenInstruction::pack() ne produit que ses données.

Les autorités simples et multisig partagent le contrat classique des builders : l'autorité est signataire lorsque la liste signer_pubkeys est vide ; sinon elle ne signe pas et chaque élément de la queue est ajouté en lecture seule avec le flag signataire. Le builder ne borne pas la taille de cette queue pour les opérations d'autorité et conserve ordre et doublons. Le runtime ne peut toutefois satisfaire qu'un multisig stockant de un à onze positions.

InitializeMultisig et InitializeMultisig2 ont un contrat différent : les clés de configuration ne signent pas. Le builder exige m et n dans 1..=11 ainsi que m <= n. Le processor consomme tous les comptes restants comme clés de configuration, valide séparément m et n, mais ne répète pas le contrôle m <= n. Une instruction manuelle m > n peut donc initialiser un état que le builder refuse. Le décodeur doit conserver ce fait et le diagnostiquer sans le normaliser.

SyncNative publie deux helpers : le premier fournit seulement le compte natif modifiable ; le second ajoute Rent en lecture seule. Le processor traite effectivement le deuxième compte comme optionnel, mais ignore ceux qui suivraient. Batch découpe les comptes selon le compteur u8 de chaque record ; les comptes externes restant après le dernier record ne sont pas rejetés. Ils devront rester visibles comme comptes non assignés.

Le contrat builder de Reallocate est fixé — compte à agrandir modifiable, payer modifiable et signataire, System Program, propriétaire conditionnel puis queue multisig — mais la consommation runtime détaillée reste ouverte jusqu'à reconfirmation du module séparé program/src/extension/reallocate.rs au même commit. Cette limite empêche de déclarer l'audit processor des comptes entièrement clos.

Sources auditées

  • dépôt solana-program/token-2022, commit figé ci-dessus ;
  • interface/src/instruction.rs et interface/src/extension/**/instruction.rs ;
  • interface/src/extension/mod.rs et les états d'extension ;
  • program/src/processor.rs et program/src/extension/**/processor.rs ;
  • dépôt solana-program/token-metadata, commit 552935e00710478541e3394a8b2624c9ba7f503f, tag interface@v1.0.0 ;
  • dépôt solana-program/token-group, commit 2645064953937118572735993ed0ac1d07db5f14, tag interface@v0.7.2.

Limites restantes

Cette tranche confirme le wire public, les règles runtime de suffixe et les metas officielles des builders des 33 formes hors enveloppes. Elle ne clôt pas encore l'audit par variante : consommation processor complète des comptes, données de preuve, builders des extensions, statuts de construction et compatibilité cluster doivent encore être renseignés avant le décodeur.

Première tranche verticale d'extensions

Le décodeur structure désormais exactement douze feuilles issues de quatre enveloppes : les six instructions Transfer Fee, les deux Default Account State, les deux Memo Transfer et les deux CPI Guard. Les discriminants, champs numériques, options compactes et règles de longueur proviennent des interfaces figées au commit audité. Les onze autres enveloppes restent opaques et ne sont pas comptées comme décodées sémantiquement.

Cette tranche ne déclare encore aucune capacité exécuteur d'extension. Conformément à l'ordre du jalon, les projections propriétaires et leurs tests de non-duplication doivent être validés avant d'activer les builders officiels correspondants.

Dispatch incorporé Token Metadata et Token Group

Le dispatch de production ne peut pas se limiter au premier octet de TokenInstruction. Après échec du décodage Token-2022 principal, le processor 11.0.0 audité tente successivement les interfaces Token Metadata puis Token Group. Les neuf discriminants déjà inventoriés dans la matrice sont donc désormais reconnus par le décodeur Token-2022 lorsque le Program ID réellement exécuté est Tokenz... : cinq feuilles Metadata et quatre feuilles Group.

Les chaînes Borsh Metadata sont bornées avant allocation et décodage. Les layouts Pod Group sont traités avec une longueur exacte. Cette intégration ne décode pas un programme tiers implémentant les mêmes interfaces : une CPI vers un autre Program ID reste la propriété du décodeur de ce programme.

L'égalité de surface à maintenir est désormais explicitement compilée et documentée :

33 feuilles hors enveloppes + 58 feuilles d'extensions + 5 Metadata + 4 Group = 100

Audit ElGamal restant

La présence de spl-elgamal-registry-interface 0.2.1, de son Program ID regVY... et de la seed elgamal-registry ne suffit pas à revendiquer sa couverture. La tranche dédiée doit encore fixer les discriminants et le wire exacts, la dérivation PDA, la relation wallet/owner, les clés ElGamal, les références de preuve, le lifecycle create/update, les autorités et signataires, les builders actuels ou expérimentaux et les preuves de déploiement cluster. Toute référence au registre depuis une instruction confidentielle Token-2022 reste un lien inter-programme et non une instruction du registre.