COLDCARD: коммит «runs» отключил аппаратный ГСЧ и ослабил кошельки

Гостевой пост на insider.btcpp.dev от ddustin, известного разработчика Core Lightning, разбирает историю коммитов прошивки аппаратного биткоин-кошелька COLDCARD, чтобы объяснить недавний баг со слабой энтропией при генерации кошельков.
Автор сначала показывает, каким должен быть здоровый коммит: на своём примере он приводит сообщение длиной 235 символов на изменение в 15 строк кода, соотношение сообщение/код около 16. Чем выше это соотношение, тем подробнее объяснена причина изменения, и для кода, критичного для безопасности, оно должно быть высоким.
Коммит, который внёс баг в COLDCARD, устроен ровно наоборот: сообщение из пяти символов, слово «runs», на изменение в 1534 строки кода, то есть соотношение около 0,003. Второй коммит, тоже причастный к проблеме, ещё хуже: сообщение из одного символа «x» на изменение примерно в 1000 строк, соотношение около 0,001.
В коммите «runs» разработчик подключал и настраивал C-код, чтобы кастомный micropython работал на микроконтроллере STM32, стандартном чипе для таких устройств. Внутри этого коммита появилась строка #define MICROPY_HW_ENABLE_RNG (0), которая отключает у micropython использование аппаратного генератора случайных чисел (RNG) и переключает его на программный генератор Yasmarang. Строку сопровождает единственный комментарий в коде: «У нас есть собственная версия этого кода».
По реконструкции ddustin, разработчик пытался переопределить функцию pyb_rng_get_obj из библиотеки STM32 собственным файлом rng.c, но библиотека STM32 уже определяет эту переменную, компилятор выдавал ошибку «duplicate symbol» (дублирующееся определение). Вместо того чтобы разобраться в причине конфликта, разработчик, похоже, методом проб и ошибок нашёл, что установка MICROPY_HW_ENABLE_RNG в 0 убирает ошибку компиляции, потому что это попутно удаляет и второе (конфликтующее) определение переменной. Ошибка компилятора была последним предупреждением, но её просто заглушили, а не разобрали.
Проблема оказалась глубже, чем задумывал автор изменения: прошивка COLDCARD написана на C, а прикладной слой, на Python. Функция создания нового кошелька make_new_wallet() в версии COLDCARD 4.0.0 вызывает random.bytes(32), а этот вызов вообще не проходит через переопределённую разработчиком функцию pyb_rng_get, вместо этого он идёт через rng_get(), которая из-за отключённого MICROPY_HW_ENABLE_RNG использует micropython-реализацию на базе слабого генератора Yasmarang вместо аппаратного RNG. То есть переопределение, которое разработчик вносил, в реальности не применялось к генерации кошельков, зато отключение аппаратного RNG применялось везде, включая генерацию seed-фразы нового кошелька.
Автор поста заключает, что вся история, следствие того, что критичный для безопасности код меняли и принимали без понимания последствий и без содержательного описания в коммите, а слои абстракции micropython создали иллюзию, что встраиваемому разработчику не обязательно разбираться в C и особенностях процессора.
Ключевые факты
- Гостевой пост ddustin (разработчика Core Lightning) на insider.btcpp.dev восстанавливает историю коммитов, приведших к багу со слабой энтропией в прошивке аппаратного биткоин-кошелька COLDCARD.
- Коммит с сообщением «runs» (5 символов) изменил 1534 строки кода, соотношение сообщение/код около 0,003, для сравнения здоровый пример автора даёт около 16.
- Внутри этого коммита строка
#define MICROPY_HW_ENABLE_RNG (0)отключила аппаратный генератор случайных чисел micropython, переключив его на слабый программный генератор Yasmarang. - Функция создания нового кошелька
make_new_wallet()в COLDCARD 4.0.0 вызываетrandom.bytes(32), который идёт черезrng_get()и из-за отключённого аппаратного RNG использует именно Yasmarang вместо аппаратной энтропии. - Второй коммит с сообщением «x» (1 символ, около 1000 изменённых строк) также способствовал проблеме; источник не называет имя разработчика, дату коммитов и число затронутых устройств.
Почему это важно
Баг показывает, как минимальная дисциплина коммитов, сообщение из одного слова на полторы тысячи изменённых строк, может незаметно подменить источник случайности в самом критичном месте кода: генерации приватного ключа кошелька. Слабый генератор Yasmarang вместо аппаратного RNG делает seed-фразу предсказуемой или менее случайной, что напрямую угрожает средствам пользователей.
Кому это важно
Владельцам аппаратных кошельков COLDCARD, разработчикам встраиваемых систем и прошивок для криптокошельков, а также всем, кто пишет или ревьюит код, критичный для безопасности, коммиты в таком коде требуют не меньшей, а большей строгости и документирования.
Как это применить
Автор советует конкретную практику: у коммитов, которые меняют критичный для безопасности код, соотношение длины сообщения к объёму изменений должно быть высоким, сообщение обязано объяснять, что и почему меняется. Ошибки компилятора вроде «duplicate symbol» нельзя просто «убирать» первым попавшимся способом, нужно разбираться в причине конфликта, а не гасить симптом.
Можно ли доверять
Разбор построен на прямом чтении истории коммитов и кода прошивки COLDCARD, с цитированием конкретных строк и объяснением логики компиляции, это техническая реконструкция, а не предположение. Источник не называет разработчика, дату коммитов, число затронутых устройств и не приводит официального заявления COLDCARD/Coinkite, по всем этим пунктам факты просто отсутствуют в материале.
Риски и подводные камни
Материал описывает механизм бага, но не даёт данных о масштабе последствий: сколько устройств и средств пострадало, неизвестно. Реконструкция логики разработчика (что именно он делал и почему), это предположение автора поста на основе кода, а не подтверждённые слова причастного разработчика.
«Не выпускайте код, который вы не понимаете.»
— ddustin, автор гостевого поста, разработчик Core Lightning