Livre blanc · 08
Cadre de sécurité
Modèle de menaces, invariants, contrôle d'accès, frontière de confiance des hooks, gestion des clés de mise en application, sûreté des mises à niveau et posture d'audit de l'implémentation de référence d'A-REX Core.
La posture de sécurité en un paragraphe
Le jeton de référence possède un seul point de passage obligé (_update), un ordre d'évaluation fixe, un verrou de réentrance autour de toute la séquence de hooks, une autorité fondée sur les rôles qui n'est jamais dérivée des soldes, un stockage ERC-7201 et une surface réduite : huit fonctions externes modifiant l'état en plus d'ERC-1155. Les capacités les plus puissantes — gel, transfert forcé, administration des hooks, mises à niveau — sont des rôles distincts destinés à des multisigs et à des timelocks, et chacun de leurs usages est un événement.
Invariants
| Invariant | Garanti par | Testé par |
|---|---|---|
totalSupply(id) est égal à la somme des soldes pour id | OpenZeppelin ERC1155Supply ; transform détruit puis émet via _update | tests de filiation (fractionnement, fusion, traitement) |
status == 255 est terminal | setBatchStatus annule la transaction avec ARexInvalidStatusTransition | tests de statut |
| Un lot retiré ne peut être ni émis ni transféré, seulement détruit | _update | test du lot retiré |
amount ≤ balance − frozen pour les mouvements initiés par le détenteur | _update ; forcedTransfer abaisse frozen à balance − amount | tests de gel et de transfert forcé |
| Aucun hook ne peut ré-entrer dans une fonction modifiant les soldes | verrou de stockage posé avant les vérifications, levé après les notifications | test ReentrantHook |
Chaque modification de solde est évaluée comme sa propre opération, avec ses propres data | _update lit et efface le contexte d'opération avant toute vérification ou tout appel externe | OpContext.test.js |
| Les hooks sont évalués dans l'ordre d'enregistrement ; la suppression préserve l'ordre | décalage du tableau dans removeHook | test d'ordre |
| Une opération rejetée ne modifie aucun solde et n'émet aucun transfert | les vérifications s'exécutent avant super._update | test de rejet (compteur d'exécutions inchangé) |
| L'autorité ne dérive jamais du solde | chaque fonction modifiant l'état est conditionnée à un rôle ou à une approbation ERC-1155 | tests « détenir des jetons ne confère rien » |
canTransfer == (transferCheck == 0) | canTransfer est défini ainsi | tests de conformité |
Modèle de menaces
Acteurs et capacités
| Acteur | Peut | Ne peut pas |
|---|---|---|
| Détenteur | transférer des unités non gelées à des destinataires éligibles ; détruire ; réorganiser des lots de même denrée et de même unité avec quantité conservée vers son propre compte (to == from) | modifier le statut, émettre, traiter pour obtenir une autre denrée, réorganiser des lots vers un autre compte, déplacer des unités gelées |
| Opérateur approuvé | comme le détenteur, pour le compte qui l'approuve | tout ce qu'un détenteur ne peut pas faire |
ISSUER_ROLE | créer des lots | déplacer les jetons d'autrui |
TRANSFORMER_ROLE | effectuer des transformations de traitement, et des transformations dont les sorties vont à un autre compte, sur les jetons qu'il détient ou pour lesquels il est approuvé | transformer des jetons sans approbation |
STATUS_ROLE | modifier le statut (y compris retirer), mettre à jour les métadonnées | déplacer des jetons |
ENFORCER_ROLE | geler n'importe quel montant ; forcer n'importe quel transfert | contourner les hooks (ils s'exécutent avec OP_FORCED_TRANSFER) |
HOOK_ADMIN_ROLE | ajouter/retirer des hooks | modifier directement les soldes |
PAUSER_ROLE | tout interrompre sauf les transferts forcés | — |
UPGRADER_ROLE | remplacer l'implémentation | — (c'est un pouvoir total ; voir la gestion des clés) |
ATTEST_ADMIN_ROLE | décider quels émetteurs sont de confiance pour chaque thème d'attestation ; dans les versions de référence jusqu'à 2.0.0-alpha.4, révoquer aussi n'importe quelle attestation (ADR-019, Proposé : une révocation administrative NE DEVRAIT PAS exister en 2.x et NE DOIT PAS exister à partir de 3.0) | attester au nom d'un émetteur |
| Émetteur de confiance | attester des lots pour ses thèmes ; révoquer ses propres attestations, ou déléguer la révocation | attester pour un thème pour lequel il n'est pas de confiance |
| Contrat de hook | rejeter des opérations ; les observer ; répondre sur l'éligibilité | ré-entrer ; modifier les soldes |
Scénarios
Hook malveillant ou défectueux (déni de service). Un hook qui rejette toujours bloque tous les transferts du jeton ; un hook qui annule la transaction dans check produit le même effet. Atténuation : HOOK_ADMIN_ROLE peut retirer n'importe quel hook à tout moment (ordre préservé) ; PAUSER_ROLE peut tout interrompre pendant l'enquête ; les transferts forcés s'exécutent toujours, de sorte que la mise en application n'est pas perdue. L'enregistrement vérifie la prise en charge d'ERC-165 et des drapeaux non nuls, et rejette les EOA. Recommandation : auditer les hooks avant de les enregistrer ; en garder un petit nombre ; placer l'administrateur derrière un timelock afin que les changements de hooks soient visibles avant leur prise d'effet.
Réentrance d'un hook. Le onExecuted d'un hook rappelle safeTransferFrom. Atténuation : le verrou ; l'appel annule la transaction avec ARexReentrantCall. Notez que le callback de réception d'ERC-1155 (onERC1155Received) s'exécute après la levée du verrou, comme dans tout jeton ERC-1155 ; l'état du jeton est alors définitif.
Contexte d'opération dans les callbacks de réception. Un destinataire de type contrat lance un transfert ou une destruction depuis onERC1155Received. Cet appel imbriqué est un OP_TRANSFER ou un OP_BURN avec ses propres data, et non une partie de l'opération qui a livré les jetons. Jusqu'à 2.0.0-alpha.2, le jeton de référence conservait l'opération externe en stockage jusqu'au retour de l'appel externe : un appel imbriqué après un forcedTransfer s'exécutait donc en tant que OP_FORCED_TRANSFER (en contournant la pause, l'éligibilité et les soldes gelés), et un appel après un transform en tant que OP_TRANSFORM, que les hooks qui ne contrôlent que OP_TRANSFER laissent passer. Atténuation : _update lit et efface le contexte d'opération avant toute vérification ou tout appel externe, de sorte qu'un appel lancé depuis un callback est évalué pour lui-même ; OpContext.test.js utilise un destinataire qui transfère et détruit depuis son callback.
Livraison par réorganisation de lots. Les deux mouvements d'un transform exécutent les hooks avec OP_TRANSFORM. Si un détenteur pouvait réorganiser des lots vers un autre compte, des biens changeraient de mains sans les vérifications des hooks qui ne contrôlent que OP_TRANSFER, comme une attestation exigée avant la vente ou des unités verrouillées pour une livraison. Atténuation : sans TRANSFORMER_ROLE, une réorganisation de lots n'est permise que si to == from ; les biens réorganisés parviennent à un autre détenteur par un transfert ordinaire qui exécute toutes les règles de transfert (ADR-007, amendé le 2026-10-20).
Contourner un verrou par destruction ou réorganisation de lots. L'état d'un hook conservé par identifiant de lot ne suit pas un lot dans ses sorties. Un hook qui verrouille des unités et ne contrôle que OP_TRANSFER n'empêche pas le détenteur de détruire les unités verrouillées, ni de les réorganiser sur place dans un nouvel identifiant que le verrou ne couvre pas. Atténuation : DeliveryHook rejette avec IN_TRANSIT toute destruction ou tout mouvement d'entrée d'un transform qui ferait passer le solde sous les unités verrouillées ; les transferts forcés restent illimités, et le hook renvoie un code au lieu d'annuler la transaction après une saisie. Les hooks qui réservent des unités devraient contrôler OP_BURN et OP_TRANSFORM en plus de OP_TRANSFER.
Clé de mise en application compromise. Quiconque détient ENFORCER_ROLE peut déplacer n'importe quels jetons. Atténuation : multisig avec une procédure documentée (ordonnance judiciaire, contrat de nantissement, avis de sanctions) ; chaque action émet ForcedTransfer/Frozen ; les hooks peuvent exiger une référence d'ordonnance dans les données de transfert (OP_FORCED_TRANSFER n'est pas exempté des hooks). Envisager un timelock pour la mise en application non urgente.
Compromission du rôle de mise à niveau. Compromission totale. Atténuation : multisig plus timelock ; validations de mise à niveau d'OpenZeppelin en CI ; stockage ERC-7201 pour éviter les accidents de disposition.
Abus de statut. STATUS_ROLE peut retirer un lot détenu par d'autres. C'est intentionnel — un entrepôt, un inspecteur ou un régulateur déclarant un lot impropre — et cela doit être communiqué aux détenteurs. Atténuation : l'attribuer à une institution responsable ; chaque changement est un événement accompagné d'un motif.
Fraude de l'émetteur. ISSUER_ROLE peut créer un lot qui n'existe pas physiquement. Le jeton ne peut pas l'empêcher ; il le rend imputable (issuer, originRef, metadataHash) et permet d'exiger avant tout transfert, via AttestationHook, des attestations de parties indépendantes (entrepôt, inspecteur, registre).
Localisation cachée dans la référence d'origine. Un hash, un geohash ou tout autre encodage des coordonnées d'une parcelle dans originRef semble anonyme, mais il peut être recalculé à partir d'une parcelle devinée et ne peut jamais être effacé de la chaîne. Atténuation : originRef est une référence à des preuves ou une référence aléatoire ; la géolocalisation reste off-chain (pour l'EUDR, dans le système d'information de l'UE). Le projet proposé avec l'ADR-019 (Proposé) fait de « jamais dérivée de la localisation » une exigence de niveau MUST ; c'était déjà la décision de l'ADR-008.
Références en amont non vérifiées. Un lot fabriqué à partir du lot d'un autre jeton le désigne dans la liste upstream de ses métadonnées. C'est une affirmation de l'émetteur en aval : il peut désigner un lot qu'il n'a jamais détenu ou en omettre un qu'il a détenu. Atténuation : les indexeurs, les auditeurs et les outils de rappel vérifient chaque référence auprès du jeton en amont (le lot existe, le hash des métadonnées correspond, le transfert vers l'émetteur a bien eu lieu lorsqu'un hash de transaction est fourni) et signalent comme non vérifiées celles qu'ils n'ont pas pu vérifier.
Substitution de métadonnées. setBatchMetadata modifie le hash et l'URI. Atténuation : accès réservé à un rôle ; les événements BatchMetadataUpdated et URI conservent l'historique ; les documents antérieurs restent vérifiables par rapport aux hashes antérieurs.
Front-running de transferCheck. Une vérification préalable peut devenir obsolète avant l'exécution de la transaction (l'état d'un hook change). La transaction réévalue ; le pire cas est un revert avec un code précis.
Griefing de gaz via les hooks. Chaque hook ajoute un appel externe par identifiant concerné et par phase. Un hook avec des boucles non bornées rend les transferts inabordables. Atténuation : règles d'écriture des hooks (check borné), revue avant l'enregistrement.
Confiance envers les hooks pouvant être mis à niveau. Un hook déployé derrière un proxy peut modifier la politique de transfert sans aucun changement sur le jeton. Atténuation : traiter les clés d'administration des hooks avec le même soin que HOOK_ADMIN_ROLE ; privilégier des hooks immuables en production.
Modèle de contrôle d'accès
AccessControl d'OpenZeppelin avec huit rôles sur le jeton et un sur le registre d'attestations ; DEFAULT_ADMIN_ROLE les administre tous. Disposition recommandée en production :
| Rôle | Titulaire |
|---|---|
DEFAULT_ADMIN_ROLE, UPGRADER_ROLE, HOOK_ADMIN_ROLE | multisig de gouvernance derrière un timelock |
ENFORCER_ROLE | multisig de mise en application (conformité + juridique) |
PAUSER_ROLE | clé de réponse aux incidents (rapide) |
ISSUER_ROLE, STATUS_ROLE, TRANSFORMER_ROLE | services opérationnels au moindre privilège, avec rotation des clés |
ATTEST_ADMIN_ROLE | la gouvernance du jeton : elle décide quels émetteurs sont de confiance pour chaque thème, non ce qu'ils attestent |
initialize accorde tout à un seul administrateur par commodité ; la liste de contrôle de déploiement exige une redistribution avant la production.
Sémantique de la mise en application et de la pause
- La pause bloque les émissions, les transferts, les destructions et les transformations ; elle ne bloque pas les transferts forcés, de sorte qu'un ordre légal peut être exécuté pendant un incident.
- Les transferts forcés ignorent
canSend/canReceiveet la vérification du solde gelé, abaissent le montant gelé à ce qui reste, et exécutent tout de même les hooks avecOP_FORCED_TRANSFER. Les hooks de référence renvoientOKpour cette opération ; un hook qui régit la mise en application peut exiger des références d'ordonnance dansdata. - Le gel peut dépasser le solde (ERC-7943), de sorte qu'une sûreté peut couvrir les dépôts futurs.
Les capacités dans le standard, la politique dans les profils
L'ADR-019 (Proposé) trace une même ligne à travers toutes les fonctions de mise en application. Le standard fixe ce qui existe et ce qu'il garantit : les rôles, les événements, ce qu'une pause arrête, les codes AREX:PAUSED et AREX:SANCTIONED, qui peut révoquer une attestation, et les règles de protection de la vie privée. Un profil de politique, tenu par juridiction et par organisation et versionné avec des dates comme une réglementation, indique qui peut utiliser chaque capacité et à quelles conditions :
| Capacité | Fixé par le standard | Défini par le profil de politique |
|---|---|---|
| Gel et transfert forcé | setFrozenTokens, forcedTransfer, leurs événements, un rôle dédié distinct de l'administration, des hooks exécutés avec OP_FORCED_TRANSFER | si le rôle peut être activé ou non, qui le détient et avec combien de signataires, quelle référence juridique chaque usage doit porter, qui notifier |
| Pause | ce qu'une pause arrête, AREX:PAUSED, les transferts forcés toujours permis | qui peut mettre en pause et lever la pause, la confirmation et le motif exigés, qui est informé |
| Sanctions | AREX:SANCTIONED et le sens de canSend / canReceive | quelles listes officielles s'appliquent (ONU, OFAC, UE, OFSI ou une combinaison), comment elles sont tenues à jour, et l'exigence qu'une personne confirme une correspondance avant qu'un compte soit marqué |
| Révocation d'attestation | l'émetteur révoque, un révocateur qu'il a désigné le peut ; un rôle administratif ne le devrait pas en 2.x et ne le doit pas à partir de 3.0 | quels émetteurs un jeton reconnaît pour chaque thème ; comment un opérateur consigne une contestation |
| Confidentialité de l'origine | originRef ne contient ni coordonnées ni identifiants personnels et n'est pas dérivé de la localisation | quelle référence un émetteur utilise, et où la géolocalisation est conservée off-chain |
Un changement de loi devient alors une mise à jour de profil, jamais un nouveau contrat ni une nouvelle version du standard.
Sûreté des mises à niveau
- UUPS ;
_authorizeUpgradeconditionné parUPGRADER_ROLE. - Structure de stockage ERC-7201 à espace de noms ; ajouts uniquement.
- Les validations du plugin de mises à niveau d'OpenZeppelin s'exécutent dans les tests et devraient s'exécuter en CI avant toute transaction de mise à niveau.
- Le paramètre
optimizer.runs = 1par fichier maintient l'implémentation sous EIP-170 ; une modification qui la fait dépasser la limite échoue à la compilation avec un avertissement et au déploiement avec un revert.
Sécurité des dépendances et du build
- OpenZeppelin Contracts et Contracts-Upgradeable épinglés à 5.3.0 ; fichier de verrouillage versionné ; la CI utilise
npm ci. - Solidity 0.8.22, viaIR, EVM
paris(aucune dépendance au stockage transitoire, déployable sur des chaînes qui n'ont pas adopté Cancun). - Aucune bibliothèque externe liée ; aucun delegatecall en dehors du proxy UUPS.
Posture d'audit
- 46 tests sur l'implémentation de référence, les hooks et les extensions couvrent chaque invariant ci-dessus, y compris une exécution d'invariants aléatoire à graine fixée.
- Le gaz et la taille sont mesurés par des scripts du dépôt.
- Un audit par un tiers de
ARexToken,ARexAttestationset des hooks de référence est une condition préalable déclarée pour un participant d'ancrage en production et pour la soumission de l'ERC (voir Évolutions futures). - La vérification formelle des invariants du pipeline figure sur la feuille de route ; les invariants sont suffisamment petits pour être énoncés sous forme de propriétés.
Avis de sécurité
| Avis | Concerne | Corrigé dans | Problèmes |
|---|---|---|---|
| AREX-2026-001 | implémentation de référence jusqu'à 2.0.0-alpha.2 inclus | 2.0.0-alpha.3 | le contexte d'opération fuyait dans les callbacks de réception ERC-1155 ; la réorganisation de lots sans rôle pouvait livrer des biens à un autre détenteur en contournant les hooks OP_TRANSFER ; les verrous de DeliveryHook pouvaient être contournés par destruction ou réorganisation de lots |
2.0.0-alpha.3 remplace 2.0.0-alpha.2. Les proxys de jeton déployés sont mis à niveau vers la nouvelle implémentation via UPGRADER_ROLE ; la disposition du stockage est inchangée. DeliveryHook n'est pas évolutif : déployer la nouvelle version, l'enregistrer avec registerHook, retirer l'ancienne avec removeHook, et replanifier les livraisons actives, car leurs verrous résident dans le stockage de l'ancien hook. Les intégrations qui fractionnent ou fusionnent directement vers le compte d'un acheteur doivent réorganiser sur place puis transférer, ou utiliser un compte doté de TRANSFORMER_ROLE. Les interfaces du standard ne changent pas.
Risques résiduels que le standard ne supprime pas
- L'existence physique et la qualité d'un lot sont attestées, non prouvées ; le jeton rend les affirmations imputables, non vraies.
- La disponibilité des métadonnées off-chain dépend du stockage choisi ; le hash garantit l'intégrité, non la disponibilité.
- La gouvernance des hooks et des modules ne vaut que ce que valent les clés qui la contrôlent.