La défaillance d’entropie de COLDCARD : comment un basculement silencieux du RNG a coûté 38 millions de dollars aux utilisateurs

Points clés
- Les utilisateurs ayant généré et utilisé une seed phrase sur COLDCARD Mk3 doivent générer immédiatement une nouvelle seed phrase sur un autre wallet et migrer leurs fonds.
- Les utilisateurs de COLDCARD Mk4 ou Mk5 doivent migrer si la seed phrase d’origine a été générée avec un firmware inférieur à 5.6.0.
- Les utilisateurs de COLDCARD Q doivent migrer si la seed phrase d’origine a été générée avec un firmware inférieur à 1.5.0Q.
- Les hardware wallets OneKey ne sont pas affectés par ce bug et peuvent continuer à être utilisés en toute confiance.
En cryptographie, « aléatoire » est une mesure exigeante. C’est quelque chose que l’on peut quantifier, et qui doit résister à une puissance de calcul réelle.
Une phrase de récupération de 12 mots repose sur un nombre aléatoire de 128 bits, encodé en mots lisibles par l’humain selon la spécification BIP-39. Le degré d’imprévisibilité de ce nombre détermine directement si le wallet peut résister à une attaque par force brute. 128 bits signifient 2¹²⁸ possibilités. Même en réunissant toute la puissance de calcul disponible sur Terre, il serait impossible de les énumérer avant la fin de l’univers.
En théorie, les hardware wallets devraient donc traiter ce point avec une rigueur extrême. Il faut un générateur matériel dédié de nombres véritablement aléatoires (TRNG), qui extrait de l’aléa physique du bruit de circuit, associé à un secure element (SE) certifié EAL, puis un algorithme mature qui mélange le tout à la fin.
Le 30 juillet 2026, environ 594 BTC (près de 38 millions de dollars) ont été vidés de près de 500 wallets COLDCARD en moins d’une demi-heure. Le lendemain, le fabricant Coinkite a confirmé la cause racine dans « Technical Deep Dive into the Entropy Issue » [1] : depuis un changement de firmware en mars 2021, une erreur de configuration de build faisait silencieusement basculer le code vers le générateur pseudo-aléatoire logiciel (PRNG) intégré à MicroPython.
Les conséquences varient selon le modèle. L’estimation préliminaire de Coinkite indique qu’il ne reste au Mk3 qu’environ 40 bits d’espace de recherche effectif, et environ 72 bits pour les Mk4, Mk5 et Q, contre un objectif de conception de 128 bits. Quarante bits représentent environ 1 100 milliards de possibilités, ce qui semble encore astronomique, mais pour un attaquant capable de louer un cluster GPU, on est passé de « physiquement impossible » à un projet d’ingénierie viable en espérance. De 128 à 40 bits, l’espace de recherche a été réduit d’un facteur 2⁸⁸, soit 300 septillions.
Ce vol de 38 millions de dollars a transformé cette espérance en fait accompli.
Note : les fonds des utilisateurs OneKey sont en sécurité. Aucun code OneKey n’est concerné par ce problème, et OneKey n’utilise ni les implémentations ni les dépendances impliquées. Déclaration complète : https://x.com/OneKeyHQ/status/2083106588022509974?s=20
TL;DR
- Les utilisateurs ayant généré et utilisé une seed phrase sur un COLDCARD Mk3 doivent générer immédiatement une seed phrase sur un autre wallet et migrer leurs fonds vers ce nouveau wallet.
- Les utilisateurs ayant généré et utilisé une seed phrase sur un COLDCARD Mk4 ou Mk5 doivent, si la version du firmware ayant généré la seed phrase d’origine était inférieure à 5.6.0, générer immédiatement une seed phrase sur un autre wallet et migrer leurs fonds vers ce nouveau wallet.
- Les utilisateurs ayant généré et utilisé une seed phrase sur un COLDCARD Q doivent, si la version du firmware ayant généré la seed phrase d’origine était inférieure à 1.5.0Q, générer immédiatement une seed phrase sur un autre wallet et migrer leurs fonds vers ce nouveau wallet.
- Les utilisateurs de hardware wallets OneKey ne sont pas affectés par ce bug et peuvent continuer à les utiliser en toute confiance.
Passons maintenant à la partie technique avancée 👇
Cause racine : un basculement logiciel silencieux
De l’interface RNG matérielle à libNgU
La migration de la pile crypto en 2021 a remplacé, pour la génération de seed du wallet, ckcc.rng_bytes() par ngu.random.bytes() [6] :
ngu.random.bytes() → libNgU rng_get() → le symbole rng_get vers lequel il est finalement lié
Le risque ne se situe ni dans le nom de la fonction ni dans la longueur de la sortie. Il se situe dans le fichier objet qui fournit réellement ce rng_get() final.
#ifndef vérifie seulement si quelque chose est défini, pas sa valeur
La configuration de la carte cible définissait MICROPY_HW_ENABLE_RNG à 0, ce qui signifie que le chemin RNG matériel de MicroPython n’était pas activé.
Mais à l’époque, libNgU utilisait [8] :
#ifndef MICROPY_HW_ENABLE_RNG
#error ...
#endif
#ifndef vérifie uniquement si la macro existe. Une macro définie à 0 reste « définie », donc le #error censé bloquer un mauvais build ne s’est jamais déclenché.
Un symbole portant le même nom a laissé la mauvaise implémentation entrer dans le firmware final
Lorsque le RNG matériel n’est pas activé, MicroPython fournit tout de même un rng_get() sous le même nom, mais il s’agit du fallback PRNG logiciel Yasmarang [9].
Cela a produit la chaîne de défaillance complète :
la macro existe, mais sa valeur est 0 → la garde de build de libNgU ne se déclenche pas → le firmware compile quand même avec succès → le rng_get portant le même nom se résout vers le fallback de MicroPython → la génération de seed ne reçoit jamais l’entrée du RNG matériel qu’elle était censée recevoir
Pris isolément, chaque maillon de cette chaîne n’est qu’une petite négligence. Mis bout à bout, ils rendent le contrôle du système de build dépourvu de sens : l’existence d’une implémentation sécurisée dans le source ou le binaire ne signifie pas que l’appel critique pour la sécurité l’atteint réellement.
Mélanger des sources d’entropie ne compense pas une entropie absente
L’état initial du fallback implique une partie de l’UID, SysTick, l’heure RTC et un état infra-seconde, après quoi Yasmarang le fait évoluer de manière déterministe. libNgU le mélange aussi avec un second flux Yasmarang qui démarre depuis un état initial fixe.
Deux flux déterministes qui peuvent être reconstruits ou énumérés ne gagnent aucune nouvelle imprévisibilité physique parce qu’ils sont combinés par XOR ou passés dans SHA-256. Le hachage peut conditionner l’entrée, mais il ne peut pas transformer un ensemble fini de candidats en un véritable espace inconnu de 2^256.
Ce que signifient réellement 40 bits et 72 bits
Les estimations d’espace de recherche effectif utilisées par Coinkite ne sont pas des valeurs d’entropie mesurées et certifiées. Ces deux nombres décrivent la taille de l’espace d’états candidats qu’un attaquant doit parcourir. Ils ne mesurent pas la min-entropie, ne se convertissent pas en nombre moyen d’essais et ne donnent certainement pas un temps ou un coût d’attaque spécifique.
Le coût réel d’une attaque dépend d’une chaîne de préconditions : l’attaquant connaît-il ou peut-il deviner l’UID de l’appareil ; peut-il réduire la plage de RTC, SysTick et du temps de démarrage ; combien d’appels RNG ont eu lieu avant la génération de la seed ; dispose-t-il d’un xpub, d’une adresse ou d’une clé publique pour valider les candidats hors ligne ; et quels coûts de dérivation BIP-39/BIP-32 et de matériel parallèle doit-il supporter.
La formulation correcte est donc la suivante : selon les hypothèses d’attaque préliminaires actuelles de Coinkite, l’espace de recherche effectif du Mk3 est estimé à environ 40 bits, et celui des Mk4/Q/Mk5 à environ 72 bits. Ce n’est pas la même chose que « chaque wallet Mk3 possède exactement 40 bits d’entropie », ni « les modèles plus récents ont une sécurité cryptographique prouvée de 72 bits », et encore moins « n’importe quel ordinateur ordinaire peut récupérer tous les wallets dans un délai fixe ».
Pourquoi les Mk4, Mk5 et Q sont aussi affectés
The two secure elements on the Mk5 and Q boards (source: coldcard.com)
Les deux secure elements sur les cartes Mk5 et Q (source : coldcard.com)
Les Mk4, Mk5 et Q tirent bien du matériel aléatoire de deux secure elements, mais l’implémentation antérieure au correctif n’injectait pas toute cette sortie dans un DRBG cryptographique.
Le source public montre qu’après avoir haché le matériel issu des secure elements, il ne prend que quatre octets et les transmet à ngu.random.reseed(), remplaçant seulement un mot d’état de 32 bits de Yasmarang.
Ainsi, lorsque le reste de l’état du fallback et la trace d’appels sont fixes :
nombre de flux de sortie distinguables par le reseed du secure element ≤ 2³²
Cela ne contredit pas le chiffre officiel d’« environ 72 bits ». Le modèle à 72 bits compte aussi l’incertitude liée à des états comme l’UID, les timers, RTC et l’historique des appels. La borne supérieure combinée approximative de Block Engineering reste sous 2^73.3, et ils indiquent explicitement qu’il ne s’agit pas d’une certification d’une sécurité cryptographique de 73 bits [10].
Autrement dit : le Mk3 est le pire cas, car il n’y a aucun reseed sécurisé. L’entrée du secure element sur les Mk4/Q/Mk5 réduit le risque, mais un reseed de quatre octets ne restaure pas la sécurité de 128 bits visée par la conception.
L’avis officiel et le périmètre réel
L’avis actuel de Coinkite commence au Mk3 4.0.1. Mais le source immuable v4.0.0 contient déjà le chemin pertinent de génération d’entropie [7], et il n’existe aucun correctif RNG correspondant entre 4.0.0 et 4.0.1.
Block Engineering va plus loin en intégrant aussi Mk2/Mk3 v4.0.0–v4.1.9 dans son analyse du source [10].
Il faut donc distinguer deux niveaux :
- Le périmètre officiel de Coinkite : Mk3 4.0.1+ ;
- Le périmètre technique étayé par le code source : Mk2/Mk3 v4.0.0–v4.1.9.
On ne peut pas dire que Coinkite a officiellement confirmé Mk2 ou v4.0.0. Mais on ne peut pas non plus en déduire qu’ils sont sûrs simplement parce que l’avis ne les nomme pas.
Fonctionnement du correctif
Le hotfix dans Mk4/Mk5 5.6.0 et Q 1.5.0Q a fait plus que modifier une expression conditionnelle. Il a ajouté une application stricte au niveau du build [3][4][5] :
- Exclure l’objet RNG fallback de MicroPython ;
- S’assurer que l’objet au niveau carte fournit le
rng_get()global ; - Utiliser
nmpour inspecter les symboles dans les fichiers objets ; - Faire échouer le build immédiatement si le fallback exporte encore le symbole, ou si l’objet au niveau carte ne fournit pas correctement
rng_get().
Ce que les utilisateurs doivent faire
Mk4, Mk5 et Q
- Mettre d’abord à jour vers 5.6.0 ou ultérieur (Mk4/Mk5), ou 1.5.0Q ou ultérieur (Q).
- Générer une seed entièrement nouvelle sur le firmware corrigé.
- Noter la sauvegarde et effectuer une vérification de restauration.
- Vérifier l’empreinte du wallet et l’adresse de réception sur l’écran de l’appareil.
- Envoyer une petite transaction de test avant de migrer le reste des fonds.
- Conserver l’ancienne sauvegarde jusqu’à confirmation de tous les transferts, mais cesser de recevoir des fonds sur l’ancien wallet.
Mk3
Il n’existe actuellement aucun firmware correctif publié pour le Mk3, et 4.1.9 n’est pas un correctif. Les utilisateurs ne devraient pas attendre un firmware qui pourrait arriver plus tard, car aucun nouveau firmware ne peut ajouter de l’entropie à une seed existante.
Suivez les recommandations de migration actuelles de Coinkite : générez une nouvelle seed via un chemin de confiance, vérifiez la sauvegarde, les adresses et une petite transaction, puis migrez vos actifs.
Deux seuils différents pour les dés
Il existe deux nombres pour les dés : 50 et 99. Ils répondent à deux objectifs différents et ne sont pas interchangeables.
50+ lancers ajoutés lors de la génération initiale de la seed. Si au moins 50 lancers équitables, indépendants et privés d’un dé à six faces, jamais enregistrés, stockés ni divulgués, ont été mélangés lorsque la seed a été générée pour la première fois, Coinkite indique ne pas considérer cette seed comme exposée par ce seul problème de RNG [11].
50 × log₂(6) ≈ 129.25 bits
C’est le seuil d’environ 128 bits pour cet incident précis, pas une preuve absolue de sécurité pour l’ensemble de votre processus opérationnel.
Le flux Dice Roll Import sur un Mk3 vierge. Coinkite propose aussi un autre flux : sur un Mk3 vierge exécutant 4.1.9, sélectionnez Import Existing > Dice Rolls et saisissez au moins 99 lancers équitables. Ce chemin dédié consomme directement la séquence de dés et n’utilise pas le générateur de l’appareil affecté.
99 × log₂(6) ≈ 255.91 bits
99 est le seuil du flux officiel ; en partant littéralement d’une norme « au moins 256 bits », il en faudrait 100.
Une passphrase est une barrière distincte, pas un correctif
Une passphrase BIP-39 forte, unique, aléatoire et secrète dérive un wallet différent et peut ajouter un facteur de recherche indépendant ; le PIN de l’appareil ne contrôle que l’accès local et ne participe pas à la dérivation de la clé racine BIP-39 [12].
Mais une passphrase ne peut pas compenser l’entropie que le mnémonique n’a jamais eue. Les passphrases courtes, les citations connues, les motifs prévisibles et les mots de passe réutilisés peuvent toujours être devinés. Une faute de frappe générera aussi un wallet qui semble valide mais qui est totalement différent ; il faut donc vérifier l’empreinte et conserver une sauvegarde fiable dont vous avez testé la restauration.
Conclusion
Le problème central révélé par cet incident est que l’accessibilité réelle d’une implémentation critique pour la sécurité n’était jamais imposée par le système de build. Dire que « quelqu’un s’est trompé sur une macro » est très insuffisant.
Au minimum, les hardware wallets et autres systèmes de génération de clés devraient :
- Vérifier à quel objet et à quel symbole une API de sécurité est finalement liée, et pas seulement la signature de la fonction ;
- Faire en sorte que les contrôles de capacité vérifient à la fois l’existence et la valeur d’une macro ;
- Faire échouer les fallbacks critiques pour la sécurité en mode fermé, au lieu de fournir silencieusement un PRNG ordinaire ;
- Faire inspecter par la CI la composition des objets, les link maps, la provenance des symboles et le flux de données de bout en bout ;
- Prouver par des tests que l’entropie matérielle réelle atteint la seed finale, au lieu de vérifier seulement la longueur de sortie, son caractère non nul ou l’absence de répétition ;
- Rédiger des avis qui distinguent clairement la version ayant généré la seed, le firmware actuel, la version corrigée et la marche à suivre pour les anciennes seeds.
L’open source de COLDCARD a permis à des chercheurs externes de reconstruire cette chaîne d’appels, et Coinkite a publié à la fois un rapport technique formel et une version corrigée.
Affirmer qu’une marque est « absolument sécurisée » ne vaut rien. Ce qui compte, c’est de faire de « d’où vient l’aléa, vers quoi il est finalement lié, où il va en cas d’échec, et comment vérifier cela dans le produit livré » une propriété système que l’on peut recontrôler en continu.
Références
[1] Coinkite: Technical Deep Dive into the Entropy Issue [2] Coinkite: Mk3 Security Advisory [3] Coldcard 5.6.0 / 1.5.0Q fix commit [4] Coldcard 5.6.0 release commit [5] Coldcard 1.5.0Q release commit [6] 2021 libNgU seed generation migration commit [7] Official v4.0.0 source snapshot [8] libNgU random.c [9] MicroPython commit introducing the fallback [10] Block Engineering technical analysis [11] Coldcard dice roll math [12] Coldcard passphrase documentation [13] OneKey trezorcrypto.random source branch [14] OneKey seed generation logic [15] OneKey standard Makefile [16] OneKey SCons config






