Wyciek słabości portfeli Coldcard: w ataku z 2026 r. skradziono 1 719 BTC
Podsumowanie rynku AI
Zgłoszona luka w entropii oprogramowania firmware Coldcard umożliwiła offline'owe odzyskiwanie seedów metodą brute force w przypadku urządzeń z domyślną konfiguracją, co doprowadziło do potwierdzonej kradzieży co najmniej 1"719 BTC z tysięcy adresów. Incydent jest istotny, ponieważ zmienia perspektywę ryzyka "cold storage", przenosząc ją na jakość generowania kluczy zamiast kompromitacji sieci, co może wywierać presję na zaufanie do portfeli sprzętowych, skłaniać do pilnych aktualizacji firmware oraz zwiększać krótkoterminowe, motywowane bezpieczeństwem przepływy funduszy i wzmożoną kontrolę praktyk depozytowych BTC.
Wpływ
● Wysoki
Aktywa, których dotyczy
BTC/USDT-1.15%
Analiza AI · BTC/USDTAnaliza AI
▼ Spadkowy
Handluj teraz
⚠️ Analizy generowane przez AI opierają się na treściach z wiadomości i mają charakter informacyjny. Nie stanowią porady inwestycyjnej ani nie odzwierciedlają poglądów BingX. Inwestowanie wiąże się z ryzykiem. Handluj odpowiedzialnie.
30 lipca 2026 r. na łańcuchu bloków odnotowano serię następujących po sobie wypłat z wielu adresów. W ciągu 41 minut wyczyszczono 1 196 adresów jednosygnaturowych, co przełożyło się na zniknięcie ok. 1 082 BTC. Była to pierwsza fala. Na początku sierpnia potwierdzone straty sięgnęły co najmniej 1 719 BTC, czyli ok. 111 mln USD; incydent objął ponad 5 200 adresów i przebiegał w trzech–czterech falach.
Najbardziej niepokoi to, że wiele z tych środków przez miesiące lub lata spoczywało bez ruchu w portfelach "cold". Problem dotyczył kluczy prywatnych. Wszystkie skompromitowane klucze zostały wygenerowane przez sprzętowe portfele Coldcard, przy czym właściciele nie dodali dodatkowej entropii (np. rzutów kością) ani nie włączyli hasła BIP39. Zaatakowane urządzenia korzystały z najprostszego, domyślnego ustawienia.
Skala ryzyka zależała od linii firmware’u. W przypadku Mk2 i Mk3 z wersjami 4.0.1–4.1.9 efektywna entropia wynosiła jedynie ok. 40 bitów, co jest najpoważniejszym scenariuszem. Dla Mk4, Mk5 oraz Q na wybranych wersjach oprogramowania entropia miała wynosić ok. 72 bity, nadal w zasięgu ataków offline metodą brute force.
Producent, Coinkite, zareagował szybko. 30 lipca opublikował ostrzeżenie, a dzień później udostępnił poprawione wersje firmware’u: Mk2 i Mk3 do 4.2.0 lub nowszej, Mk4 i Mk5 do 5.6.0 lub nowszej, a Q do 1.5.0Q lub nowszej. Firma zadeklarowała zniszczenie całego zapasu podatnych wersji i wstrzymanie wysyłek.
Według analizy atak nie miał związku z przejęciem zdalnym ani "zatruciem" łańcucha dostaw. Napastnik nie musiał dotykać urządzeń: klucze odzyskał offline, systematycznie testując możliwe seedy w przestrzeni wyszukiwania. Luka przez lata pozostawała uśpiona, aż zewnętrzni badacze opracowali narzędzie do brute force i ją ujawnili. Muan miał później odtworzyć cały łańcuch ataku.
Poniżej streszczono techniczne tło, rekonstruowane na przykładzie Mk3 z firmware’em 4.1.9, i wskazano pojedynczą decyzję w kodzie, która otworzyła drogę do masowego opróżniania tysięcy urządzeń.
Źródło podatności
Coldcard miał generować seedy z użyciem wbudowanego sprzętowego TRNG (true random number generator) w układzie STM32L475. Dwa błędy we firmware’ie sprawiły jednak, że proces w praktyce opierał się na programowym PRNG o niemal identycznym stanie na urządzeniach na całym świecie. Wykorzystywany PRNG to Yasmarang, co na Mk3 obniżało efektywną entropię do ok. 40 bitów.
Błąd 1: sprzętowy RNG został celowo wyłączony
W konfiguracji builda Coldcard wyłączono wsparcie sprzętowego RNG w MicroPythonie, w pliku mpconfigboard.h:
#define MICROPY_HW_ENABLE_RNG (0)
Na pierwszy rzut oka nie musi to wyglądać groźnie: Coinkite ma własne ckcc.rng_bytes, które woła bezpośrednio sprzętowy TRNG STM32 i daje większą kontrolę niż warstwa portu MicroPythona. Problem ujawnia się dopiero w zależnościach poniżej. Funkcja my_random_bytes() z biblioteki libngu pobiera liczby losowe przez makro CHIP_TRNG_32(), które po rozwinięciu wywołuje rng_get() z warstwy STM32 portu MicroPythona i traktuje wynik jak dane z TRNG. Gdy MICROPY_HW_ENABLE_RNG ustawiono na 0, rng_get() bez ostrzeżenia przechodzi w tryb awaryjny, co prowadzi do drugiego błędu.
Błąd 2: tryb awaryjny rng_get() uznano za TRNG
Analiza random.c w libngu prowadzi do gałęzi else w ports/stm32/rng.c (warstwa portu STM32 MicroPythona). Gdy MICROPY_HW_ENABLE_RNG jest równe 0, rng_get() nie czyta RNG>DR, tylko zwraca wyjście programowego PRNG (Yasmarang). W konsekwencji każde Mk3 opuszczające fabrykę z tą konfiguracją podąża tą ścieżką.
W punkcie seedowania Yasmarang używa m.in. UID układu oraz SysTick>VAL w chwili pierwszego wywołania rng_get(). SysTick ma zakres 0–79 999 (przy 80 MHz i wartości reload 80 000), czyli daje ok. 17 bitów entropii. Dodatkowo dochodzą rejestry RTC (RTC_TR i RTC_SSR) odczytane zaraz po włączeniu zasilania, zanim aplikacja je zainicjalizuje. W zweryfikowanych na łańcuchu przypadkach oba te pola wynosiły 0.
Po zsumowaniu składników UID wnosi ok. 14 bitów, SysTick ok. 17 bitów, oba pola RTC 0 bitów, a naciśnięcia przycisków ok. 5 bitów. Daje to realne źródło entropii rzędu 36–37 bitów dla całego procesu generowania seeda. Deklarowane przez Coinkite "ok. 40 bitów" pasuje do przestrzeni wyszukiwania wynikającej z inżynierii wstecznej kodu — do spenetrowania w kilka dni przez klaster GPU.
Jak przebiegał atak
Podczas pierwszego uruchomienia Mk3 losowość jest zużywana według kilku powtarzalnych profili. Kluczowe zadanie napastnika polega na zamodelowaniu każdego z nich i wiernym odtworzeniu kroków PRNG.
Faza 1: stan początkowy
Zaraz po podaniu zasilania występują dwa zdarzenia: (1) w libngu istnieje stały, globalny strumień Yasmarang (z parametrem pad=0x0a8ce26f oraz stałymi n, d, dat), (2) pierwsze wywołanie rng_get() seeduje Yasmarang wartościami pad=UID^SysTick, n=RTC_TR=0, d=RTC_SSR=0, dat=0. W tej chwili cała "losowość" sprowadza się do 32-bitowego pad oraz kilku małych wymiarów możliwych do enumeracji.
Faza 2: naciśnięcia przycisków
Podczas pierwszej konfiguracji użytkownik musi ustawić PIN, zaakceptować warunki (OK) i poruszać się po menu. Każde naciśnięcie uruchamia _start_scan() w shared/mempad.py, które przez shuffle(self.scan_order) wywołuje _rand_below() trzy razy. Długość scan_order wynosi NUM_ROWS=4. Funkcja shuffle (Fisher–Yates) korzysta z randbelow podpiętego bezpośrednio do ngu.random.uniform (C-level _rand_below).
Po analizie źródeł v4.1.9 wskazano trzy profile zużycia losowości:
Profil A (typowa konfiguracja detaliczna): użytkownik naciska przyciski dwa razy przed akceptacją warunków; następnie settings.save() szuka wolnego miejsca wśród 32 slotów (z wyłączeniem my_pos), co daje 31 slotów i 30 wywołań _rand_below; dalej dochodzą naciśnięcia podczas wpisywania PIN i nawigacji (kpad_b w zakresie [4, 34]); na końcu wykonywane jest random_bytes(32).
Profil B (pierwszy start po flashowaniu lub wyczyszczeniu, puste NVRAM): nvstore wykonuje shuffle(32) (31 wywołań _rand_below), potem "blanking" zużywa 3×16×256B, czyli 3072 kroki w synchronizacji bez sprzężenia zwrotnego (stan tylko się przesuwa). Dla onboarding przy pustym NVRAM wykonywane jest jeszcze jedno shuffle(32), następnie zużycie z naciśnięć (kpad_b [4, 34]) i finalnie my_random_bytes(32).
Profil C (paper wallet): po utworzeniu istniejącego portfela, ponowne wejście do menu wymaga 8–25 naciśnięć. Potem my_random_bytes(32) staje się bezpośrednio kluczem prywatnym, z pominięciem listy słów BIP39.
Faza 3: budowa seeda i wyprowadzenie adresu
Od random_bytes(32) do końcowego adresu Bitcoin Mk3 stosuje deterministyczny łańcuch: SHA256 pojedynczy (sha256s), następnie BIP39 (24 słowa, wordlist=english), potem PBKDF2-HMAC-SHA512 z 2048 rundami, dalej BIP32 (HMAC-SHA512 "Bitcoin seed"), po czym ścieżka m/{44,49,84}'/0'/0'/0/0 i adres bech32 z hash160 skompresowanego klucza publicznego (domyślnie BIP84, P2WPKH). Gdy napastnik odgadnie pad, może offline odtworzyć cały ciąg.
Faza 4: brute force na GPU i trafienia
Metoda atakującego sprowadzała się do czterech kroków:
1) Zbudowanie zbioru kandydatów pad: współrzędne wafla X i Y od 0 do 72 połączone z przestrzenią UID (ok. 5 300 wartości) oraz iloczyn kartezjański z SysTick 0–79 999. Daje to ok. 424 mln kandydatów pad — liczba duża, ale do przerobienia w kilka dni na GPU.
2) Dla każdego pad enumeracja liczby naciśnięć (kpad_b 4–34). Ponieważ dla rtc_tr i rtc_ssr obserwowano trafienia 0, umieszczano je w pętlach zewnętrznych.
3) Uruchomienie w jądrze GPU pełnego pipeline'u seeda: SHA256, BIP39, PBKDF2 (2048 rund), BIP32, Hash160 i Bech32. Stały strumień z libngu można było policzyć wcześniej; jądro pobierało słowa po indeksie, ograniczając koszt symulacji.
4) Dopasowanie wyników przez filtr Blooma lub posortowaną tablicę hash160 z złożonością O(log n), celując w zbiór jednosygnaturowych adresów P2WPKH w sieci.
Koszt obliczeniowy został oszacowany liczbowo. Przeszukanie przestrzeni Profilu A na pojedynczym GPU Apple M1 — z siatką 72×72, 80 000 wariantów SysTick i 31 możliwymi wartościami liczby naciśnięć, co daje ok. 14,8 mld kandydatów — zajęło ok. 8,6 dnia. Klaster klasy data center na A100 mógł skrócić to do kilku godzin.
Przestrzeń ok. 72 bitów dla Mk4, Mk5 i Q jest 2^32 razy większa niż dla Mk3. Źródło słabości ma jednak ten sam charakter, a sposób profilowania i modelowania pozostaje analogiczny; zmienia się głównie skala infrastruktury — z jednej maszyny na klaster — i czas rośnie z dni do tygodni. To koszt, który atakujący wciąż mogą uznać za akceptowalny, co ma tłumaczyć obecność tych modeli na listach ofiar.