Ваш холодный криптокошелёк взломан. Разбор уязвимости RNG у Coldcard Mk2–Mk5

от автора

Аппаратный кошелёк Coldcard считается одним из самых защищённых устройств для холодного хранения биткоина. Две мелкие ошибки в конфигурации сборки прошивки отключили аппаратный генератор случайных чисел и заставили устройство использовать программный генератор 2005 года. Для моделей Mk2/Mk3 это свело генерацию сид-фразы к эффективной энтропии в ~23 бита — пространство, которое полностью перебирается за пару часов на встроенной графике обычного ноутбука. Я восстановил 274 сид-фразы и 1 396 адресов — и все они оказались уже обчищены. Волны краж, связанных с этим дефектом, унесли около 90,5 млн долларов, и эти деньги до сих пор лежат на адресах атакующих.


Ночь, когда опустошили 1 196 адресов

30 июля 2026 года, 01:10 UTC. Блокчейн-аналитики первыми заметили, что по сети движется что-то необычное: четыре адреса-коллектора начали принимать средства с огромного числа адресов, которые до этого выглядели как типичные адреса холодного хранения. Комиссия 30 sat/vB, автоматизированный сбор, никаких эмоций. За 41 минуту было опустошено 1 196 адресов на 1 082,65 BTC.

Хронология четырёх волн краж: даты, число адресов, объёмы в BTC и топология вывода

Хронология четырёх волн краж: даты, число адресов, объёмы в BTC и топология вывода

Через сутки — вторая волна: ещё от 1 126 до 1 478 адресов и 45,97–76,16 BTC. Семь адресов-коллекторов, комиссия 50,2 sat/vB, следы замены неподтверждённых транзакций (RBF) — атакующие явно торопились. 2 августа — третья волна, и она принципиально другая по топологии: 293 адреса защищённых хранилищ (vault, тип P2WSH) на 207,73 BTC выводились напрямую, по схеме 1:1, без промежуточных коллекторов, с комиссией около 201 sat/vB. Четвёртая волна (1–5 августа) — всего 29 подтверждённых адресов на 0,29 BTC, связь с Coldcard не подтверждена, но на картину это уже не влияет.

Что общего у пострадавших кошельков? Почти все — «холодные». Их создали на устройствах, которые не подключались к интернету, не контактировали с заражённым софтом и физически хранились у владельцев. Люди делали всё «правильно» — и всё равно потеряли деньги.

Совокупные цифры по данным на 7 августа: 4 925 подтверждённых адресов и 1 405,005 BTC (coldcardwatch.com); оценка Galaxy Research — около 7 300 адресов и 1 719 BTC, с учётом подозрительных — до 7 700 адресов и 2 055 BTC. При курсе около 64 500 долларов за 1 BTC средства волн 1–3 составляют примерно 90,5 млн долларов. Деньги с момента кражи не трогали.

Почему Coldcard считали эталоном

Coldcard — линейка аппаратных кошельков компании Coinkite, выпускающаяся с 2018 года (модели Mk2, Mk3, Mk4, Q и Mk5). Репутацию устройство зарабатывало годами: открытая прошивка на базе MicroPython (её может прочитать и проверить любой желающий), опция полностью автономной работы без подключения к компьютеру, поддержка всех основных стандартов деривации (BIP39, BIP32, BIP44/BIP49/BIP84) и, главное, декларируемый аппаратный генератор случайных чисел (TRNG).

Для криптосообщества наличие честного источника энтропии — это фундамент всей безопасности. Сид-фраза генерируется один раз, при первом включении устройства, и от того, насколько случайной она получилась, зависит всё: даже идеальная криптография бессмысленна, если ключ можно угадать. Именно поэтому доверие к Coldcard строилось на словах «аппаратный генератор случайных чисел».

На деле всё оказалось сложнее — и одновременно в сотню раз банальнее.

Первопричина: две мелкие ошибки сборки

Когда 30 июля Coinkite выпустила официальное предупреждение (Security Advisory), а инженеры Block Engineering — технический разбор, сообщество получило шок-контент. Причина оказалась не в изощрённой атаке на криптографию, а в двух мелких дефектах сборки, которые по отдельности были бы безобидны.

Первый дефект — в конфигурации сборки прошивки всех моделей Coldcard стоял макрос MICROPY_HW_ENABLE_RNG=0. Он отключал использование аппаратного генератора случайных чисел микроконтроллера.

Второй дефект — в библиотеке libngu, которая отвечает за криптографические примитивы. Проверка директивы препроцессора была записана как #ifdef, а не #if. Разницу легко не заметить при ревью:

/* Было (ошибочно): */#ifdef MICROPY_HW_ENABLE_RNG    /* использовать аппаратный TRNG */#else    /* программный резервный генератор */#endif

Директива #ifdef проверяет только «определён ли макрос», но не его значение. А макрос был определён — просто равным нулю. #ifdef счёл его включённым и скомпилировал ветку с программным генератором. Корректная #if потребовала бы ненулевого значения и отправила бы прошивку в правильную ветку.

Фрагмент libngu: #ifdef вместо #if, из-за чего макрос со значением 0 включает программный PRNG

Фрагмент libngu: #ifdef вместо #if, из-за чего макрос со значением 0 включает программный PRNG

Результат: вместо аппаратного TRNG при генерации сид-фразы использовался программный резервный генератор — Yasmarang, компактный псевдослучайный генератор 2005 года. И использовался он безусловно, во всех моделях (Mk2, Mk3, Mk4, Q, Mk5), на протяжении всего периода эксплуатации.

Как это работает (объяснение для IT-аудитории)

Yasmarang — детерминированный генератор: при одинаковых входных параметрах он всегда выдаёт одинаковую последовательность. Его состояние описывается четырьмя переменными: 32-битными pad, n, d и 8-битным dat. На криптографическом уровне это не «генератор случайных чисел», а сдвигово-смесительная конструкция: 32-битные арифметические операции, побитовые сдвиги и XOR. Для перемешивания потока данных она годится, для генерации секретов — нет.

В прошивке Coldcard энтропия собирается из двух независимых потоков этого генератора:

chip  = Yas(uid_systick, 0, 0)      # старт от счётчика и идентификатора микроконтроллераmixer = Yas(pad_reseed, 69, 233)    # старт от «случайного» пада и констант
Схема двух потоков Yasmarang: chip и mixer, входы и итоговая энтропия через SHA-256

Схема двух потоков Yasmarang: chip и mixer, входы и итоговая энтропия через SHA-256

Поток chip инициализируется значением, которое объединяет счётчик SysTick (24-битный таймер ядра ARM Cortex-M) и идентификатор микроконтроллера (UID): pad = ((Y << 16) | X) ^ SysTick, где X и Y — младшие и старшие 16 бит UID. Итоговая энтропия — это SHA-256 от сырых данных генератора. Для Mk2/Mk3 применялось однократное хэширование (SHA-256s), для Mk4 — двукратное (SHA-256d).

Дальше идёт математика, которая и сделала всё перебираемым.

Для Mk2 и Mk3 поток mixer полностью фиксирован: значение pad_reseed постоянно (0x0a8ce26f), параметры n = 69, d = 233. Вся случайность сконцентрирована в потоке chip. Номинально состояние генератора — около 72 бит (32 бита pad + 32 бита счётчика/инкремента + 8 бит аккумулятора). Но фактическое разнообразие входных данных намного меньше:

  • Счётчик SysTick хоть и 24-битный, на практике принимает значения примерно до 80 000. Это log₂(80 000) ≈ 16,3 бита разнообразия, а не 24.

  • Старшие 16 бит UID частично коррелированы между устройствами и дают лишь около 7,2 бита реального разнообразия.

Итого: 16,3 + 7,2 ≈ 23,5 бита, округлённо 2²³ ≈ 8,4 млн комбинаций. Это не шифр — это pet-project на выходные, который реализуется на встроенной графике обычного ноутбука.

Отдельно стоит упомянуть связь между числом циклов генерации Q и номером вызова генератора K, на котором формируется итоговое значение энтропии. Дизассемблирование и симуляция прошивки показали устойчивую формулу K = 3Q + 8: каждый цикл генерации «съедает» три вызова генератора, плюс восемь служебных вызовов в начале процедуры. Это значит, что параметр Q (число циклов) становится полноценной координатой пространства поиска наряду с pad, y и st.

Для Mk4 картина иная: поток mixer получает 32-битное начальное значение pad_reseed, и пространство поиска расширяется до 2³² × 2²² ≈ 2⁵⁴. При производительности 600 000 кандидатов в секунду полный перебор на одном GPU занял бы около 950 GPU-лет. Формально уязвимость Mk4 та же, практически — это уже требует промышленных ресурсов.

Как я это искал: история расследования

Дальше — как из гипотезы получаются 274 восстановленные сид-фразы.

Шаг 1. Отбрасываем гипотезы. В первые сутки после волн краж сообщество перебирало объяснения: утечка базы данных производителя? Атака на цепочку поставок? Физический компромат? Подмена устройств? Каждая гипотеза разбивалась о простой факт: кошельки были холодными, купленными в разное время и в разных местах. Общим было только одно — производитель.

Шаг 2. Исходники. Как только Coinkite и Block Engineering указали на генератор, стало возможным проверить всё самостоятельно. Прошивка Coldcard открытая — огромное преимущество для исследователя. Чтение исходников подтвердило обе ошибки: MICROPY_HW_ENABLE_RNG=0 и #ifdef/#if. Но наличие бага в коде не равно пониманию того, как он проявляется на уровне сид-фразы. Нужно было восстановить полную цепочку: Yasmarang → SHA-256 → BIP39 (24 слова) → PBKDF2-HMAC-SHA512 (2048 итераций) → BIP32 → BIP84/BIP44.

Шаг 3. Модель и неизвестные. Нам известны уравнения генератора, но не изнвестны конкретных значений для произвольного устройства: SysTick, UID, число циклов Q. Ключевой инсайт пришёл из дизассемблирования: связь K = 3Q + 8. Значит, пространство поиска для Mk2/Mk3 можно параметризовать кортежем (Q, pad, y, st) и перебрать.

Шаг 4. Дёшево и сердито. Здесь у исследования есть вторая важная мораль: для discovery-фазы не нужен дорогой облачный GPU. Первичное обнаружение рабочих параметров выполнялось на встроенной графике обычного ноутбука — AMD Radeon 680M (iGPU процессоров Ryzen 6000H) через OpenCL (Mesa rusticl). Производительность скромная: около 149 млн «сырых» дериваций в секунду и примерно 11 тысяч полных цепочек end-to-end (BIP39 → PBKDF2 → BIP32 → BIP84). Но этого хватило, чтобы закрыть полный перебор пространства 2²² × 17 значений Q за 1–2 часа. Стоимость discovery-фазы — 0 долларов. Первые 184 сид-фразы были найдены на встроенной графике.

Бюджет исследования: $0 на iGPU-фазу и $5,50 на облачные GPU; сравнение производительности 680M и RTX 5090

Бюджет исследования: $0 на iGPU-фазу и $5,50 на облачные GPU; сравнение производительности 680M и RTX 5090

Шаг 5. Уточнение. Для Mk4 картина усложнилась: пространство 2⁵⁴ не перебирается целиком. Я сформулировал четыре конкурирующих сценария интерпретации процедуры генерации (обозначили их b, a1, a2, c), отличающихся порядком использования двух потоков и моментом внесения 32-битного pad_reseed. Ни один из них не дал подтверждённых совпадений — только ложные срабатывания фильтров. Это согласуется с теорией: при 2⁵⁴ вариантах вероятность наткнуться на истинное совпадение при частичном переборе ничтожна.

Шаг 6. Полное восстановление. Фазы расширения параметров, уточнения и полного восстановления выполнялись на облачных NVIDIA RTX 5090 (архитектура Blackwell, CUDA, около 600 000 кандидатов в секунду). Два инстанса по два GPU — и весь облачный счёт составил 5,5 доллара.

Контроль качества. Каждый кандидат, прошедший фильтр Блума, перепроверялся независимо на CPU в чистом Python. Контрольный прогон по полному набору параметров дал 188 из 188 идентичных результатов — 100 % воспроизводимость. Дополнительно воспроизвел публичный тест-вектор из репозитория eminogrande (pad = 3 547 157 718, Q = 22) — совпадение с эталоном подтвердило корректность уравнений генератора.

Пара слов о фильтрах Блума, потому что без них ничего бы не вышло. Целевых адресов — десятки миллионов (список пострадавших плюс полный перечень из 24,15 млн финансированных адресов для проверки остатков). Держать их в памяти GPU как есть невозможно. Фильтр Блума сжимает множество в битовый массив с четырьмя хэш-функциями: ложноотрицательных ответов он не даёт в принципе, а ложноположительные я отсекал повторной точной проверкой. В результате — сотни тысяч проверок кандидатов в секунду при полностью контролируемом качестве.

Результаты: 274 → 1 396 → 0

Что получилось в итоге:

Показатель

Значение

Восстановленные сид-фразы

274 (184 на этапе discovery + 90 новых)

Уникальные адреса

1 396

Диапазон глубин деривации

0…97

Адреса с ненулевым остатком

0

Проверено ненулевых адресов

24 155 093

Покрытие verified-выборки coldcardwatch

1 396 / 4 925 = 28,3 %

Распределение по параметру Q имеет колоколообразную форму в диапазоне 6…26 с «хвостом» до 100. Мода — Q = 12: на него приходится 52 сид-фразы, 19 % всей выборки. В среднем одна сид-фраза порождает 5,1 адреса (медиана 2), но есть и фраза с 98 адресами — видимо, очень старый и очень активно использовавшийся кошелёк.

Колоколообразное распределение параметра Q: основной диапазон 6–26, мода Q=12, хвост до 100

Колоколообразное распределение параметра Q: основной диапазон 6–26, мода Q=12, хвост до 100

Типология адресов ожидаема: 1 390 из 1 396 — P2WPKH (префикс bc1q…, схема BIP84, по умолчанию для Coldcard), шесть — legacy P2PKH (BIP44). Ни одного P2SH — и это согласуется с моделью: адреса с префиксом 3… в verified-выборке coldcardwatch (13 шт.), скорее всего, результат мультиподписных схем и vault-скриптов, которые из одиночной сид-фразы не порождаются.

И главное число: все 1 396 адресов уже опустошены. Это означает одно: атакующие дошли до того же пространства раньше, что предсказуемо. Уязвимость была не теоретической — пространство ключей активно «собиралось» до того, как кто-либо опубликовал анализ.

Масштаб: что это говорит об атакующих

Данные крипторазведки о поведении атакующих заставляют задуматься. По данным Galaxy Research, в атаках выявлено 25 и более различных паттернов; TRM Labs идентифицировали как минимум 15 независимых операторов. Это не один «гений», а целый тренд: независимые группы, которые параллельно перебирали одно и то же пространство. Часть средств прошла через микшеры: 64,9 BTC — через Wasabi CoinJoin, 200 ETH — через Tornado Cash. Одни операторы выводили средства осторожно, мелкими порциями, другие — оптом с высокими комиссиями.

Средства волн 1–3 (около 90,5 млн долларов) с момента кражи не двигаются. Возможные объяснения: атакующие ждут благоприятного момента для конвертации или смешивания, либо упираются в ликвидность — вывести такие объёмы без привлечения внимания сложно даже в 2026 году.

Что делать владельцам Coldcard

Если вы держали средства на Coldcard, есть ровно одна рекомендация: Любая сид-фраза, сгенерированная до 31 июля 2026 года, должна считаться потенциально скомпрометированной.

Конкретный план:

  1. Обновите прошивку до версии, содержащей исправление Coinkite — коммит ca72463709f4 от 31 июля 2026 года. Генерация энтропии переведена на аппаратный TRNG, программный резервный генератор исключён из процедуры инициализации.

  2. Создайте новый кошелёк на обновлённой прошивке. Ключевое слово — именно «новый»: не восстанавливайте старую фразу, а сгенерируйте новую.

  3. Переведите средства на новые адреса и не используйте старую сид-фразу нигде — ни для восстановления, ни для проверки, ни в другом кошельке.

  4. Если подозреваете, что старый кошелёк уже опустошён, проверьте свои адреса по публичным спискам пострадавших (coldcardwatch.com).

Это касается всех моделей: Mk2, Mk3, Mk4, Q и Mk5. Для Mk4 пространство 2⁵⁴ формально не перебрано в рамках исследования, но архитектурный дефект тот же, и перестраховка здесь оправдана.

Этика раскрытия

Ключевой вопрос, который справедливо задают любому исследователю уязвимостей: а не сделали ли вы хуже?

Что я публикую: методологию, параметры восстановления (Q, pad, y, st), SHA-256-хэши мнемоник и статистические агрегаты. Это позволяет независимо проверить результаты, не раскрывая ни одной секретной единицы.

Что я не публикую: сид-фразы, закрытые ключи и адреса жертв. Их нет ни в этой статье, ни в репозитории.

Обоснование простое. Все рассмотренные адреса взяты из публичных списков уже опустошённых адресов (coldcardwatch.com, Galaxy Research). На момент исследования на них не было средств. Публикация параметров восстановления не может причинить дополнительного финансового вреда — вору нечего украсть, а исследователю есть что проверить. В этой ситуации «молчание» было бы хуже: чем дольше владельцы не знают о проблеме, тем выше шанс, что следующая партия затронутых средств уйдёт атакующим.

Возможные вопросы

«Зачем публиковать параметры восстановления? Разве это не поможет атакующим?»

Параметры (Q, pad, y, st) работают только в связке с публичными списками уже обчищенных адресов. Сид-фразы не публикуются, живых кошельков нет — атакующие, которые и так первыми прошли это пространство, не получат ничего нового. А сообщество получает возможность проверить результаты и оценить масштаб бедствия.

«Это не исследование, а снятие сливок с чужих результатов»

Исследование задокументировало атаку, которая уже произошла, и подтвердило её масштаб независимым воспроизведением. Этот вектор первыми использовали атакующие; моя задача — сделать так, чтобы он не повторился: вендоры получили публичное подтверждение первопричины, пользователи — чёткую рекомендацию по миграции.

«Стоимость $5,50 выглядит неправдоподобно дёшево»

Это реальные цифры: $0 за discovery-фазу на встроенной графике ноутбука и $5,50 за аренду облачных RTX 5090. Именно в этом и состоит главный вывод для индустрии: перебор пространства в 23 бита — тривиальная задача даже за такие деньги.

«А Mk4 можно взломать так же?»

Формально уязвимость Mk4 та же, но пространство поиска 2⁵⁴ при нашей производительности потребовало бы около 950 GPU-лет. Но поиск параметров не подтвердил эксплуатацию Mk4. Однако владельцам Mk4/Q/Mk5 я всё равно рекомендую миграцию: дефект архитектурный, и нельзя исключать целевые атаки на отдельные регионы пространства.

Выводы

Этот инцидент — поучительная история о том, как одна строка условной компиляции может обнулить годы работы инженеров и доверие пользователей.

Для вендоров вывод жёсткий: криптографическая энтропия не должна зависеть от программного резервного генератора, выбранного условной компиляцией. Если TRNG недоступен — процедура инициализации должна прерываться (принцип fail-closed), а не молчаливо деградировать до детерминированного PRNG. Плюс обязательные криптографические самопроверки генератора (known-answer tests) на этапе загрузки и статический анализ директив препроцессора.

Для пользователей вывод не менее жёсткий: «холодное хранение» защищает от внешнего мира, но не от ошибок в прошивке. Проверяйте дату создания сид-фразы, обновляйте прошивку и не доверяйте маркетингу — доверяйте аудиту.

И последнее. Каждая сид-фраза, сгенерированная на уязвимой прошивке до 31.07.2026, потенциально скомпрометирована — это не гипотеза, а перебранное пространство. Если вы хранили в нём что-то ценное, переезд на новую фразу на обновлённой прошивке — единственный безопасный путь.


Ссылки и материалы

Исследование выполнено в соответствии с принципами ответственного раскрытия. В публикации отсутствуют сид-фразы, закрытые ключи и адреса жертв.

ссылка на оригинал статьи https://habr.com/ru/articles/1069172/