
Étude pédagogique de l'entropie des clés Bitcoin : énumération de portefeuilles à RNG faible (classe Milk Sad / CVE-2023-39910) vs l'impossibilité de forcer une bonne clé par force brute.
Une étude pédagogique de d'où vient réellement la sécurité des clés Bitcoin — et d'où elle ne vient pas. Construite autour d'une dérivation BIP39/BIP32 réelle, avec deux expériences qui se situent aux deux extrémités opposées de la même idée :
| Expérience | Espace de recherche | Faisable ? | Ce qu'elle enseigne |
|---|---|---|---|
weak_entropy — énumère un espace de portefeuilles à RNG faible | 2³² (un mauvais RNG) | Oui, en quelques jours | Un portefeuille est aussi solide que l'entropie qui le sous-tend. |
blind_search — force par force brute un bon espace de clés | 2¹⁶⁰ (un bon RNG) | Non, jamais | Avec une entropie réelle, l'espace de clés est physiquement impossible à explorer. |
Tout l'intérêt réside dans le contraste : les clés sont volées en attaquant un mauvais aléa, jamais en surpassant par le calcul un bon aléa. Les deux expériences dérivent le même type d'adresses réelles sur le mainnet, la seule variable étant donc l'entropie.
Ce dépôt est destiné à la formation défensive et à la recherche en sécurité : comprendre pourquoi les bugs de portefeuille à RNG faible (Milk Sad / CVE-2023-39910, le bug de 2018 de l'extension Trust Wallet, et leurs semblables) ont été catastrophiques, et pourquoi forcer par force brute une clé correctement générée ne l'est pas.
weak_entropy énumère un espace synthétique autonome. Sa
weak_entropy() est un modèle délibéré de la classe de vulnérabilité — un PRNG 32 bits
alimentant BIP39 — pas un clone octet pour octet du RNG d'un portefeuille réel. Les
adresses qu'elle produit ne correspondent à aucun portefeuille réel.blind_search est une consultation de solde en lecture seule sur votre propre nœud. Elle
ne dérive aucune clé et ne peut rien dépenser ; son mode principal mesure la futilité
de la recherche aveugle.Utilisez-le sur des portefeuilles que vous contrôlez, sur testnet, ou comme bac à sable. Ne le dirigez pas vers l'argent des autres.
Uniquement pour l'éducation et la recherche en sécurité autorisée. Utilisez-le uniquement sur des clés, portefeuilles et systèmes que vous possédez ou pour lesquels vous avez l'autorisation écrite explicite de tester. L'accès non autorisé aux portefeuilles ou systèmes d'autrui, ainsi que le vol d'actifs numériques, sont illégaux en vertu de lois telles que le CFAA des États-Unis (18 U.S.C. § 1030), le Computer Misuse Act 1990 du Royaume-Uni et la directive UE 2013/40/EU — quels que soient les moyens techniques. Fourni « en l'état », sans garantie ; les auteurs n'assument aucune responsabilité en cas d'usage abusif. Ceci n'est pas un conseil juridique. Conditions complètes et recommandations de divulgation responsable : DISCLAIMER.md.
La sécurité d'un portefeuille est bornée par l'entropie qui a alimenté sa graine, et non par la robustesse de SHA-256 / PBKDF2 / secp256k1 en aval.
BIP39 transforme l'entropie en phrase mnémonique, PBKDF2 l'étire en une graine, et BIP32 dérive une arborescence de clés. Tout cela est robuste. Mais aucune de ces étapes n'ajoute d'entropie — si la graine a été tirée d'un petit ensemble, chaque adresse que le portefeuille peut produire est énumérable, et nul hachage ne le masque.
bitcoin-security/
├── bitcoin_security/ # the package
│ ├── derivation.py # shared core: BIP39 + BIP32 + P2PKH/WIF
│ ├── data/english.txt # BIP39 English wordlist (2048 words)
│ ├── weak_entropy/ # Experiment 1 — enumerable weak space
│ │ ├── enumerate.py # recovery demo + full-space dump
│ │ └── README.md
│ └── blind_search/ # Experiment 2 — futile brute-force search
│ ├── scan.py # UTXO-set balance scanner + odds
│ └── README.md
├── tests/ # offline test suites (no node/network)
│ ├── test_blind_search_addresses.py
│ └── test_blind_search_pipeline.py
├── docs/FINDINGS.md # benchmarks + exact full-space size derivation
├── pyproject.toml
├── requirements.txt
└── LICENSE
L'arborescence reflète l'idée : un noyau derivation partagé, deux expériences qui
l'utilisent pour aboutir à des conclusions opposées sur le même espace d'adresses.
pip install -r requirements.txt # runtime deps
# optional — installs the package + `bsec-enumerate` / `bsec-scan` commands:
pip install -e .
coincurve (libsecp256k1 native) rend la dérivation ~5× plus rapide mais reste optionnel —
derivation.py retombe sur ecdsa en pur Python avec une sortie identique. Rien
ici ne touche au réseau, à l'exception du drapeau --check-balance activable explicitement et de
blind_search (qui nécessite un nœud Bitcoin local).
Tout s'exécute comme un module depuis la racine du dépôt ; aucune installation requise :
python3 -m bitcoin_security.weak_entropy --help
python3 -m bitcoin_security.blind_search --help
weak_entropy : l'entropie faible est énumérableLes 16 octets « aléatoires » d'entropie BIP39 du portefeuille proviennent en réalité d'une graine de 32 bits. La dérivation complète est réelle et vérifiée par rapport aux vecteurs de spécification.
seed32 ──MT19937──▶ 16-byte entropy ──BIP39──▶ mnemonic ──PBKDF2(2048)──▶ 64-byte seed
│
address ◀──hash160──── pubkey ◀──BIP32 m/44'/0'/0'/0/0
# Recovery: derive a victim wallet from a weak seed, then recover its full
# mnemonic + private key knowing only the public address.
python3 -m bitcoin_security.weak_entropy
# Expand one seed into every form.
python3 -m bitcoin_security.weak_entropy --show 3571
# Materialize the whole 2**32 space to sharded, resumable files.
python3 -m bitcoin_security.weak_entropy --dump-all --out-dir space_dump --max-file-mb 1000
Les lignes sont minimales (address <TAB> seed32) car toute autre forme est une
fonction déterministe de seed32, régénérée à la demande avec --show. Les dumps
sont reprenables à la granularité du fragment (vérifié pour reproduire une couverture
contiguë, sans trou ni doublon). Référence complète des drapeaux dans
weak_entropy/README.md.
Avertissement sur l'échelle. Le dump complet 2³² fait ~196 Go (minimal) ou ~1,05 To (
--full) et prend des jours même avec le backend natif — voirdocs/FINDINGS.md. Vous n'avez presque jamais besoin de tout ; dump une tranche bornée pour inspection.
blind_search : impossible de forcer par force brute une bonne cléUn véritable scanner de solde fonctionnel sur l'ensemble UTXO d'un nœud local (élagage OK) via
scantxoutset. Son but est le résultat nul : chercher à l'aveugle dans l'espace
d'adresses, constater que vous ne trouvez rien, et afficher le calcul montrant que vous n'en
trouverez jamais.