
Estudio educativo de la entropía de las claves de Bitcoin: enumeración de carteras con RNG débil (clase Milk Sad / CVE-2023-39910) frente a la inviabilidad de atacar por fuerza bruta una clave segura.
Un estudio educativo sobre de dónde viene realmente la seguridad de las claves de Bitcoin — y de dónde no. Construido en torno a la derivación real BIP39/BIP32, con dos experimentos que se sitúan en extremos opuestos de la misma idea:
| Experiment | Search space | Feasible? | What it teaches |
|---|---|---|---|
weak_entropy — enumerate a weak-RNG wallet space | 2³² (a bad RNG) | Yes, in days | A wallet is only as strong as the entropy behind it. |
blind_search — brute-force a good key space | 2¹⁶⁰ (a good RNG) | No, ever | With real entropy, the key space is physically unsearchable. |
El punto central es el contraste: las claves se roban atacando la aleatoriedad débil, nunca superando por cómputo la aleatoriedad buena. Ambos experimentos derivan el mismo tipo de direcciones reales de mainnet, así que la única variable es la entropía.
Este repositorio es para educación defensiva e investigación de seguridad: comprender por qué los fallos de RNG débil en carteras (Milk Sad / CVE-2023-39910, el fallo de la extensión de Trust Wallet de 2018 y similares) fueron catastróficos, y por qué forzar por fuerza bruta una clave generada correctamente no lo es.
weak_entropy enumera un espacio sintético autocontenido. Su weak_entropy() es un modelo deliberado de la clase de vulnerabilidad — un PRNG de 32 bits alimentando BIP39 — no un clon byte a byte del RNG de ninguna cartera real. Las direcciones que produce no se corresponden con la cartera real de nadie.blind_search es una consulta de saldos de solo lectura contra tu propio nodo. No deriva claves y no puede gastar nada; su modo principal mide la futilidad de la búsqueda a ciegas.Úsalo en carteras que controlas, en testnet o como banco de pruebas. No lo apuntes al dinero de otras personas.
Solo para educación e investigación de seguridad autorizada. Úsalo únicamente sobre claves, carteras y sistemas que poseas o sobre los que tengas permiso explícito por escrito para probar. El acceso no autorizado a carteras o sistemas ajenos, y el robo de activos digitales, son ilegales según leyes como la CFAA de EE. UU. (18 U.S.C. § 1030), la Computer Misuse Act 1990 del Reino Unido y la Directiva 2013/40/UE de la Unión Europea — independientemente de los medios técnicos. Se proporciona "tal cual", sin garantía; los autores no aceptan ninguna responsabilidad por el mal uso. Esto no es asesoramiento legal. Términos completos y guía de divulgación responsable: DISCLAIMER.md.
La seguridad de una cartera está limitada por la entropía que se usó en su semilla, no por la fortaleza de SHA-256 / PBKDF2 / secp256k1 aguas abajo.
BIP39 convierte la entropía en un mnemónico, PBKDF2 la estira hasta convertirla en una semilla, y BIP32 deriva un árbol de claves. Todo eso es sólido. Pero nada de ello añade entropía — si la semilla se extrajo de un conjunto pequeño, cada dirección que la cartera pueda producir jamás es enumerable, y ninguna cantidad de hash lo oculta.
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
El árbol refleja la idea: un núcleo derivation compartido, dos experimentos que lo usan para llegar a conclusiones opuestas sobre el mismo espacio de direcciones.
pip install -r requirements.txt # runtime deps
# optional — installs the package + `bsec-enumerate` / `bsec-scan` commands:
pip install -e .
coincurve (libsecp256k1 nativa) hace la derivación ~5× más rápida, pero es opcional — derivation.py recurre al ecdsa de Python puro con una salida idéntica. Nada de esto toca la red salvo la opción --check-balance y blind_search (que necesita un nodo Bitcoin local).
Todo se ejecuta como módulo desde la raíz del repositorio; no se requiere instalación:
python3 -m bitcoin_security.weak_entropy --help
python3 -m bitcoin_security.blind_search --help
weak_entropy: la entropía débil es enumerableLos 16 bytes "aleatorios" de entropía BIP39 de la cartera provienen en realidad de una semilla de 32 bits. La derivación completa es real y está verificada contra los vectores de las especificaciones.
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
Las filas son mínimas (address <TAB> seed32) porque cualquier otra forma es una función determinista de seed32, regenerada bajo demanda con --show. Los volcados son reanudables a nivel de fragmento (verificado que reproducen una cobertura contigua, sin huecos y sin duplicados). Referencia completa de opciones en weak_entropy/README.md.
Advertencia de escala. El volcado completo de 2³² ocupa ~196 GB (mínimo) o ~1.05 TB (
--full) y tarda días incluso con el backend nativo — consultadocs/FINDINGS.md. Casi nunca necesitas el total; vuelca un fragmento acotado para inspeccionarlo.
blind_search: no se puede forzar por fuerza bruta una clave buenaUn escáner de saldos real y funcional sobre el conjunto UTXO de un nodo local (que admita poda) mediante scantxoutset. Su propósito es el resultado nulo: buscar a ciegas en el espacio de direcciones, comprobar que no encuentras nada, e imprimir la aritmética que demuestra que siempre será así.
python3 -m bitcoin_security.blind_search --odds-only # just the math, no node
python3 -m bitcoin_security.blind_search --random 200000 --odds # search 200k random addrs, then the math
python3 -m bitcoin_security.blind_search --control # verify the pipeline finds known-funded addrs
El resultado: el espacio es 2¹⁶⁰ ≈ 1.46×10⁴⁸; ~55M de direcciones tienen saldo; así que una dirección aleatoria tiene fondos con probabilidad ~3.8×10⁻⁴¹. Incluso a mil millones de direcciones por segundo tardarías ~8×10²³ años — unas 61 billones de veces la edad del universo. No es un problema de hardware: según el límite de Landauer, el mero contar hasta 2¹⁶⁰ supera la producción de energía restante del Sol. Configuración del nodo en blind_search/README.md.
Ambos experimentos derivan el mismo tipo de dirección. La única diferencia es el tamaño del conjunto del que se extrajo la semilla:
Esa brecha — 128 bits — lo es todo. Todos los robos reales de esta clase vivieron en ella.
python3 tests/test_blind_search_addresses.py # BIP-173/350/380 address vectors
python3 tests/test_blind_search_pipeline.py # scan pipeline, RPC stubbed
python3 -m bitcoin_security.derivation # derivation self-test (spec vectors)
Detalle completo, métodos y especificaciones de la máquina en docs/FINDINGS.md.
ecdsa de Python puro por coincurve (libsecp256k1) redujo la derivación por cartera de 3.02 ms → 0.591 ms (~5.1×) en un Apple M3 Max; el mínimo restante es PBKDF2-HMAC-SHA512 ×2048 a 0.463 ms/cartera.--full, a partir de una forma cerrada exacta para los dígitos de seed32 más una longitud media de dirección medida.bx seed (MT19937 de 32 bits sembrado con la hora). Se robaron fondos reales. https://milksad.info/MIT — consulta LICENSE. Términos de uso y guía de divulgación responsable: DISCLAIMER.md. Informes: SECURITY.md.