
비트코인 키 엔트로피에 대한 교육용 연구: 취약한 RNG 지갑 열거(Milk Sad / CVE-2023-39910 계열)와 좋은 키의 브루트포스 공격이 불가능함을 비교.
비트코인 키 보안이 실제로 어디에서 비롯되는지 — 그리고 어디에서 비롯되지 않는지에 대한 교육용 연구입니다. 실제 BIP39/BIP32 파생을 기반으로, 동일한 아이디어의 양극단에 놓인 두 가지 실험을 담고 있습니다:
| 실험 | 검색 공간 | 실현 가능? | 배우는 점 |
|---|---|---|---|
weak_entropy — 약한 RNG 지갑 공간 열거 | 2³² (나쁜 RNG) | 가능, 수일 내 | 지갑은 그 뒤에 있는 엔트로피만큼만 강합니다. |
blind_search — 좋은 키 공간 무차별 대입 | 2¹⁶⁰ (좋은 RNG) | 불가능, 영원히 | 실제 엔트로피가 있다면 키 공간은 물리적으로 검색 불가능합니다. |
핵심은 바로 그 대비입니다: 키는 나쁜 무작위성을 공격하는 방식으로 도난당하지, 좋은 무작위성을 연산으로 압도하는 방식으로는 절대 도난당하지 않습니다. 두 실험 모두 동일한 종류의 실제 메인넷 주소를 파생하므로, 유일한 변수는 엔트로피입니다.
이 저장소는 방어적 교육과 보안 연구를 위한 것입니다: 약한 RNG 지갑 버그 (Milk Sad / CVE-2023-39910, 2018 Trust Wallet 확장 프로그램 버그 및 그 계열)가 왜 치명적이었는지, 그리고 제대로 생성된 키를 무차별 대입하는 것이 왜 불가능한지 이해하기 위한 것입니다.
weak_entropy는 자체 완결형 합성 공간을 열거합니다. 여기의
weak_entropy()는 취약점 클래스의 의도적인 모델입니다 — BIP39에 공급되는
32비트 PRNG — 실제 지갑 RNG의 바이트 단위 정확한 복제가 아닙니다. 이
모듈이 생성하는 주소는 누구의 실제 지갑에도 해당하지 않습니다.blind_search는 자신의 노드에 대한 읽기 전용 잔액 조회입니다. 키를
파생하지 않으며 아무것도 지출할 수 없습니다. 대표 모드는 블라인드 검색의
무의미함을 측정합니다.통제할 수 있는 지갑, 테스트넷, 또는 샌드박스 환경에서 사용하세요. 다른 사람의 돈을 대상으로 삼지 마세요.
교육 및 승인된 보안 연구 전용입니다. 소유하거나 테스트할 명시적 서면 허가를 받은 키, 지갑, 시스템에만 사용하세요. 타인의 지갑이나 시스템에 대한 무단 접근 및 디지털 자산 절도는 기술적 수단과 관계없이 미국 CFAA(18 U.S.C. § 1030), 영국 Computer Misuse Act 1990, EU Directive 2013/40/EU와 같은 법률에 따라 불법입니다. "있는 그대로(as is)", 보증 없이 제공되며, 작성자는 오용에 대해 아무런 책임도 지지 않습니다. 이는 법률 자문이 아닙니다. 전체 약관 및 책임 있는 공개 지침: DISCLAIMER.md.
지갑의 보안은 다운스트림의 SHA-256 / PBKDF2 / secp256k1 강도가 아니라 시드에 들어간 엔트로피에 의해 결정됩니다.
BIP39는 엔트로피를 니모닉으로 변환하고, PBKDF2는 이를 시드로 확장하며, BIP32는 키 트리를 파생합니다. 이 모든 과정은 강력합니다. 그러나 그 어느 것도 엔트로피를 추가하지 않습니다 — 시드가 작은 집합에서 추출되었다면 지갑이 생성할 수 있는 모든 주소는 열거 가능하며, 어떤 양의 해싱도 이를 숨길 수 없습니다.
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
트리 구조는 이 아이디어를 반영합니다: 하나의 공유 derivation 코어와, 동일한
주소 공간에 대해 상반된 결론을 내리기 위해 이를 사용하는 두 실험.
pip install -r requirements.txt # runtime deps
# optional — installs the package + `bsec-enumerate` / `bsec-scan` commands:
pip install -e .
coincurve(네이티브 libsecp256k1)는 파생 속도를 약 5배 높이지만 선택 사항입니다
— derivation.py는 동일한 출력으로 순수 Python ecdsa에 폴백합니다. 여기서
네트워크에 접촉하는 것은 옵트인 --check-balance 플래그와 blind_search(로컬
비트코인 노드 필요)뿐입니다.
모든 것은 저장소 루트에서 모듈로 실행되며 설치가 필요 없습니다:
python3 -m bitcoin_security.weak_entropy --help
python3 -m bitcoin_security.blind_search --help
weak_entropy: 약한 엔트로피는 열거 가능합니다지갑의 "무작위" 16바이트 BIP39 엔트로피는 실제로 32비트 시드에서 비롯됩니다. 전체 파생 과정은 실제이며 사양 벡터에 대해 검증되었습니다.
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
행은 최소 형식입니다(address <TAB> seed32). 다른 모든 형태는 seed32의
결정적 함수이므로 --show로 필요할 때 재생성됩니다. 덤프는 샤드 단위로 재개
가능합니다(연속적이고, 공백이 없고, 중복이 없는 커버리지를 재현함이
검증되었습니다). 전체 플래그 참조는
weak_entropy/README.md에 있습니다.
규모 경고. 전체 2³² 덤프는 약 196GB(최소) 또는 약 1.05TB (
--full)이며, 네이티브 백엔드에서도 며칠이 걸립니다 —docs/FINDINGS.md참조. 전체가 필요할 일은 거의 없습니다. 검사하려면 제한된 범위만 덤프하세요.
blind_search: 좋은 키는 무차별 대입할 수 없습니다scantxoutset를 통해 로컬(프루닝 허용) 노드의 UTXO 집합을 스캔하는 실제
작동하는 잔액 스캐너입니다. 그 목적은 **무결과(null result)**입니다: 주소
공간을 블라인드로 검색하고, 아무것도 찾지 못함을 측정한 다음, 항상 그럴 수밖에
없음을 보여주는 계산을 출력합니다.
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
결과: 공간의 크기는 2¹⁶⁰ ≈ 1.46×10⁴⁸입니다. 약 5,500만 개의 주소에
잔액이 있으므로, 무작위 주소에 자금이 있을 확률은 약 3.8×10⁻⁴¹입니다. 초당
10억 개의 주소 속도로도 약 8×10²³년을 기다려야 합니다 — 우주 나이의 약 61조
배입니다. 하드웨어 문제가 아닙니다: Landauer 한계에 따르면 2¹⁶⁰까지 단순히
세는 것만으로도 태양의 남은 에너지 출력을 초과합니다. 노드 설정은
blind_search/README.md에 있습니다.
두 실험 모두 동일한 종류의 주소를 파생합니다. 유일한 차이는 시드가 추출된 집합의 크기입니다:
그 간격 — 128비트 — 이 전부입니다. 이 부류의 모든 실제 도난 사건은 바로 그 간격 안에서 발생했습니다.
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)
전체 세부 사항, 방법 및 머신 사양은
docs/FINDINGS.md에 있습니다.
ecdsa를 coincurve(libsecp256k1)로 교체하여 Apple
M3 Max에서 지갑당 파생 시간을 **3.02ms → 0.591ms(약 5.1배)**로 단축했습니다.
남은 하한은 지갑당 0.463ms의 PBKDF2-HMAC-SHA512 ×2048입니다.seed32 자릿수에 대한 정확한 닫힌 형태(closed
form)와 측정된 평균 주소 길이를 기반으로 196.3GB 최소 / 약 1.05TB
--full.bx seed(시간으로
시드된 32비트 MT19937). 실제 자금이 도난당함. https://milksad.info/