
Studio educativo sull'entropia delle chiavi Bitcoin: enumerazione di wallet con RNG debole (classe Milk Sad / CVE-2023-39910) rispetto all'infattibilità del brute-forcing di una chiave sicura.
# bitcoin-security
Uno studio didattico su **da dove proviene realmente la sicurezza delle chiavi Bitcoin** — e da dove non proviene. Costruito attorno alla derivazione reale BIP39/BIP32, con due esperimenti che si trovano agli estremi opposti della stessa idea:
| Experiment | Search space | Feasible? | What it teaches |
|---|---|---|---|
| [`weak_entropy`](https://github.com/xaxis/bitcoin-security/blob/main/bitcoin_security/weak_entropy) — enumerare uno spazio wallet con RNG debole | **2³²** (un RNG cattivo) | **Sì**, in giorni | Un wallet è forte quanto l'entropia che lo genera. |
| [`blind_search`](https://github.com/xaxis/bitcoin-security/blob/main/bitcoin_security/blind_search) — forzare bruta un buon spazio di chiavi | **2¹⁶⁰** (un RNG buono) | **No**, mai | Con entropia reale, lo spazio delle chiavi è fisicamente non ricercabile. |
Il punto centrale è il contrasto: **le chiavi vengono rubate attaccando la scarsa casualità, mai superando in calcolo la buona casualità.** Entrambi gli esperimenti derivano lo stesso tipo di indirizzi reali su mainnet, quindi l'unica variabile è l'entropia.
---
## ⚠️ Scopo e intento
Questo repository è per **educazione difensiva e ricerca sulla sicurezza**: comprendere perché i bug dei wallet con RNG debole (Milk Sad / CVE-2023-39910, il bug dell'estensione Trust Wallet del 2018 e simili) sono stati catastrofici, e perché non lo è forzare brutalmente una chiave generata correttamente.
- `weak_entropy` enumera uno **spazio sintetico autonomo**. La sua `weak_entropy()` è un *modello* deliberato della classe di vulnerabilità — un PRNG a 32 bit che alimenta BIP39 — **non** un clone byte-per-byte dell'RNG di un wallet reale. Gli indirizzi che produce **non corrispondono al wallet reale di nessuno.**
- `blind_search` è una **ricerca di saldo in sola lettura** sul tuo nodo. Non deriva alcuna chiave e non può spendere nulla; la sua modalità principale misura l'*inutilità* della ricerca cieca.
- Questo progetto intenzionalmente **non** fornisce, e non verrà esteso per fornire, una pipeline che colpisce i fondi di vittime reali (ad es. riprodurre l'RNG di un wallet reale, o ricongiungere i risultati di indirizzi finanziati alle chiavi recuperate). Questo è il confine tra studiare la vulnerabilità e trasformarla in arma.
Usalo su wallet che controlli, su testnet o come sandbox. Non puntarlo verso il denaro degli altri.
## Uso legale e responsabile
**Solo per istruzione e ricerca sulla sicurezza autorizzata.** Usalo esclusivamente su chiavi, wallet e sistemi che **possiedi** o per i quali hai **esplicito permesso scritto** di testare. L'accesso non autorizzato a wallet o sistemi altrui e il furto di asset digitali sono illegali ai sensi di leggi come il CFAA statunitense (18 U.S.C. § 1030), il Computer Misuse Act 1990 del Regno Unito e la Direttiva UE 2013/40/UE — indipendentemente dai mezzi tecnici. Fornito **"così com'è", senza garanzia**; gli autori **non si assumono alcuna responsabilità** per un uso improprio. **Non è consulenza legale.** Termini completi e indicazioni sulla divulgazione responsabile: **[DISCLAIMER.md](https://github.com/xaxis/bitcoin-security/blob/main/DISCLAIMER.md)**.
---
## La lezione fondamentale
> La sicurezza di un wallet è limitata dall'**entropia che è confluita nel suo seed**, non dalla robustezza di SHA-256 / PBKDF2 / secp256k1 a valle.
BIP39 trasforma l'entropia in un mnemonico, PBKDF2 la espande in un seed e BIP32 deriva un albero di chiavi. Tutto questo è robusto. Ma nessuno di questi passaggi *aggiunge* entropia — se il seed è stato estratto da un insieme piccolo, ogni indirizzo che il wallet può mai produrre è enumerabile, e nessuna quantità di hashing lo nasconde.
---
## Struttura del repository
```
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'albero riflette l'idea: un unico core `derivation` condiviso, due esperimenti che lo usano per giungere a conclusioni opposte sullo stesso spazio di indirizzi.
## Installazione
```bash
pip install -r requirements.txt # runtime deps
# optional — installs the package + `bsec-enumerate` / `bsec-scan` commands:
pip install -e .
```
`coincurve` (libsecp256k1 nativa) rende la derivazione ~5× più veloce ma è opzionale — `derivation.py` ripiega su `ecdsa` puro Python con output identico. Niente qui tocca la rete, eccetto il flag opzionale `--check-balance` e `blind_search` (che richiede un nodo Bitcoin locale).
Tutto viene eseguito come modulo dalla radice del repository; nessuna installazione richiesta:
```bash
python3 -m bitcoin_security.weak_entropy --help
python3 -m bitcoin_security.blind_search --help
```
---
## Esperimento 1 — `weak_entropy`: l'entropia debole è enumerabile
I 16 byte "casuali" di entropia BIP39 del wallet provengono in realtà da un seed a 32 bit. L'intera derivazione è reale e verificata rispetto ai vettori di specifica.
```
seed32 ──MT19937──▶ 16-byte entropy ──BIP39──▶ mnemonic ──PBKDF2(2048)──▶ 64-byte seed
│
address ◀──hash160──── pubkey ◀──BIP32 m/44'/0'/0'/0/0
```
```bash
# 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
```
Le righe sono minime (`address <TAB> seed32`) perché ogni altra forma è una funzione deterministica di `seed32`, rigenerata on demand con `--show`. I dump sono **riprendibili a granularità di shard** (verificati per riprodurre una copertura contigua, senza buchi e senza duplicati). Riferimento completo ai flag in [`weak_entropy/README.md`](https://github.com/xaxis/bitcoin-security/blob/main/bitcoin_security/weak_entropy/README.md).
> **Avvertenza sulla scala.** Il dump completo 2³² è di **~196 GB** (minimo) o **~1.05 TB** (`--full`) e richiede **giorni** anche con il backend nativo — vedi [`docs/FINDINGS.md`](https://github.com/xaxis/bitcoin-security/blob/main/docs/FINDINGS.md). Non ti serve quasi mai l'intero dump; esporta una porzione limitata per ispezione.
---
## Esperimento 2 — `blind_search`: non puoi forzare brutalmente una buona chiave
Uno scanner di saldo reale e funzionante sull'insieme UTXO di un nodo locale (anche in pruning) tramite `scantxoutset`. Il suo scopo è il **risultato nullo**: cercare lo spazio degli indirizzi alla cieca, misurare che non trovi nulla e stampare l'aritmetica che dimostra che non troverai mai nulla.
```bash
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
```
**Il risultato:** lo spazio è 2¹⁶⁰ ≈ 1,46×10⁴⁸; ~55M di indirizzi hanno un saldo; quindi un indirizzo casuale è finanziato con probabilità ~3,8×10⁻⁴¹. Anche a un miliardo di indirizzi al secondo aspetteresti ~8×10²³ anni — circa 61 mila miliardi di volte l'età dell'universo. Non è un problema hardware: secondo il limite di Landauer, il solo *contare* fino a 2¹⁶⁰ supera l'energia residua del sole. Configurazione del nodo in [`blind_search/README.md`](https://github.com/xaxis/bitcoin-security/blob/main/bitcoin_security/blind_search/README.md).
---
## La sintesi
Entrambi gli esperimenti derivano lo stesso tipo di indirizzo. L'unica differenza è la dimensione dell'insieme da cui è stato estratto il seed:
- **Buona entropia → 2¹⁶⁰**, fisicamente non ricercabile. (Esperimento 2)
- **Entropia debole → 2³²**, esauribile in giorni su un laptop. (Esperimento 1)
Quel divario — 128 bit — è l'intera partita. Ogni furto reale di questa classe si trovava lì dentro.
---
## Test
```bash
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)
```
## Risultati sperimentali (sintesi)
Dettagli completi, metodi e specifiche della macchina in [`docs/FINDINGS.md`](https://github.com/xaxis/bitcoin-security/blob/main/docs/FINDINGS.md).
- **Correttezza:** la derivazione è verificata rispetto ai vettori di specifica BIP39/BIP32/BIP-173/350/380; lo scanner include suite di test offline e un indirizzo di controllo obbligatorio con fondi noti per ogni esecuzione.
- **Prestazioni:** la sostituzione di `ecdsa` puro Python con `coincurve` (libsecp256k1) ha ridotto la derivazione per wallet da **3,02 ms → 0,591 ms (~5,1×)** su un Apple M3 Max; il limite rimanente è PBKDF2-HMAC-SHA512 ×2048 a **0,463 ms/wallet**.
- **Dimensione dello spazio completo (esatta):** **196,3 GB** minimo / ~1,05 TB `--full`, da una forma chiusa esatta per le cifre di `seed32` più una lunghezza media misurata degli indirizzi.
- **Tempo per lo spazio completo:** ~29 giorni-CPU su singolo core; ~2–3 giorni sui core prestazionali dell'M3 Max; limite rigido di PBKDF2 ~1,6 giorni. "Ore" richiederebbe una GPU.
---
## Riferimenti
- **CVE-2023-39910** — "Milk Sad", libbitcoin-explorer `bx seed` (MT19937 a 32 bit con seed basato sul tempo). Fondi reali rubati. https://milksad.info/
- **Entropia debole dell'estensione browser di Trust Wallet** (2018).
- **BIP**: [39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) (frasi mnemoniche), [32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) (wallet HD), [44](https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki) (percorsi), [173](https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki)/[350](https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki) (bech32/bech32m), [380](https://github.com/bitcoin/bips/blob/master/bip-0380.mediawiki) (descrittori).
## Licenza
MIT — vedi [LICENSE](https://github.com/xaxis/bitcoin-security/blob/main/LICENSE). Termini di utilizzo e indicazioni sulla divulgazione responsabile: [DISCLAIMER.md](https://github.com/xaxis/bitcoin-security/blob/main/DISCLAIMER.md). Segnalazioni: [SECURITY.md](https://github.com/xaxis/bitcoin-security/blob/main/SECURITY.md).