
Estudo educacional sobre a entropia de chaves Bitcoin: enumeração de carteiras com RNG fraco (classe Milk Sad / CVE-2023-39910) vs. a inviabilidade de quebrar uma boa chave por força bruta.
| Experimento | Espaço de busca | Viável? | O que ensina |
|---|
weak_entropy — enumerar um espaço de carteira com RNG fraco | 2³² (um RNG ruim) | Sim, em dias | Uma carteira é tão forte quanto a entropia por trás dela. |
blind_search — aplicar força bruta a um espaço de chaves bom | 2¹⁶⁰ (um RNG bom) | Não, jamais | Com entropia real, o espaço de chaves é fisicamente impossível de pesquisar. |
O ponto central é o contraste: chaves são roubadas atacando aleatoriedade ruim, nunca superando computacionalmente aleatoriedade boa. Ambos os experimentos derivam o mesmo tipo de endereços reais de mainnet, então a única variável é a entropia.
Este repositório é para educação defensiva e pesquisa de segurança: entender por que bugs de carteiras com RNG fraco (Milk Sad / CVE-2023-39910, o bug da extensão Trust Wallet de 2018 e seus semelhantes) foram catastróficos, e por que aplicar força bruta a uma chave gerada corretamente não é.
weak_entropy enumera um espaço sintético autocontido. Sua weak_entropy() é um modelo deliberado da classe de vulnerabilidade — um PRNG de 32 bits alimentando BIP39 — não um clone byte a byte do RNG de qualquer carteira real. Os endereços que ela produz não correspondem à carteira real de ninguém.blind_search é uma consulta de saldo somente leitura contra o seu próprio nó. Ela não deriva chaves e não pode gastar nada; seu modo principal mede a futilidade da busca cega.Use-o em carteiras que você controla, em testnet ou como um sandbox. Não o aponte para o dinheiro de outras pessoas.
Somente para educação e pesquisa de segurança autorizada. Use-o exclusivamente em chaves, carteiras e sistemas que você possui ou para os quais tem permissão explícita por escrito para testar. Acesso não autorizado a carteiras ou sistemas alheios e roubo de ativos digitais são ilegais sob leis como a CFAA dos EUA (18 U.S.C. § 1030), o Computer Misuse Act 1990 do Reino Unido e a Diretiva 2013/40/EU da UE — independentemente dos meios técnicos. Fornecido "como está", sem garantia; os autores não aceitam nenhuma responsabilidade por uso indevido. Isto não é aconselhamento jurídico. Termos completos e orientação de divulgação responsável: DISCLAIMER.md.
A segurança de uma carteira é limitada pela entropia que entrou na sua semente, não pela força de SHA-256 / PBKDF2 / secp256k1 a jusante.
BIP39 transforma entropia em um mnemônico, PBKDF2 a estica em uma semente e BIP32 deriva uma árvore de chaves. Tudo isso é forte. Mas nada disso adiciona entropia — se a semente foi extraída de um conjunto pequeno, todo endereço que a carteira pode produzir é enumerável, e nenhuma quantidade de hash esconde isso.
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
A árvore reflete a ideia: um núcleo derivation compartilhado, dois experimentos que o usam para chegar a conclusões opostas sobre o mesmo espaço de endereços.
pip install -r requirements.txt # runtime deps
# optional — installs the package + `bsec-enumerate` / `bsec-scan` commands:
pip install -e .
coincurve (libsecp256k1 nativa) torna a derivação ~5× mais rápida, mas é opcional — derivation.py recorre ao ecdsa puro em Python com saída idêntica. Nada aqui toca a rede, exceto pela flag opt-in --check-balance e pelo blind_search (que precisa de um nó Bitcoin local).
Tudo roda como módulo a partir da raiz do repositório; nenhuma instalação é necessária:
python3 -m bitcoin_security.weak_entropy --help
python3 -m bitcoin_security.blind_search --help
weak_entropy: entropia fraca é enumerávelOs 16 bytes "aleatórios" de entropia BIP39 da carteira na verdade vêm de uma semente de 32 bits. A derivação completa é real e verificada contra vetores de especificação.
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
As linhas são mínimas (address <TAB> seed32) porque toda outra forma é uma função determinística de seed32, regenerada sob demanda com --show. Os dumps são retomáveis na granularidade de shard (verificados para reproduzir cobertura contígua, sem lacunas e sem duplicatas). Referência completa das flags em weak_entropy/README.md.
Aviso de escala. O dump completo de 2³² tem ~196 GB (mínimo) ou ~1.05 TB (
--full) e leva dias mesmo com o backend nativo — vejadocs/FINDINGS.md. Você quase nunca precisa do conjunto inteiro; faça o dump de uma fatia limitada para inspeção.
blind_search: você não consegue aplicar força bruta a uma chave boaUm scanner de saldo real e funcional sobre o conjunto UTXO de um nó local (compatível com pruning) via scantxoutset. Seu propósito é o resultado nulo: buscar no espaço de endereços às cegas, constatar que nada é encontrado e imprimir a aritmética mostrando que sempre será assim.
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
O resultado: o espaço é 2¹⁶⁰ ≈ 1.46×10⁴⁸; ~55M endereços possuem saldo; portanto, um endereço aleatório tem saldo com probabilidade ~3.8×10⁻⁴¹. Mesmo a um bilhão de endereços/segundo, você esperaria ~8×10²³ anos — ~61 trilhões de vezes a idade do universo. Não é um problema de hardware: pelo limite de Landauer, meramente contar até 2¹⁶⁰ excede a produção de energia restante do sol. Configuração do nó em blind_search/README.md.
Ambos os experimentos derivam o mesmo tipo de endereço. A única diferença é o tamanho do conjunto do qual a semente foi extraída:
Essa lacuna — 128 bits — é a questão toda. Todo roubo real desta classe viveu nela.
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)
Detalhes completos, métodos e especificações da máquina em docs/FINDINGS.md.
ecdsa puro em Python por coincurve (libsecp256k1) reduziu a derivação por carteira de 3.02 ms → 0.591 ms (~5.1×) em um Apple M3 Max; o piso restante é PBKDF2-HMAC-SHA512 ×2048 a 0.463 ms/carteira.--full, a partir de uma forma fechada exata para os dígitos de seed32 mais um comprimento médio de endereço medido.bx seed (MT19937 de 32 bits semeado a partir do tempo). Fundos reais roubados. https://milksad.info/MIT — veja LICENSE. Termos de uso e orientação de divulgação responsável: DISCLAIMER.md. Relato de problemas: SECURITY.md.