WEBVTT Automatically aligned narration script; synthetic voices; timing not manually reviewed.

00:00:00.000 --> 00:00:01.540
The Random Number Degenerator.

00:00:01.780 --> 00:00:04.500
Why is a mistake in randomness so hard to catch?

00:00:04.820 --> 00:00:07.960
From the blog: Sjorsnado passed through a transformer.

00:00:08.632 --> 00:00:20.212
Aaron and I recently discussed the COLDCARD vulnerability on episode 101 of Bitcoin, Explained. A firmware integration mistake weakened the randomness used to generate wallet seeds, without producing an obvious malfunction.

00:00:20.962 --> 00:00:34.002
Some mistakes would be easy to spot. If a bug caused every hardware wallet to generate the same seed, a tester setting up two devices would get identical seed phrases. Customers might discover that their new wallet already had transactions.

00:00:34.602 --> 00:00:54.322
COLDCARD's mistake was harder to spot. The affected devices could still generate different seeds, receive bitcoin, and spend it normally. A tester setting up a handful of wallets might see no duplicate seed phrases or failed transactions. Yet the seeds came from a much smaller set of possibilities than intended, making them easier for an attacker to guess.

00:00:55.042 --> 00:01:06.522
A uniformly random 256-bit secret has 2²⁵⁶ possibilities. Merely storing a number in 256 bits does not give it that much unpredictability.

00:01:06.982 --> 00:01:26.082
Imagine choosing one number between zero and 255, hashing it, and using the resulting 256-bit digest as a secret. Each digest would look quite different. But an attacker could reproduce the entire collection with 256 hash calculations. Encoding the results as seed words changes nothing.

00:01:26.442 --> 00:01:38.282
A cryptographically secure pseudorandom generator can expand unpredictable input into the bytes an application needs. A general-purpose generator for simulations need not keep its output unpredictable to an attacker.

00:01:39.162 --> 00:01:55.422
Coinkite's technical account describes a migration to libNgU that changed which random-byte function wallet creation called. That function ultimately reached MicroPython's general-purpose Yasmarang generator instead of the intended STM32 hardware random-number generator (RNG).

00:01:56.482 --> 00:02:22.312
LibNgU wraps libsecp256k1 and other cryptographic operations. Correct elliptic-curve arithmetic does not settle how the surrounding application generates its seed. For background on software libraries, chapter 4 of Bitcoin: A Work in Progress, libsecp256k1 (Software Libraries), explains why Bitcoin Core replaced OpenSSL and the risks of changing cryptographic libraries.

00:02:22.762 --> 00:02:36.532
In the podcast, we described the danger of simulator code reaching a real device. Here, the specific failure involved a runtime library's fallback, selected when building the firmware. It was not a runtime switch after hardware failure.

00:02:37.182 --> 00:02:57.552
The build checks confused a setting's existence with its value. COLDCARD defined MICROPY_HW_ENABLE_RNG as zero, intentionally using its own hardware implementation. LibNgU's #ifndef guard accepted a defined setting, while MicroPython's #if selected its software generator because the value was zero.

00:02:57.852 --> 00:03:13.752
The board implementation also lacked the global rng_get() function libNgU expected; the fallback supplied it. The linker found a function and reported no error. Fixing the guard would expose the configuration problem; the call also needed connecting to the hardware implementation.

00:03:14.792 --> 00:03:19.032
Hardware-RNG code could therefore be present without wallet creation ever using it.

00:03:19.332 --> 00:03:38.022
The fallback implementation initialized its state using part of the chip identifier and timing registers, then evolved it deterministically. These inputs can make devices and boots produce different outputs, but identifiers are fixed manufacturing information, and timing values may be correlated or constrained. Neither guarantees an unguessable secret.

00:03:38.722 --> 00:03:55.082
There were two copies of Yasmarang in this path. LibNgU XORed the MicroPython fallback's output with output from its own Yasmarang generator, which started from public constants. If an attacker can reproduce both streams, XORing them leaves the result reproducible too.

00:03:55.642 --> 00:04:15.752
Keeping the device offline cannot prevent an attacker from reconstructing its seed and testing candidates against a known address. The attacker still needs to reproduce or search the relevant generator state and execution history. Block's analysis and Wizardsardine's reconstruction examine the device, timing, and execution-history assumptions behind attack estimates.

00:04:16.262 --> 00:04:48.072
Later devices added another input, from the secure elements. This was a chance to compensate for the broken STM32 path: retaining and securely mixing enough independent, unpredictable input could have kept the seed secure despite that first mistake. More precisely, the Mk4 reseeding code hashed the collected material and retained just four bytes of the digest before passing it to ngu.random.reseed(). That function replaced one 32-bit state word in libNgU's generator. It did not reseed MicroPython's fallback.

00:04:48.452 --> 00:05:14.732
Four bytes can hold at most 2³² distinct values. For any fixed values of the generator's other state and call history, varying that four-byte input can therefore select at most 2³² output streams. Producing more bytes afterward cannot undo that bottleneck. This is a limit on what that input contributes, rather than a claim that every affected wallet can be recovered by trying exactly 2³² candidates.

00:05:15.832 --> 00:05:44.032
Why didn't testing catch the problem? Consider a test that requests random bytes and checks that successive requests differ. A deterministic generator can pass. The affected seed-generation code checked that its 32-byte output contained more than four distinct byte values, then hashed it twice with SHA-256. A predictable sequence can pass that check too. Even tests that examine the statistical distribution of output do not establish that an attacker cannot reproduce it.

00:05:44.272 --> 00:06:10.842
Testing many devices for duplicate seeds would not necessarily expose the problem either. In a toy system with 2³² equally likely seeds, 1,000 independent draws have only about a 0.012% chance of producing any duplicate. Yet an attacker would have at most 2³² candidates to enumerate. This is a birthday-paradox example, not an estimate of COLDCARD's actual seed space.

00:06:11.802 --> 00:06:47.902
Aaron and I discussed the same dangerous middle ground in episode 83, The Milk Sad Vulnerability. Libbitcoin's developers describe bx seed as intended for demonstrations, but people used it to create real wallets. It produced long, varied-looking outputs using Mersenne Twister initialized from only 32 bits of clock data. A tutorial could seem to work: generate a seed, derive an address, receive bitcoin. The weakness did not announce itself. A deliberately fixed dummy seed would have made the distinction between a working example and a secure wallet much harder to overlook.

00:06:48.922 --> 00:06:55.922
For COLDCARD, Coinkite says the fix added a build-time check that rng_get() comes from the board's hardware-RNG code.

00:06:56.582 --> 00:07:24.652
In the episode, Aaron and I explore making test implementations return an obvious fixed value, and combining sources so any one can supply enough unpredictability on its own. Our discussion needs one qualification: XORing a value with an identical copy gives zero. At least one source must remain unpredictable to the attacker, and the combination must preserve that uncertainty. Either approach still needs checks of what the production build actually uses.

00:07:25.392 --> 00:07:48.622
Audits and open source create opportunities to find these mistakes, but scope matters. Security labs have demonstrated how to extract seeds from hardware wallets. Ledger's Donjon published a physical seed-extraction attack on Trezor, and Kraken Security Labs used voltage glitching to extract an encrypted seed and then crack its PIN. These attacks ask whether someone who gets hold of your device can recover its secrets.

00:07:49.642 --> 00:08:08.722
My theory in the podcast was that researchers may have concentrated on that question and missed the generation problem. You can spend weeks trying to read a seed out of a chip without checking whether it was unpredictable when created. I don't know what each researcher examined on COLDCARD; this is a possible explanation, not a finding about those labs' work.

00:08:09.852 --> 00:08:21.562
Generation does get scrutiny elsewhere: Donjon also discovered weak seed generation in the Trust Wallet browser extension. The lesson is to check both how a seed is created and how it is protected afterward.

00:08:22.282 --> 00:08:38.192
The review question is simple: where do these bytes actually come from? Follow it through the build configuration, linked implementation, reseeding interface, and hardware call. Put those checks in the build and test suite, so a refactor cannot silently change the answer.

00:08:38.532 --> 00:08:57.942
For more on testing, combining randomness sources, and the limits of audits, listen to episode 101 of Bitcoin, Explained. Aaron and I also discuss the trade-offs of hardware-wallet multisig and, toward the end, our responsibilities as podcasters who had accepted Coinkite as a sponsor. Subscribe in your podcast app for future technical Bitcoin discussions.

