A falha de entropia da COLDCARD: como um fallback silencioso do RNG custou US$ 38 milhões aos usuários

Principais Resultados
- Usuários que geraram e usaram uma seed phrase em uma COLDCARD Mk3 devem gerar imediatamente uma nova seed phrase em outro wallet e migrar seus fundos.
- Usuários de COLDCARD Mk4 ou Mk5 devem migrar se a seed phrase original tiver sido gerada por uma versão de firmware inferior à 5.6.0.
- Usuários de COLDCARD Q devem migrar se a seed phrase original tiver sido gerada por uma versão de firmware inferior à 1.5.0Q.
- Os hardware wallets OneKey não são afetados por esse bug e podem continuar sendo usados com confiança.
Em criptografia, “aleatório” é uma métrica rigorosa. É algo que pode ser quantificado e que precisa resistir a poder computacional real.
Uma frase de recuperação de 12 palavras é sustentada por um número aleatório de 128 bits, codificado em palavras legíveis por humanos conforme a especificação BIP-39. O grau de imprevisibilidade desse número determina diretamente se o wallet consegue resistir a força bruta. 128 bits significam 2¹²⁸ possibilidades. Mesmo juntando todo o poder computacional da Terra, ainda assim você não conseguiria terminar de enumerá-las antes do fim do universo.
Por isso, em teoria, hardware wallets deveriam tratar isso com máximo rigor. É preciso haver um gerador verdadeiro de números aleatórios dedicado (TRNG), que extraia aleatoriedade física do ruído de circuitos, combinado a um secure element (SE) certificado EAL, com um algoritmo maduro misturando tudo no final.
Em 30 de julho de 2026, cerca de 594 BTC (aproximadamente US$ 38 milhões) foram drenados de quase 500 wallets COLDCARD em menos de meia hora. No dia seguinte, a fabricante Coinkite confirmou a causa raiz em “Technical Deep Dive into the Entropy Issue” [1]: desde uma mudança de firmware em março de 2021, um erro de configuração de build vinha fazendo o código cair silenciosamente no gerador pseudoaleatório de software (PRNG) integrado ao MicroPython.
As consequências variam por modelo. A estimativa preliminar da Coinkite é que a Mk3 tenha apenas cerca de 40 bits de espaço de busca efetivo restante, e a Mk4, Mk5 e Q cerca de 72, contra uma meta de projeto de 128. Quarenta bits representam aproximadamente 1,1 trilhão de possibilidades, o que ainda parece astronômico, mas para um invasor que pode alugar um cluster de GPU isso deixou de ser “fisicamente impossível” e virou um projeto de engenharia viável em expectativa. De 128 para 40, o espaço de busca encolheu por um fator de 2⁸⁸, ou 300 septilhões.
Esse roubo de US$ 38 milhões transformou essa expectativa em fato consumado.
Observação: os fundos dos usuários OneKey estão seguros. Nenhum código da OneKey toca esse problema, e a OneKey não usa as implementações nem as dependências envolvidas. Declaração completa: https://x.com/OneKeyHQ/status/2083106588022509974?s=20
TL;DR
- Usuários que geraram e usaram uma seed phrase em uma COLDCARD Mk3: gerem imediatamente uma seed phrase em outro wallet e migrem seus fundos para o novo wallet.
- Usuários que geraram e usaram uma seed phrase em uma COLDCARD Mk4 ou Mk5: se a versão do firmware que gerou a seed phrase original era inferior à 5.6.0, gerem imediatamente uma seed phrase em outro wallet e migrem seus fundos para o novo wallet.
- Usuários que geraram e usaram uma seed phrase em uma COLDCARD Q: se a versão do firmware que gerou a seed phrase original era inferior à 1.5.0Q, gerem imediatamente uma seed phrase em outro wallet e migrem seus fundos para o novo wallet.
- Usuários de hardware wallets OneKey não são afetados por esse bug e podem continuar usando-os com confiança.
Agora vem a parte técnica pesada 👇
Causa raiz: um fallback de software silencioso
Da interface RNG de hardware para libNgU
A migração da pilha criptográfica em 2021 alterou a geração de seed do wallet de ckcc.rng_bytes() para ngu.random.bytes() [6]:
ngu.random.bytes() → libNgU rng_get() → o símbolo rng_get ao qual ele finalmente é vinculado
O risco não está no nome da função nem no tamanho da saída. Está em qual arquivo objeto realmente fornece esse rng_get() final.
#ifndef só verifica se algo está definido, não seu valor
A configuração da placa-alvo definia MICROPY_HW_ENABLE_RNG como 0, o que significava que o caminho de RNG de hardware do MicroPython não estava habilitado.
Mas, na época, libNgU usava [8]:
#ifndef MICROPY_HW_ENABLE_RNG
#error ...
#endif
#ifndef testa apenas se a macro existe. Uma macro definida como 0 ainda está “definida”, então o #error que deveria bloquear um build ruim nunca disparou.
Um símbolo com o mesmo nome deixou a implementação errada entrar no firmware final
Quando o RNG de hardware não está habilitado, o MicroPython ainda fornece um rng_get() com o mesmo nome, mas ele é o fallback PRNG de software Yasmarang [9].
Isso produziu a cadeia completa da falha:
a macro existe, mas seu valor é 0 → a guarda de build da libNgU não é acionada → o firmware ainda compila com sucesso → o rng_get de mesmo nome resolve para o fallback do MicroPython → a geração de seed nunca recebe a entrada do RNG de hardware que deveria receber
Tomado isoladamente, cada elo dessa cadeia é apenas um pequeno descuido. Encadeados, eles tornam sem sentido a função de controle do sistema de build: uma implementação segura existir no código-fonte ou no binário não significa que a chamada crítica de segurança realmente chegue até ela.
Misturar fontes de entropia não compensa a entropia ausente
O estado inicial do fallback envolve parte do UID, SysTick, horário RTC e estado subsegundo; depois disso, Yasmarang avança de forma determinística. libNgU também o mistura com um segundo fluxo Yasmarang que parte de um estado inicial fixo.
Dois fluxos determinísticos que podem ser reconstruídos ou enumerados não ganham nova imprevisibilidade física por serem combinados via XOR ou processados por SHA-256. Hashing pode condicionar a entrada, mas não consegue expandir um conjunto finito de candidatos para um espaço genuinamente desconhecido de 2^256.
O que 40 bits e 72 bits realmente significam
As estimativas de espaço de busca efetivo usadas pela Coinkite não são valores de entropia medidos e certificados. Os dois números descrevem o tamanho do espaço de estados candidatos que um invasor precisa percorrer. Eles não são uma medição de min-entropia, não se convertem em um número médio de tentativas e certamente não fornecem um tempo ou custo específico de ataque.
O custo real de ataque depende de uma cadeia de pré-condições: se o invasor conhece ou consegue adivinhar o UID do dispositivo; se consegue restringir o intervalo de RTC, SysTick e tempo de boot; quantas chamadas RNG ocorreram antes da seed ser gerada; se tem um xpub, endereço ou chave pública para validar candidatos offline; e qual derivação BIP-39/BIP-32 e custo de hardware paralelo precisa executar.
Portanto, a forma precisa de dizer é esta: sob as hipóteses preliminares atuais de ataque da Coinkite, o espaço de busca efetivo da Mk3 é estimado em cerca de 40 bits, e o da Mk4/Q/Mk5 em cerca de 72. Isso não é o mesmo que “todo wallet Mk3 tem exatamente 40 bits de entropia”, nem “os modelos mais novos comprovadamente têm segurança criptográfica de 72 bits”, e certamente não significa que “qualquer computador comum consegue recuperar todos os wallets em um tempo fixo”.
Por que Mk4, Mk5 e Q também são afetadas
The two secure elements on the Mk5 and Q boards (source: coldcard.com)
Os dois secure elements nas placas Mk5 e Q (fonte: coldcard.com)
A Mk4, Mk5 e Q de fato extraem material aleatório de dois secure elements, mas a implementação anterior ao fix não alimentava toda essa saída em um DRBG criptográfico.
O código-fonte público mostra que, depois de hashear o material do secure element, ele pega apenas quatro bytes e os passa para ngu.random.reseed(), substituindo apenas uma palavra de estado de 32 bits do Yasmarang.
Assim, quando o restante do estado do fallback e o rastreamento de chamadas estão fixos:
número de fluxos de saída distinguíveis pelo reseed do secure element ≤ 2³²
Isso não contradiz o “cerca de 72 bits” oficial. O modelo de 72 bits também conta incerteza em estados como UID, timers, RTC e histórico de chamadas. O limite superior combinado e aproximado da Block Engineering fica abaixo de 2^73.3, e eles afirmam explicitamente que isso não é uma certificação de segurança criptográfica de 73 bits [10].
Em outras palavras: a Mk3 é o pior caso, porque não há reseed seguro algum. A entrada do secure element na Mk4/Q/Mk5 reduz o risco, mas um reseed de quatro bytes não restaura a segurança de 128 bits que o projeto buscava.
O comunicado oficial e o escopo real
O comunicado atual da Coinkite começa em Mk3 4.0.1. Mas o código-fonte imutável v4.0.0 já contém o caminho relevante de geração de entropia [7], e não há fix RNG correspondente entre 4.0.0 e 4.0.1.
A Block Engineering vai além, incluindo Mk2/Mk3 v4.0.0–v4.1.9 em sua análise de código-fonte também [10].
Portanto, há dois níveis que precisam ser mantidos separados:
- O escopo oficial da Coinkite: Mk3 4.0.1+;
- O escopo técnico sustentado pelo código-fonte: Mk2/Mk3 v4.0.0–v4.1.9.
Você não pode dizer que a Coinkite confirmou oficialmente Mk2 ou v4.0.0. Também não pode inferir que são seguros apenas porque o comunicado não os cita.
Como o fix funciona
O hotfix em Mk4/Mk5 5.6.0 e Q 1.5.0Q fez mais do que alterar uma expressão condicional. Ele adicionou enforcement em nível de build [3][4][5]:
- Excluir o objeto RNG fallback do MicroPython;
- Garantir que o objeto em nível de placa forneça o
rng_get()global; - Usar
nmpara inspecionar os símbolos nos arquivos objeto; - Fazer o build falhar imediatamente se o fallback ainda exportar o símbolo, ou se o objeto em nível de placa não fornecer corretamente
rng_get().
O que os usuários devem fazer
Mk4, Mk5 e Q
- Primeiro atualize para 5.6.0 ou posterior (Mk4/Mk5), ou 1.5.0Q ou posterior (Q).
- Gere uma seed totalmente nova no firmware corrigido.
- Registre o backup e conclua uma verificação de restauração.
- Confira a fingerprint do wallet e o endereço de recebimento na tela do dispositivo.
- Envie uma pequena transação de teste antes de migrar o restante dos fundos.
- Mantenha o backup antigo até que todas as transferências sejam confirmadas, mas pare de receber no wallet antigo.
Mk3
Atualmente não há firmware fix lançado para a Mk3, e 4.1.9 não é um fix. Os usuários não devem esperar por um firmware que talvez venha depois, porque nenhum firmware novo consegue adicionar entropia a uma seed existente.
Siga a orientação atual de migração da Coinkite: gere uma nova seed por um caminho confiável, verifique o backup, os endereços e uma pequena transação, e então migre seus ativos.
Dois limiares diferentes para dados
Existem dois números para dados: 50 e 99. Eles atendem a dois objetivos diferentes e não podem ser usados de forma intercambiável.
50+ lançamentos adicionados na geração original da seed. Se pelo menos 50 lançamentos justos, independentes e privados de um dado de seis faces, que nunca foram registrados, armazenados ou divulgados, foram misturados quando a seed foi gerada pela primeira vez, a Coinkite diz que não considera essa seed em risco por esse problema de RNG sozinho [11].
50 × log₂(6) ≈ 129.25 bits
Esse é o limiar aproximado de 128 bits para este incidente específico, não uma prova absoluta de segurança para todo o seu processo operacional.
O fluxo Dice Roll Import em uma Mk3 em branco. A Coinkite também oferece um fluxo diferente: em uma Mk3 em branco rodando 4.1.9, selecione Import Existing > Dice Rolls e insira pelo menos 99 lançamentos justos. Esse caminho dedicado consome diretamente a sequência de dados e não usa o gerador afetado do dispositivo.
99 × log₂(6) ≈ 255.91 bits
99 é o limiar do fluxo oficial; trabalhando de trás para frente a partir de um padrão literal de “pelo menos 256 bits”, seriam necessários 100.
Uma passphrase é uma barreira separada, não um fix
Uma passphrase BIP-39 forte, única, aleatória e secreta deriva um wallet diferente e pode adicionar um fator de busca independente; o PIN do dispositivo controla apenas o acesso local e não participa da derivação da chave raiz BIP-39 [12].
Mas uma passphrase não compensa a entropia que o mnemônico nunca teve. Passphrases curtas, citações famosas, padrões previsíveis e senhas reutilizadas ainda podem ser adivinhados. Um erro de digitação também gera um wallet que parece válido, mas é completamente diferente; por isso, é preciso verificar a fingerprint e manter um backup confiável cuja restauração você já testou.
Encerramento
O problema central que este incidente expôs é que a alcançabilidade real de uma implementação crítica de segurança nunca foi imposta pelo sistema de build. “Alguém errou uma macro” fica muito aquém de descrever o ocorrido.
No mínimo, hardware wallets e outros sistemas de geração de chaves deveriam:
- Verificar a qual objeto e símbolo uma API de segurança finalmente se vincula, não apenas conferir a assinatura da função;
- Fazer verificações de capacidade validarem tanto a existência quanto o valor de uma macro;
- Fazer fallbacks críticos de segurança falharem fechados, nunca fornecendo silenciosamente um PRNG comum;
- Fazer a CI inspecionar composição de objetos, link maps, proveniência de símbolos e fluxo de dados de ponta a ponta;
- Provar por testes que a entropia real de hardware chega à seed final, em vez de checar apenas o tamanho da saída, se ela não é zero ou se não se repete;
- Escrever comunicados que separem claramente a versão que gerou a seed, o firmware atual, a versão corrigida e o que fazer com seeds antigas.
O código aberto da COLDCARD foi o que permitiu que pesquisadores externos reconstruíssem essa cadeia de chamadas, e a Coinkite publicou tanto um relatório técnico formal quanto uma versão corrigida.
Afirmar que uma marca é “absolutamente segura” não vale nada. O que vale é transformar “de onde vem a aleatoriedade, a que ela finalmente se vincula, para onde ela vai quando falha e como verificar isso no produto entregue” em uma propriedade do sistema que possa ser reavaliada continuamente.
Referências
[1] Coinkite: Technical Deep Dive into the Entropy Issue [2] Coinkite: Mk3 Security Advisory [3] Coldcard 5.6.0 / 1.5.0Q fix commit [4] Coldcard 5.6.0 release commit [5] Coldcard 1.5.0Q release commit [6] 2021 libNgU seed generation migration commit [7] Official v4.0.0 source snapshot [8] libNgU random.c [9] MicroPython commit introducing the fallback [10] Block Engineering technical analysis [11] Coldcard dice roll math [12] Coldcard passphrase documentation [13] OneKey trezorcrypto.random source branch [14] OneKey seed generation logic [15] OneKey standard Makefile [16] OneKey SCons config






