
Signature aveugle : ce que vous approuvez réellement quand l'écran ne dit rien d'utile
Il y a un moment précis où surviennent la plupart des grosses pertes en crypto. Ce n'est pas quand une clé est volée. C'est quand quelqu'un regarde une demande de signature qu'il ne comprend pas, se dit que c'est sans doute bon, et appuie sur approuver.
Le secteur a un nom pour cela : la signature aveugle. Cela veut dire apposer sa signature sur des données qu'on ne peut pas lire. Et pendant des années, les portefeuilles ont traité cela comme normal.
Ce qu'un écran de signature est censé faire
La promesse de sécurité de l'autoconservation repose sur une hypothèse : que rien ne bouge sans votre approbation. Le multisig deux-sur-deux, les portefeuilles matériels, les signataires hors ligne — chacun d'eux est une machine à transformer votre intention en signature.
Cette mécanique vaut exactement ce que vaut votre compréhension de ce que vous avez approuvé.
Si l'écran dit « Envoyer 0,5 ETH à 0x8f3C… », votre approbation signifie quelque chose. Vous l'avez comparée à ce que vous vouliez faire. Si l'écran dit « Interaction avec un contrat — données : 0xa22cb465000000000000000000000000d8dA6…0001 », votre approbation ne signifie rien du tout. Vous ne consentez pas à une transaction ; vous consentez à un rectangle d'hexadécimal.
Les attaquants savent lequel de ces deux écrans ils ont besoin que vous voyiez.
Ce qui se cache dans les octets
La charge utile d'une transaction EVM, ce sont les calldata : un sélecteur de fonction de quatre octets suivi d'arguments encodés en ABI. Parfaitement lisible par une machine, parfaitement opaque pour une personne. Une poignée de ces sélecteurs, c'est par là que l'argent s'en va.
approve(address,uint256) — sélecteur 095ea7b3. Accorde à un contrat la permission de dépenser vos jetons. Pas un transfert ; une autorisation permanente. Elle ne déplace rien quand vous la signez, et c'est exactement pour cela qu'elle passe. Le siphonnage arrive plus tard, au calendrier de l'attaquant.
setApprovalForAll(address,bool) — sélecteur a22cb465. L'équivalent NFT, et pire : une seule signature donne à un opérateur autorité sur tous les jetons de cette collection que vous détenez, maintenant et à l'avenir. Aucun champ de montant pour se rassurer.
increaseAllowance(address,uint256) — sélecteur 39509351. Recharge une autorisation existante. Souvent négligé parce que ce n'est pas le sélecteur qu'on a dit à tout le monde de surveiller.
transferFrom(address,address,uint256) — sélecteur 23b872dd. Déplace des jetons depuis une adresse qui a déjà accordé une autorisation. La signature qui dépense enfin ce qu'un approve antérieur avait permis.
Le schéma à intérioriser : la transaction qui vous vole votre argent est rarement celle que vous avez signée. Vous avez signé une permission. Le vol est une transaction distincte, envoyée plus tard, que vous ne voyez jamais.
Pourquoi « illimité » est le mot à traquer
Presque tous les abus d'autorisation partagent un trait : le montant est infini en pratique.
Les dapps demandent des approbations illimitées parce que c'est commode — approuver une fois, ne plus jamais être sollicité. Résultat : les utilisateurs sont dressés à accorder des droits de dépense illimités par routine, et une interface qui affiche 115792089237316195423570985008687907853269984665640564039457584007913129639935 ne dit rien à personne.
Il y a une subtilité qui piège les implémentations naïves. La valeur canonique de « l'approbation infinie » est exactement 2²⁵⁶−1, la vérification évidente consiste donc à la comparer à cette constante. Mais les dapps utilisent couramment d'autres nombres astronomiques — la moitié du maximum, 0xff…f0, 10¹⁸ × 10³⁸ — qui sont illimités à tous égards pratiques tout en échouant à un test d'égalité exacte. Un avertissement qui ne se déclenche que sur la sentinelle exacte est un avertissement qu'un attaquant contourne en retranchant un.
Le bon seuil se situe bien en dessous de la sentinelle et bien au-dessus de tout ce qui est réel. SSP signale toute autorisation supérieure ou égale à 2²⁵⁵ — environ 5,8 × 10⁷⁶, ce qui dépasse toute offre ERC-20 possible de dizaines d'ordres de grandeur. Rien de légitime n'est jamais mal signalé, et ajuster une valeur juste sous le maximum n'esquive pas l'avertissement.
Ce seuil ne s'applique qu'aux appels accordant des autorisations. Sur un simple transfert, « illimité » n'a pas de sens — vous déplacez un montant précis — donc la sentinelle du maximum exact y est laissée tranquille.
Ce que fait SSP
SSP décode les calldata en langage clair sur l'écran d'approbation. Les sélecteurs reconnus sont rendus pour ce qu'ils sont : qui est la contrepartie, quel est le montant, si les droits accordés sont illimités. L'hexadécimal brut n'est plus le contenu principal — il vit derrière une section Avancé, pour qui le veut.
Trois décisions de conception comptent davantage que le décodage lui-même.
Le décodeur relève de la seule présentation. Il ne change jamais ce qui est signé. L'approbation signe toujours la charge utile d'origine exacte ; l'assistant ne fait que re-présenter des octets auparavant montrés en hexadécimal brut. Un décodeur capable d'altérer la charge serait une nouvelle surface d'attaque plutôt qu'une défense — ce que vous lisez et ce que vous signez doivent être les mêmes octets, toujours.
Il échoue en se fermant. Sélecteur inconnu, longueur erronée, hexadécimal malformé, remplissage d'adresse non standard — tout imprévu ne renvoie rien, et l'interface retombe sur une action générique avec l'hexadécimal brut dans Avancé. Il ne devine jamais. Un décodeur qui devine vaut moins que pas de décodeur, car un résumé faux mais assuré est plus dangereux que de l'hexadécimal visible : l'hexadécimal vous dit au moins honnêtement que vous ne le comprenez pas.
Cet instinct d'échec fermé va plus loin que les fonctions inconnues. Une adresse encodée en ABI, ce sont douze octets nuls suivis de vingt octets d'adresse ; un mot contenant autre chose dans ces octets de tête n'est pas encodé canoniquement, et SSP le traite comme suspect plutôt que d'essayer de l'interpréter. Idem pour les booléens : seules les encodages canoniques — tout à zéro (faux) et 31 zéros puis 0x01 (vrai) — sont acceptés. Des encodages non canoniques sur un écran qui accorde des droits de dépense sont un signal d'alarme, pas un défi d'analyse syntaxique.
Les symboles et décimales de jetons ne sont jamais devinés. Un montant lisible n'est affiché que lorsque le jeton est connu avec certitude depuis le registre embarqué. Sinon, vous obtenez le nombre brut en unités de base. C'est délibérément moins joli : afficher « 5,0 USDC » pour un contrat qui se contente de s'appeler USDC transformerait le décodeur en machine à mentir, ce qui est précisément le résultat que vise un attaquant.
Et puis il y a la partie structurelle plutôt que cosmétique. Dans SSP, la transaction est construite à un endroit et approuvée à un autre : composée dans l'extension du navigateur, décodée et affichée indépendamment sur votre téléphone, où SSP Key recalcule l'empreinte de la transaction sur l'appareil et refuse de signer si elle ne correspond pas à ce qui a été montré. Signer aveuglément sur un seul appareil, c'est dire qu'un écran compromis suffit. Ici, l'écran qui vous montre l'action décodée et l'appareil qui détient la seconde clé sont le même appareil, et il vérifie au lieu de faire confiance.
Que faire de vos propres approbations
Traitez approve et setApprovalForAll comme les dangereuses. Ce sont les signatures qui coûtent de l'argent aux gens, et elles paraissent inoffensives précisément parce que rien ne bouge.
Refusez les approbations illimitées quand vous le pouvez. Beaucoup de dapps acceptent un montant borné si vous en fixez un. C'est de la friction, et cela plafonne votre perte à ce que vous vouliez réellement dépenser.
Auditez ce que vous avez déjà accordé. Les vieilles approbations n'expirent pas. Une permission donnée à un protocole il y a deux ans est toujours active, et si ce contrat est compromis plus tard, c'est un chemin ouvert vers vos jetons. Révoquer des approbations est rapide, et c'est l'heure de ménage la plus rentable de l'autoconservation.
Quand l'écran ne vous dit rien, c'est cela le signal. Si votre portefeuille ne peut pas dire ce que fait une transaction, c'est une information — pas un désagrément à franchir d'un clic. La bonne réponse à une demande illisible est de s'arrêter, pas de plisser les yeux.
Le but n'a jamais été de vous faire lire de l'hexadécimal. C'est de garantir que, quand vous approuvez quelque chose, vous et votre portefeuille êtes d'accord sur ce que c'était.


