COLDCARD RNG incident the public record, collected and explained
Informational only, and this site never asks for your seed words. details

Informational only. This is an open source collection of what others have published about the incident, together with an explanation of it. It is not financial, security or legal advice, and not a substitute for professional advice about your own situation. It is not affiliated with, endorsed by, or speaking for Coinkite. Material is attributed and quoted as published; where sources disagree their scenarios are kept separate with their assumptions rather than reconciled into one answer. Everything is meant to be checked against the linked evidence rather than taken on trust. Act on your own judgement about a particular situation. Editorial standards and corrections.

Do not disclose recovery material to a website, form, message or support account. This site never asks for it, and contributions containing recovery words or private keys are not accepted.

Maxwell's correction on the defect mechanism

hn-maxwell-mechanism

https://news.ycombinator.com/item?id=49141886

Latest reviewed change

source content difference between and

New Hacker News comments discussed compiler checks, entropy taint analysis and the need to avoid exploitation details while coins remain vulnerable.

seen +11 -11 full history below
 Well, if the developer read libngu's random.c he would have noticed that it uses rng_get() and that it's asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered "wait, what?").
 So I'd say the root cause that the developer didn't check what random.bytes() does under the hood or how libngu expects random to be generated.
 reply
+nullc <relative-time> | prev | next [–]
+Aside, I'm somewhat surprised that MISRA (as of 2012 at least) doesn't have a directive related to definedness vs value errors.
+reply
+RustyRussell <relative-time> | parent | next [–]
+Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.

First lines only. The complete diff is in the timeline below.

Organisation
Hacker News
Evidence role
Independent technical analysis
Published
2026-08-02
Source changes
2
Detected differences
5
Unreviewed
0
Copies held
6

Greg Maxwell (nullc), commenting on the bitcoin++ Insider writeup, disputes the framing of the defect as a small build-flag change and states the mechanism differently: the commit added the whole RNG infrastructure, and the failure is a mixup between a value test and a definedness test across two repositories. The micropython path is gated by an #if on MICROPY_HW_ENABLE_RNG while the replacement path is gated by an #ifndef, so defining the macro to (0) deactivated one without activating the other. He adds that the Mk4-class reseed masked rather than corrected the problem, and questions hashing 64 bits of TRNG output before discarding half.

Held because it is a technical correction to published reporting by a recognised source, and because it bears directly on how this site's own explainer characterises the guard. Attributed commentary on a public forum, not a vendor statement or a reproduction: it is graded as reported, and the code claims within it are checkable against the repositories the site already cites.

Every check is recorded, including checks that found no text change. A detected edit is therefore bounded between two checks. The publisher's exact save time is not observable from this record. Last checked .

  1. capture correction difference between and Current capture correction +140 -233

    The collector changed this Algolia item from pretty-printed API JSON to deterministic readable thread text; the same comment subtree is held, with HTML decoded and volatile API fields omitted.

    seen · Captured here 10,336 chars
    What changed from the previous capture 373 lines
    -{
    -  "author": "nullc",
    -  "children": [
    -    {
    -      "author": "nullc",
    -      "children": [
    -        {
    -          "author": "RustyRussell",
    -          "children": [],
    -          "created_at": "2026-08-02T09:04:47.000Z",
    -          "created_at_i": 1785661487,
    -          "id": 49142559,
    -          "options": [],
    -          "parent_id": 49142014,
    -          "points": null,
    -          "story_id": 49140405,
    -          "text": "Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.",
    -          "title": null,
    -          "type": "comment",
    -          "url": null
    -        }
    -      ],
    -      "created_at": "2026-08-02T07:34:54.000Z",
    -      "created_at_i": 1785656094,
    -      "id": 49142014,
    -      "options": [],
    -      "parent_id": 49141886,
    -      "points": null,
    -      "story_id": 49140405,
    -      "text": "Aside, I&#x27;m somewhat surprised that MISRA (as of 2012 at least) doesn&#x27;t have a directive related to definedness vs value errors.",
    -      "title": null,
    -      "type": "comment",
    -      "url": null
    -    },
    -    {
    -      "author": "smithcoin",
    -      "children": [
    -        {
    -          "author": "nullc",
    -          "children": [],
    -          "created_at": "2026-08-02T17:54:43.000Z",
    -          "created_at_i": 1785693283,
    -          "id": 49146689,
    -          "options": [],
    -          "parent_id": 49142373,
    -          "points": null,
    -          "story_id": 49140405,
    -          "text": "Hopefully people are being a little circumspect right now with explotation instructions because there are still vulnerable coins out there (particularly from mk4+ wallets, as well ones with passwords that are not strong enough) which have not been taken or rescued yet and may still be rescued by owners.",
    -          "title": null,
    -          "type": "comment",
    -          "url": null
    -        }
    -      ],
    -      "created_at": "2026-08-02T08:37:43.000Z",
    -      "created_at_i": 1785659863,
    -      "id": 49142373,
    -      "options": [],
    -      "parent_id": 49141886,
    -      "points": null,
    -      "story_id": 49140405,
    -      "text": "Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?",
    -      "title": null,
    -      "type": "comment",
    -      "url": null
    -    },
    -    {
    -      "author": "dist-epoch",
    -      "children": [],
    -      "created_at": "2026-08-02T10:28:58.000Z",
    -      "created_at_i": 1785666538,
    -      "id": 49143073,
    -      "options": [],
    -      "parent_id": 49141886,
    -      "points": null,
    -      "story_id": 49140405,
    -      "text": "I wonder if some sort of &quot;taint&quot; analysis could be used - you generate a byte of randomness and trace it back through code until you see all the sources of entropy which were used as input.<p>Hard problem in general, but with LLMs surely this is possible now, either with them inspecting or by them providing some sort of &quot;formal&quot; proof - this function calls this function which reads this buffer which gets input from here...",
    -      "title": null,
    -      "type": "comment",
    -      "url": null
    -    },
    -    {
    -      "author": "egwor",
    -      "children": [],
    -      "created_at": "2026-08-02T11:30:10.000Z",
    -      "created_at_i": 1785670210,
    -      "id": 49143450,
    -      "options": [],
    -      "parent_id": 49141886,
    -      "points": null,
    -      "story_id": 49140405,
    -      "text": "Although I might disagree with the style of the write up, I think it is right that there are weak engineering practices applied:<p>- not enough information provided in commit message. \n- this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware)\n- limited (automated) testing\n- potential confusion by the author over C concepts\n- additional complexity which could have been hidden behind a cleaner interface rather than using defines<p>I agree about your points about:<p>- defines over multiple repo&#x27;s made this more complicated that was necessary. \n- fault injections would have been the appropriate way to test this (or maybe mocks)\n- testing of randomness needs careful design, e.g. RNG seeding can be a good way<p>Taking a step back, we as a community of excellence need to emphasise that this isn&#x27;t a criticism about the person&#x2F;author. There needs to be a clear analysis of what went wrong whilst being kind to the person&#x2F;people involved.",
    -      "title": null,
    -      "type": "comment",
    -      "url": null
    -    },
    -    {
    -      "author": "inigyou",
    -      "children": [
    -        {
    -          "author": "nullc",
    -          "children": [
    -            {
    -              "author": "dale_glass",
    -              "children": [
    -                {
    -                  "author": "nullc",
    -                  "children": [
    -                    {
    -                      "author": "inigyou",
    -                      "children": [
    -                        {
    -                          "author": "nullc",
    -                          "children": [],
    -                          "created_at": "2026-08-02T17:40:41.000Z",
    -                          "created_at_i": 1785692441,
    -                          "id": 49146562,
    -                          "options": [],
    -                          "parent_id": 49146187,
    -                          "points": null,
    -                          "story_id": 49140405,
    -                          "text": "Right, that&#x27;s particularly a concern for DSA nonces.  Coldcard uses RFC 6979 however, and duplicates at the 32-bit word level is a different matter than at the byte level.",
    -                          "title": null,
    -                          "type": "comment",
    -                          "url": null
    -                        }
    -                      ],
    -                      "created_at": "2026-08-02T16:57:49.000Z",
    -                      "created_at_i": 1785689869,
    -                      "id": 49146187,
    -                      "options": [],
    -                      "parent_id": 49146167,
    -                      "points": null,
    -                      "story_id": 49140405,
    -                      "text": "There have been cases where a small statistical bias like avoiding duplicate bytes can be amplified to a full break. I don&#x27;t think this problem is applicable to a bitcoin private key though.",
    -                      "title": null,
    -                      "type": "comment",
    -                      "url": null
    -                    }
    -                  ],
    -                  "created_at": "2026-08-02T16:55:41.000Z",
    -                  "created_at_i": 1785689741,
    -                  "id": 49146167,
    -                  "options": [],
    -                  "parent_id": 49145802,
    -                  "points": null,
    -                  "story_id": 49140405,
    -                  "text": "I have absolutely no interest in defending this code-- and already considered it untrusthworthy before any of this.  But in the interest of accuracy:<p>&gt; It does a memcpy of a minimum of 4 bytes<p>This is a common misreading of MIN().  MIN(4,x) is a number that is a MAXIMUM of 4, not a minimum.<p>Count is the number of bytes remaining in the buffer.  The input to the copy is a 4-byte word.  min(4,count) will produce a number 0-4 which is always equal to or less than count.  The copy will not overflow the buffer or overrun the input: If count is 3, for example, then here will be 3 and it will copy 3 bytes.<p>&gt; believe it errors out if the HWRNG returns the same value twice. That&#x27;s actually a thing that can legitimately happen. &quot;0&quot; is also a legitimate output.<p>It&#x27;s been a while since I looked but I believe the STM32 manual advises you to throw away data when this happens, because the rng is updated async with the processor and reads that are too fast will produce 0s or duplicate values.  Entropy loss from doing so is generally negligible. Were it me I&#x27;d read enough into a cryptographic hash to render the output cryptographically close to uniform and not have to worry about it further. (particularly since some uses of cryptographic numbers are extremely sensitive to even small biases)<p>I do wonder how fatal MP_EFAULT actually is...-- on a device like this being jumpy at failing the RNG is reasonable, but if it bricks the device (for example) that would be too much for a condition that (IIRC) the datasheet says can happen.  If that error is worse than causing a reboot then it might be the case that their emergency fix deployment might have the effect of causing problems by deploying never-actually-tested code into the wild.  When I initially looked at this code before finding the flaw I was somewhat surprised that this test didn&#x27;t produce spurious failures.<p>[Maybe someone who isn&#x27;t traveling and on vacation might want to trace out that error condition, -- and check my vague recollection of the STM32 datasheets, as I could be remembering some other part]",
    -                  "title": null,
    -                  "type": "comment",
    -                  "url": null
    -                }
    -              ],
    -              "created_at": "2026-08-02T16:08:18.000Z",
    -              "created_at_i": 1785686898,
    -              "id": 49145802,
    -              "options": [],
    -              "parent_id": 49145062,
    -              "points": null,
    -              "story_id": 49140405,
    -              "text": "The my_random_bytes implementation doesn&#x27;t look good at all at first glance. Let&#x27;s see:<p>1. I believe it errors out if the HWRNG returns the same value twice. That&#x27;s actually a thing that can legitimately happen. &quot;0&quot; is also a legitimate output.<p>2. &quot;here&quot; is a terrible name for a length<p>3. It does a memcpy of a minimum of 4 bytes to the destination, even if count is lower. It&#x27;ll also overflow longer buffers with a length not divisible by 4.",
    -              "title": null,
    -              "type": "comment",
    -              "url": null
    -            }
    -          ],
    -          "created_at": "2026-08-02T14:32:44.000Z",
    -          "created_at_i": 1785681164,
    -          "id": 49145062,
    -          "options": [],
    -          "parent_id": 49144079,
    -          "points": null,
    -          "story_id": 49140405,
    -          "text": "<a href=\"https:&#x2F;&#x2F;github.com&#x2F;switck&#x2F;libngu&#x2F;blob&#x2F;537519a829259622ea6b0334fbafd6cae852852f&#x2F;ngu&#x2F;random.c#L77\" rel=\"nofollow\">https:&#x2F;&#x2F;github.com&#x2F;switck&#x2F;libngu&#x2F;blob&#x2F;537519a829259622ea6b03...</a><p>vs<p><a href=\"https:&#x2F;&#x2F;github.com&#x2F;Coldcard&#x2F;micropython&#x2F;blob&#x2F;4107246f8a080807b62c3b4838e71e812ea68b6f&#x2F;ports&#x2F;stm32&#x2F;rng.c#L55\" rel=\"nofollow\">https:&#x2F;&#x2F;github.com&#x2F;Coldcard&#x2F;micropython&#x2F;blob&#x2F;4107246f8a08080...</a>",
    -          "title": null,
    -          "type": "comment",
    -          "url": null
    -        }
    -      ],
    -      "created_at": "2026-08-02T12:46:41.000Z",
    -      "created_at_i": 1785674801,
    -      "id": 49144079,
    -      "options": [],
    -      "parent_id": 49141886,
    -      "points": null,
    -      "story_id": 49140405,
    -      "text": "At no point did I ever think that setting &quot;enable HW RNG&quot; to 0 &quot;provided a more aggressive handling of fault&#x2F;error conditions&quot;. I always assumed it disabled the HW RNG.",
    -      "title": null,
    -      "type": "comment",
    -      "url": null
    -    },
    -    {
    -      "author": "mlcrypto",
    -      "children": [],
    -      "created_at": "2026-08-02T16:16:51.000Z",
    -      "created_at_i": 1785687411,
    -      "id": 49145865,
    -      "options": [],
    -      "parent_id": 49141886,
    -      "points": null,
    -      "story_id": 49140405,
    -      "text": "The article clearly indicates the size of the changes: &quot;The commit message is 5 characters and is simply the word “runs.” The commit changes 1534 lines of code making the ratio 5&#x2F;1534 = ~0.003&quot;",
    -      "title": null,
    -      "type": "comment",
    -      "url": null
    -    },
    -    {
    -      "author": "killerstorm",
    -      "children": [],
    -      "created_at": "2026-08-02T18:52:59.000Z",
    -      "created_at_i": 1785696779,
    -      "id": 49147215,
    -      "options": [],
    -      "parent_id": 49141886,
    -      "points": null,
    -      "story_id": 49140405,
    -      "text": "Well, if the developer read libngu&#x27;s random.c he would have noticed that it uses  rng_get() and that it&#x27;s asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered &quot;wait, what?&quot;).<p>So I&#x27;d say the root cause that the developer didn&#x27;t check what random.bytes() does under the hood or how libngu expects random to be generated.",
    -      "title": null,
    -      "type": "comment",
    -      "url": null
    -    }
    -  ],
    -  "created_at": "2026-08-02T07:08:53.000Z",
    -  "created_at_i": 1785654533,
    -  "id": 49141886,
    -  "options": [],
    -  "parent_id": 49140405,
    -  "points": null,
    -  "story_id": 49140405,
    -  "text": "This writeup isn&#x27;t very good and misses&#x2F;misunderstands the programming error that leads to the flaw.<p>I&#x27;m commenting because I think it&#x27;s important to understand the issue.<p>The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.<p>The fundamental cause is a mixup between a value test and a definedness test.<p>Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault&#x2F;error conditions.<p>The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef.   So &quot;#define MICROPY_HW_ENABLE_RNG (0)&quot; deactivated the micropython implementation but <i>failed</i> to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn&#x27;t fire because MICROPY_HW_ENABLE_RNG was _defined_).<p>This was easier to miss because the usages weren&#x27;t only in different files-- they were in different repositories.<p>There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input).   But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...<p>In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)<p>RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output.  I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG.  It&#x27;s something of a &quot;color of your bits&quot; issue ( <a href=\"https:&#x2F;&#x2F;ansuz.sooke.bc.ca&#x2F;entry&#x2F;23\" rel=\"nofollow\">https:&#x2F;&#x2F;ansuz.sooke.bc.ca&#x2F;entry&#x2F;23</a> ).<p>The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.<p>The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play.  I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).<p>Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).",
    -  "title": null,
    -  "type": "comment",
    -  "url": null
    -}
    +comment: 49141886
    +author: nullc
    +created_at: 2026-08-02T07:08:53.000Z
    +parent: 49140405
    +body:
    +This writeup isn't very good and misses/misunderstands the programming error that leads to the flaw.
    +I'm commenting because I think it's important to understand the issue.
    +The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.
    +The fundamental cause is a mixup between a value test and a definedness test.
    +Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault/error conditions.
    +The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef. So "#define MICROPY_HW_ENABLE_RNG (0)" deactivated the micropython implementation but failed to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn't fire because MICROPY_HW_ENABLE_RNG was _defined_).
    +This was easier to miss because the usages weren't only in different files-- they were in different repositories.
    +There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input). But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...
    +In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)
    +RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output. I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG. It's something of a "color of your bits" issue ( https://ansuz.sooke.bc.ca/entry/23 ).
    +The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.
    +The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play. I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).
    +Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).
    +
    +comment: 49142014
    +parent: 49141886
    +author: nullc
    +created_at: 2026-08-02T07:34:54.000Z
    +body:
    +Aside, I'm somewhat surprised that MISRA (as of 2012 at least) doesn't have a directive related to definedness vs value errors.
    +
    +comment: 49142373
    +parent: 49141886
    +author: smithcoin
    +created_at: 2026-08-02T08:37:43.000Z
    +body:
    +Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?
    +
    +comment: 49142559
    +parent: 49142014
    +author: RustyRussell
    +created_at: 2026-08-02T09:04:47.000Z
    +body:
    +Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.
    +
    +comment: 49143073
    +parent: 49141886
    +author: dist-epoch
    +created_at: 2026-08-02T10:28:58.000Z
    +body:
    +I wonder if some sort of "taint" analysis could be used - you generate a byte of randomness and trace it back through code until you see all the sources of entropy which were used as input.
    +Hard problem in general, but with LLMs surely this is possible now, either with them inspecting or by them providing some sort of "formal" proof - this function calls this function which reads this buffer which gets input from here...
    +
    +comment: 49143450
    +parent: 49141886
    +author: egwor
    +created_at: 2026-08-02T11:30:10.000Z
    +body:
    +Although I might disagree with the style of the write up, I think it is right that there are weak engineering practices applied:
    +- not enough information provided in commit message.
    +- this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware)
    +- limited (automated) testing
    +- potential confusion by the author over C concepts
    +- additional complexity which could have been hidden behind a cleaner interface rather than using defines
    +I agree about your points about:
    +- defines over multiple repo's made this more complicated that was necessary.
    +- fault injections would have been the appropriate way to test this (or maybe mocks)
    +- testing of randomness needs careful design, e.g. RNG seeding can be a good way
    +Taking a step back, we as a community of excellence need to emphasise that this isn't a criticism about the person/author. There needs to be a clear analysis of what went wrong whilst being kind to the person/people involved.
    +
    +comment: 49144079
    +parent: 49141886
    +author: inigyou
    +created_at: 2026-08-02T12:46:41.000Z
    +body:
    +At no point did I ever think that setting "enable HW RNG" to 0 "provided a more aggressive handling of fault/error conditions". I always assumed it disabled the HW RNG.
    +
    +comment: 49145062
    +parent: 49144079
    +author: nullc
    +created_at: 2026-08-02T14:32:44.000Z
    +body:
    +https://github.com/switck/libngu/blob/537519a829259622ea6b03...
    +vs
    +https://github.com/Coldcard/micropython/blob/4107246f8a08080...
    +
    +comment: 49145802
    +parent: 49145062
    +author: dale_glass
    +created_at: 2026-08-02T16:08:18.000Z
    +body:
    +The my_random_bytes implementation doesn't look good at all at first glance. Let's see:
    +1. I believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
    +2. "here" is a terrible name for a length
    +3. It does a memcpy of a minimum of 4 bytes to the destination, even if count is lower. It'll also overflow longer buffers with a length not divisible by 4.
    +
    +comment: 49145865
    +parent: 49141886
    +author: mlcrypto
    +created_at: 2026-08-02T16:16:51.000Z
    +body:
    +The article clearly indicates the size of the changes: "The commit message is 5 characters and is simply the word “runs.” The commit changes 1534 lines of code making the ratio 5/1534 = ~0.003"
    +
    +comment: 49146167
    +parent: 49145802
    +author: nullc
    +created_at: 2026-08-02T16:55:41.000Z
    +body:
    +I have absolutely no interest in defending this code-- and already considered it untrusthworthy before any of this. But in the interest of accuracy:
    +> It does a memcpy of a minimum of 4 bytes
    +This is a common misreading of MIN(). MIN(4,x) is a number that is a MAXIMUM of 4, not a minimum.
    +Count is the number of bytes remaining in the buffer. The input to the copy is a 4-byte word. min(4,count) will produce a number 0-4 which is always equal to or less than count. The copy will not overflow the buffer or overrun the input: If count is 3, for example, then here will be 3 and it will copy 3 bytes.
    +> believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
    +It's been a while since I looked but I believe the STM32 manual advises you to throw away data when this happens, because the rng is updated async with the processor and reads that are too fast will produce 0s or duplicate values. Entropy loss from doing so is generally negligible. Were it me I'd read enough into a cryptographic hash to render the output cryptographically close to uniform and not have to worry about it further. (particularly since some uses of cryptographic numbers are extremely sensitive to even small biases)
    +I do wonder how fatal MP_EFAULT actually is...-- on a device like this being jumpy at failing the RNG is reasonable, but if it bricks the device (for example) that would be too much for a condition that (IIRC) the datasheet says can happen. If that error is worse than causing a reboot then it might be the case that their emergency fix deployment might have the effect of causing problems by deploying never-actually-tested code into the wild. When I initially looked at this code before finding the flaw I was somewhat surprised that this test didn't produce spurious failures.
    +[Maybe someone who isn't traveling and on vacation might want to trace out that error condition, -- and check my vague recollection of the STM32 datasheets, as I could be remembering some other part]
    +
    +comment: 49146187
    +parent: 49146167
    +author: inigyou
    +created_at: 2026-08-02T16:57:49.000Z
    +body:
    +There have been cases where a small statistical bias like avoiding duplicate bytes can be amplified to a full break. I don't think this problem is applicable to a bitcoin private key though.
    +
    +comment: 49146562
    +parent: 49146187
    +author: nullc
    +created_at: 2026-08-02T17:40:41.000Z
    +body:
    +Right, that's particularly a concern for DSA nonces.  Coldcard uses RFC 6979 however, and duplicates at the 32-bit word level is a different matter than at the byte level.
    +
    +comment: 49146689
    +parent: 49142373
    +author: nullc
    +created_at: 2026-08-02T17:54:43.000Z
    +body:
    +Hopefully people are being a little circumspect right now with explotation instructions because there are still vulnerable coins out there (particularly from mk4+ wallets, as well ones with passwords that are not strong enough) which have not been taken or rescued yet and may still be rescued by owners.
    +
    +comment: 49147215
    +parent: 49141886
    +author: killerstorm
    +created_at: 2026-08-02T18:52:59.000Z
    +body:
    +Well, if the developer read libngu's random.c he would have noticed that it uses rng_get() and that it's asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered "wait, what?").
    +So I'd say the root cause that the developer didn't check what random.bytes() does under the hood or how libngu expects random to be generated.
    
    Extracted text as captured
    comment: 49141886
    author: nullc
    created_at: 2026-08-02T07:08:53.000Z
    parent: 49140405
    body:
    This writeup isn't very good and misses/misunderstands the programming error that leads to the flaw.
    I'm commenting because I think it's important to understand the issue.
    The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.
    The fundamental cause is a mixup between a value test and a definedness test.
    Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault/error conditions.
    The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef. So "#define MICROPY_HW_ENABLE_RNG (0)" deactivated the micropython implementation but failed to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn't fire because MICROPY_HW_ENABLE_RNG was _defined_).
    This was easier to miss because the usages weren't only in different files-- they were in different repositories.
    There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input). But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...
    In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)
    RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output. I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG. It's something of a "color of your bits" issue ( https://ansuz.sooke.bc.ca/entry/23 ).
    The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.
    The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play. I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).
    Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).
    
    comment: 49142014
    parent: 49141886
    author: nullc
    created_at: 2026-08-02T07:34:54.000Z
    body:
    Aside, I'm somewhat surprised that MISRA (as of 2012 at least) doesn't have a directive related to definedness vs value errors.
    
    comment: 49142373
    parent: 49141886
    author: smithcoin
    created_at: 2026-08-02T08:37:43.000Z
    body:
    Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?
    
    comment: 49142559
    parent: 49142014
    author: RustyRussell
    created_at: 2026-08-02T09:04:47.000Z
    body:
    Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.
    

    Excerpt only. The complete copy is held offline and backs quotations on this site. The original publication remains the canonical public source.

  2. capture correction difference between and capture correction +233 -85

    This collector moved from the rendered Hacker News page to the item API after HN began answering it with a persistent 429, so the whole capture is reshaped: pretty-printed JSON with absolute timestamps in place of page chrome and relative times. Maxwell's comment text is unchanged word for word across the switch; the API view carries it with HTML entities and paragraph tags as HN stores it, and adds the seven replies as structured nodes.

    seen · Captured here 16,045 chars
    What changed from the previous capture 318 lines
    -Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
    -nullc 2 days ago | parent | context | favorite | on: When random.bytes() runs but doesn't work
    -This writeup isn't very good and misses/misunderstands the programming error that leads to the flaw.
    -I'm commenting because I think it's important to understand the issue.
    -The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.
    -The fundamental cause is a mixup between a value test and a definedness test.
    -Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault/error conditions.
    -The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef. So "#define MICROPY_HW_ENABLE_RNG (0)" deactivated the micropython implementation but failed to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn't fire because MICROPY_HW_ENABLE_RNG was _defined_).
    -This was easier to miss because the usages weren't only in different files-- they were in different repositories.
    -There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input). But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...
    -In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)
    -RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output. I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG. It's something of a "color of your bits" issue ( https://ansuz.sooke.bc.ca/entry/23 ).
    -The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.
    -The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play. I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).
    -Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).
    -help
    -egwor 2 days ago | next [–]
    -Although I might disagree with the style of the write up, I think it is right that there are weak engineering practices applied:
    -- not enough information provided in commit message.
    -- this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware)
    -- limited (automated) testing
    -- potential confusion by the author over C concepts
    -- additional complexity which could have been hidden behind a cleaner interface rather than using defines
    -I agree about your points about:
    -- defines over multiple repo's made this more complicated that was necessary.
    -- fault injections would have been the appropriate way to test this (or maybe mocks)
    -- testing of randomness needs careful design, e.g. RNG seeding can be a good way
    -Taking a step back, we as a community of excellence need to emphasise that this isn't a criticism about the person/author. There needs to be a clear analysis of what went wrong whilst being kind to the person/people involved.
    -reply
    -inigyou 2 days ago | prev | next [–]
    -At no point did I ever think that setting "enable HW RNG" to 0 "provided a more aggressive handling of fault/error conditions". I always assumed it disabled the HW RNG.
    -reply
    -nullc 2 days ago | parent | next [–]
    -https://github.com/switck/libngu/blob/537519a829259622ea6b03...
    -vs
    -https://github.com/Coldcard/micropython/blob/4107246f8a08080...
    -reply
    -dale_glass 1 day ago | root | parent | next [–]
    -The my_random_bytes implementation doesn't look good at all at first glance. Let's see:
    -1. I believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
    -2. "here" is a terrible name for a length
    -3. It does a memcpy of a minimum of 4 bytes to the destination, even if count is lower. It'll also overflow longer buffers with a length not divisible by 4.
    -reply
    -nullc 1 day ago | root | parent | next [–]
    -I have absolutely no interest in defending this code-- and already considered it untrusthworthy before any of this. But in the interest of accuracy:
    -> It does a memcpy of a minimum of 4 bytes
    -This is a common misreading of MIN(). MIN(4,x) is a number that is a MAXIMUM of 4, not a minimum.
    -Count is the number of bytes remaining in the buffer. The input to the copy is a 4-byte word. min(4,count) will produce a number 0-4 which is always equal to or less than count. The copy will not overflow the buffer or overrun the input: If count is 3, for example, then here will be 3 and it will copy 3 bytes.
    -> believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
    -It's been a while since I looked but I believe the STM32 manual advises you to throw away data when this happens, because the rng is updated async with the processor and reads that are too fast will produce 0s or duplicate values. Entropy loss from doing so is generally negligible. Were it me I'd read enough into a cryptographic hash to render the output cryptographically close to uniform and not have to worry about it further. (particularly since some uses of cryptographic numbers are extremely sensitive to even small biases)
    -I do wonder how fatal MP_EFAULT actually is...-- on a device like this being jumpy at failing the RNG is reasonable, but if it bricks the device (for example) that would be too much for a condition that (IIRC) the datasheet says can happen. If that error is worse than causing a reboot then it might be the case that their emergency fix deployment might have the effect of causing problems by deploying never-actually-tested code into the wild. When I initially looked at this code before finding the flaw I was somewhat surprised that this test didn't produce spurious failures.
    -[Maybe someone who isn't traveling and on vacation might want to trace out that error condition, -- and check my vague recollection of the STM32 datasheets, as I could be remembering some other part]
    -reply
    -inigyou 1 day ago | root | parent | next [–]
    -There have been cases where a small statistical bias like avoiding duplicate bytes can be amplified to a full break. I don't think this problem is applicable to a bitcoin private key though.
    -reply
    -nullc 1 day ago | root | parent | next [–]
    -Right, that's particularly a concern for DSA nonces. Coldcard uses RFC 6979 however, and duplicates at the 32-bit word level is a different matter than at the byte level.
    -reply
    -killerstorm 1 day ago | prev | next [–]
    -Well, if the developer read libngu's random.c he would have noticed that it uses rng_get() and that it's asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered "wait, what?").
    -So I'd say the root cause that the developer didn't check what random.bytes() does under the hood or how libngu expects random to be generated.
    -reply
    -nullc 2 days ago | prev | next [–]
    -Aside, I'm somewhat surprised that MISRA (as of 2012 at least) doesn't have a directive related to definedness vs value errors.
    -reply
    -RustyRussell 2 days ago | parent | next [–]
    -Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.
    -reply
    -dist-epoch 2 days ago | prev | next [–]
    -I wonder if some sort of "taint" analysis could be used - you generate a byte of randomness and trace it back through code until you see all the sources of entropy which were used as input.
    -Hard problem in general, but with LLMs surely this is possible now, either with them inspecting or by them providing some sort of "formal" proof - this function calls this function which reads this buffer which gets input from here...
    -reply
    -smithcoin 2 days ago | prev | next [–]
    -Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?
    -reply
    -nullc 1 day ago | parent | next [–]
    -Hopefully people are being a little circumspect right now with explotation instructions because there are still vulnerable coins out there (particularly from mk4+ wallets, as well ones with passwords that are not strong enough) which have not been taken or rescued yet and may still be rescued by owners.
    -reply
    -mlcrypto 1 day ago | prev [–]
    -The article clearly indicates the size of the changes: "The commit message is 5 characters and is simply the word “runs.” The commit changes 1534 lines of code making the ratio 5/1534 = ~0.003"
    -reply
    -Consider applying for YC's Fall 2026 batch! Applications are open till July 27.
    -Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact
    -Search:
    +{
    +  "author": "nullc",
    +  "children": [
    +    {
    +      "author": "nullc",
    +      "children": [
    +        {
    +          "author": "RustyRussell",
    +          "children": [],
    +          "created_at": "2026-08-02T09:04:47.000Z",
    +          "created_at_i": 1785661487,
    +          "id": 49142559,
    +          "options": [],
    +          "parent_id": 49142014,
    +          "points": null,
    +          "story_id": 49140405,
    +          "text": "Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.",
    +          "title": null,
    +          "type": "comment",
    +          "url": null
    +        }
    +      ],
    +      "created_at": "2026-08-02T07:34:54.000Z",
    +      "created_at_i": 1785656094,
    +      "id": 49142014,
    +      "options": [],
    +      "parent_id": 49141886,
    +      "points": null,
    +      "story_id": 49140405,
    +      "text": "Aside, I&#x27;m somewhat surprised that MISRA (as of 2012 at least) doesn&#x27;t have a directive related to definedness vs value errors.",
    +      "title": null,
    +      "type": "comment",
    +      "url": null
    +    },
    +    {
    +      "author": "smithcoin",
    +      "children": [
    +        {
    +          "author": "nullc",
    +          "children": [],
    +          "created_at": "2026-08-02T17:54:43.000Z",
    +          "created_at_i": 1785693283,
    +          "id": 49146689,
    +          "options": [],
    +          "parent_id": 49142373,
    +          "points": null,
    +          "story_id": 49140405,
    +          "text": "Hopefully people are being a little circumspect right now with explotation instructions because there are still vulnerable coins out there (particularly from mk4+ wallets, as well ones with passwords that are not strong enough) which have not been taken or rescued yet and may still be rescued by owners.",
    +          "title": null,
    +          "type": "comment",
    +          "url": null
    +        }
    +      ],
    +      "created_at": "2026-08-02T08:37:43.000Z",
    +      "created_at_i": 1785659863,
    +      "id": 49142373,
    +      "options": [],
    +      "parent_id": 49141886,
    +      "points": null,
    +      "story_id": 49140405,
    +      "text": "Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?",
    +      "title": null,
    +      "type": "comment",
    +      "url": null
    +    },
    +    {
    +      "author": "dist-epoch",
    +      "children": [],
    +      "created_at": "2026-08-02T10:28:58.000Z",
    +      "created_at_i": 1785666538,
    +      "id": 49143073,
    +      "options": [],
    +      "parent_id": 49141886,
    +      "points": null,
    +      "story_id": 49140405,
    +      "text": "I wonder if some sort of &quot;taint&quot; analysis could be used - you generate a byte of randomness and trace it back through code until you see all the sources of entropy which were used as input.<p>Hard problem in general, but with LLMs surely this is possible now, either with them inspecting or by them providing some sort of &quot;formal&quot; proof - this function calls this function which reads this buffer which gets input from here...",
    +      "title": null,
    +      "type": "comment",
    +      "url": null
    +    },
    +    {
    +      "author": "egwor",
    +      "children": [],
    +      "created_at": "2026-08-02T11:30:10.000Z",
    +      "created_at_i": 1785670210,
    +      "id": 49143450,
    +      "options": [],
    +      "parent_id": 49141886,
    +      "points": null,
    +      "story_id": 49140405,
    +      "text": "Although I might disagree with the style of the write up, I think it is right that there are weak engineering practices applied:<p>- not enough information provided in commit message. \n- this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware)\n- limited (automated) testing\n- potential confusion by the author over C concepts\n- additional complexity which could have been hidden behind a cleaner interface rather than using defines<p>I agree about your points about:<p>- defines over multiple repo&#x27;s made this more complicated that was necessary. \n- fault injections would have been the appropriate way to test this (or maybe mocks)\n- testing of randomness needs careful design, e.g. RNG seeding can be a good way<p>Taking a step back, we as a community of excellence need to emphasise that this isn&#x27;t a criticism about the person&#x2F;author. There needs to be a clear analysis of what went wrong whilst being kind to the person&#x2F;people involved.",
    +      "title": null,
    +      "type": "comment",
    +      "url": null
    +    },
    +    {
    +      "author": "inigyou",
    +      "children": [
    +        {
    +          "author": "nullc",
    +          "children": [
    +            {
    +              "author": "dale_glass",
    +              "children": [
    +                {
    +                  "author": "nullc",
    +                  "children": [
    +                    {
    +                      "author": "inigyou",
    +                      "children": [
    +                        {
    +                          "author": "nullc",
    +                          "children": [],
    +                          "created_at": "2026-08-02T17:40:41.000Z",
    +                          "created_at_i": 1785692441,
    +                          "id": 49146562,
    +                          "options": [],
    +                          "parent_id": 49146187,
    +                          "points": null,
    +                          "story_id": 49140405,
    +                          "text": "Right, that&#x27;s particularly a concern for DSA nonces.  Coldcard uses RFC 6979 however, and duplicates at the 32-bit word level is a different matter than at the byte level.",
    +                          "title": null,
    +                          "type": "comment",
    +                          "url": null
    +                        }
    +                      ],
    +                      "created_at": "2026-08-02T16:57:49.000Z",
    +                      "created_at_i": 1785689869,
    +                      "id": 49146187,
    +                      "options": [],
    +                      "parent_id": 49146167,
    +                      "points": null,
    +                      "story_id": 49140405,
    +                      "text": "There have been cases where a small statistical bias like avoiding duplicate bytes can be amplified to a full break. I don&#x27;t think this problem is applicable to a bitcoin private key though.",
    +                      "title": null,
    +                      "type": "comment",
    +                      "url": null
    +                    }
    +                  ],
    +                  "created_at": "2026-08-02T16:55:41.000Z",
    +                  "created_at_i": 1785689741,
    +                  "id": 49146167,
    +                  "options": [],
    +                  "parent_id": 49145802,
    +                  "points": null,
    +                  "story_id": 49140405,
    +                  "text": "I have absolutely no interest in defending this code-- and already considered it untrusthworthy before any of this.  But in the interest of accuracy:<p>&gt; It does a memcpy of a minimum of 4 bytes<p>This is a common misreading of MIN().  MIN(4,x) is a number that is a MAXIMUM of 4, not a minimum.<p>Count is the number of bytes remaining in the buffer.  The input to the copy is a 4-byte word.  min(4,count) will produce a number 0-4 which is always equal to or less than count.  The copy will not overflow the buffer or overrun the input: If count is 3, for example, then here will be 3 and it will copy 3 bytes.<p>&gt; believe it errors out if the HWRNG returns the same value twice. That&#x27;s actually a thing that can legitimately happen. &quot;0&quot; is also a legitimate output.<p>It&#x27;s been a while since I looked but I believe the STM32 manual advises you to throw away data when this happens, because the rng is updated async with the processor and reads that are too fast will produce 0s or duplicate values.  Entropy loss from doing so is generally negligible. Were it me I&#x27;d read enough into a cryptographic hash to render the output cryptographically close to uniform and not have to worry about it further. (particularly since some uses of cryptographic numbers are extremely sensitive to even small biases)<p>I do wonder how fatal MP_EFAULT actually is...-- on a device like this being jumpy at failing the RNG is reasonable, but if it bricks the device (for example) that would be too much for a condition that (IIRC) the datasheet says can happen.  If that error is worse than causing a reboot then it might be the case that their emergency fix deployment might have the effect of causing problems by deploying never-actually-tested code into the wild.  When I initially looked at this code before finding the flaw I was somewhat surprised that this test didn&#x27;t produce spurious failures.<p>[Maybe someone who isn&#x27;t traveling and on vacation might want to trace out that error condition, -- and check my vague recollection of the STM32 datasheets, as I could be remembering some other part]",
    +                  "title": null,
    +                  "type": "comment",
    +                  "url": null
    +                }
    +              ],
    +              "created_at": "2026-08-02T16:08:18.000Z",
    +              "created_at_i": 1785686898,
    +              "id": 49145802,
    +              "options": [],
    +              "parent_id": 49145062,
    +              "points": null,
    +              "story_id": 49140405,
    +              "text": "The my_random_bytes implementation doesn&#x27;t look good at all at first glance. Let&#x27;s see:<p>1. I believe it errors out if the HWRNG returns the same value twice. That&#x27;s actually a thing that can legitimately happen. &quot;0&quot; is also a legitimate output.<p>2. &quot;here&quot; is a terrible name for a length<p>3. It does a memcpy of a minimum of 4 bytes to the destination, even if count is lower. It&#x27;ll also overflow longer buffers with a length not divisible by 4.",
    +              "title": null,
    +              "type": "comment",
    +              "url": null
    +            }
    +          ],
    +          "created_at": "2026-08-02T14:32:44.000Z",
    +          "created_at_i": 1785681164,
    +          "id": 49145062,
    +          "options": [],
    +          "parent_id": 49144079,
    +          "points": null,
    +          "story_id": 49140405,
    +          "text": "<a href=\"https:&#x2F;&#x2F;github.com&#x2F;switck&#x2F;libngu&#x2F;blob&#x2F;537519a829259622ea6b0334fbafd6cae852852f&#x2F;ngu&#x2F;random.c#L77\" rel=\"nofollow\">https:&#x2F;&#x2F;github.com&#x2F;switck&#x2F;libngu&#x2F;blob&#x2F;537519a829259622ea6b03...</a><p>vs<p><a href=\"https:&#x2F;&#x2F;github.com&#x2F;Coldcard&#x2F;micropython&#x2F;blob&#x2F;4107246f8a080807b62c3b4838e71e812ea68b6f&#x2F;ports&#x2F;stm32&#x2F;rng.c#L55\" rel=\"nofollow\">https:&#x2F;&#x2F;github.com&#x2F;Coldcard&#x2F;micropython&#x2F;blob&#x2F;4107246f8a08080...</a>",
    +          "title": null,
    +          "type": "comment",
    +          "url": null
    +        }
    +      ],
    +      "created_at": "2026-08-02T12:46:41.000Z",
    +      "created_at_i": 1785674801,
    +      "id": 49144079,
    +      "options": [],
    +      "parent_id": 49141886,
    +      "points": null,
    +      "story_id": 49140405,
    +      "text": "At no point did I ever think that setting &quot;enable HW RNG&quot; to 0 &quot;provided a more aggressive handling of fault&#x2F;error conditions&quot;. I always assumed it disabled the HW RNG.",
    +      "title": null,
    +      "type": "comment",
    +      "url": null
    +    },
    +    {
    +      "author": "mlcrypto",
    +      "children": [],
    +      "created_at": "2026-08-02T16:16:51.000Z",
    +      "created_at_i": 1785687411,
    +      "id": 49145865,
    +      "options": [],
    +      "parent_id": 49141886,
    +      "points": null,
    +      "story_id": 49140405,
    +      "text": "The article clearly indicates the size of the changes: &quot;The commit message is 5 characters and is simply the word “runs.” The commit changes 1534 lines of code making the ratio 5&#x2F;1534 = ~0.003&quot;",
    +      "title": null,
    +      "type": "comment",
    +      "url": null
    +    },
    +    {
    +      "author": "killerstorm",
    +      "children": [],
    +      "created_at": "2026-08-02T18:52:59.000Z",
    +      "created_at_i": 1785696779,
    +      "id": 49147215,
    +      "options": [],
    +      "parent_id": 49141886,
    +      "points": null,
    +      "story_id": 49140405,
    +      "text": "Well, if the developer read libngu&#x27;s random.c he would have noticed that it uses  rng_get() and that it&#x27;s asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered &quot;wait, what?&quot;).<p>So I&#x27;d say the root cause that the developer didn&#x27;t check what random.bytes() does under the hood or how libngu expects random to be generated.",
    +      "title": null,
    +      "type": "comment",
    +      "url": null
    +    }
    +  ],
    +  "created_at": "2026-08-02T07:08:53.000Z",
    +  "created_at_i": 1785654533,
    +  "id": 49141886,
    +  "options": [],
    +  "parent_id": 49140405,
    +  "points": null,
    +  "story_id": 49140405,
    +  "text": "This writeup isn&#x27;t very good and misses&#x2F;misunderstands the programming error that leads to the flaw.<p>I&#x27;m commenting because I think it&#x27;s important to understand the issue.<p>The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.<p>The fundamental cause is a mixup between a value test and a definedness test.<p>Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault&#x2F;error conditions.<p>The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef.   So &quot;#define MICROPY_HW_ENABLE_RNG (0)&quot; deactivated the micropython implementation but <i>failed</i> to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn&#x27;t fire because MICROPY_HW_ENABLE_RNG was _defined_).<p>This was easier to miss because the usages weren&#x27;t only in different files-- they were in different repositories.<p>There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input).   But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...<p>In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)<p>RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output.  I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG.  It&#x27;s something of a &quot;color of your bits&quot; issue ( <a href=\"https:&#x2F;&#x2F;ansuz.sooke.bc.ca&#x2F;entry&#x2F;23\" rel=\"nofollow\">https:&#x2F;&#x2F;ansuz.sooke.bc.ca&#x2F;entry&#x2F;23</a> ).<p>The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.<p>The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play.  I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).<p>Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).",
    +  "title": null,
    +  "type": "comment",
    +  "url": null
    +}
    
    Extracted text as captured
    {
      "author": "nullc",
      "children": [
        {
          "author": "nullc",
          "children": [
            {
              "author": "RustyRussell",
              "children": [],
              "created_at": "2026-08-02T09:04:47.000Z",
              "created_at_i": 1785661487,
              "id": 49142559,
              "options": [],
              "parent_id": 49142014,
              "points": null,
              "story_id": 49140405,
              "text": "Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.",
              "title": null,
              "type": "comment",
              "url": null
            }
          ],
          "created_at": "2026-08-02T07:34:54.000Z",
          "created_at_i": 1785656094,
          "id": 49142014,
          "options": [],
          "parent_id": 49141886,
          "points": null,
          "story_id": 49140405,
          "text": "Aside, I&#x27;m somewhat surprised that MISRA (as of 2012 at least) doesn&#x27;t have a directive related to definedness vs value errors.",
          "title": null,
          "type": "comment",
          "url": null
        },
        {
          "author": "smithcoin",
          "children": [
            {
              "author": "nullc",
              "children": [],

    Excerpt only. The complete copy is held offline and backs quotations on this site. The original publication remains the canonical public source.

  3. source content difference between and source content +11 -11

    New Hacker News comments discussed compiler checks, entropy taint analysis and the need to avoid exploitation details while coins remain vulnerable.

    seen · Captured here 9,865 chars
    What changed from the previous capture 22 lines
     Well, if the developer read libngu's random.c he would have noticed that it uses rng_get() and that it's asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered "wait, what?").
     So I'd say the root cause that the developer didn't check what random.bytes() does under the hood or how libngu expects random to be generated.
     reply
    +nullc <relative-time> | prev | next [–]
    +Aside, I'm somewhat surprised that MISRA (as of 2012 at least) doesn't have a directive related to definedness vs value errors.
    +reply
    +RustyRussell <relative-time> | parent | next [–]
    +Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.
    +reply
    +dist-epoch <relative-time> | prev | next [–]
    +I wonder if some sort of "taint" analysis could be used - you generate a byte of randomness and trace it back through code until you see all the sources of entropy which were used as input.
    +Hard problem in general, but with LLMs surely this is possible now, either with them inspecting or by them providing some sort of "formal" proof - this function calls this function which reads this buffer which gets input from here...
    +reply
     smithcoin <relative-time> | prev | next [–]
     Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?
     reply
     nullc <relative-time> | parent | next [–]
     Hopefully people are being a little circumspect right now with explotation instructions because there are still vulnerable coins out there (particularly from mk4+ wallets, as well ones with passwords that are not strong enough) which have not been taken or rescued yet and may still be rescued by owners.
     reply
    -mlcrypto <relative-time> | prev | next [–]
    +mlcrypto <relative-time> | prev [–]
     The article clearly indicates the size of the changes: "The commit message is 5 characters and is simply the word “runs.” The commit changes 1534 lines of code making the ratio 5/1534 = ~0.003"
    -reply
    -nullc <relative-time> | prev | next [–]
    -Aside, I'm somewhat surprised that MISRA (as of 2012 at least) doesn't have a directive related to definedness vs value errors.
    -reply
    -RustyRussell <relative-time> | parent | next [–]
    -Always use -Wundef. For CCAN all config vars use #if and we lint for ifdefs of them, because you have to pick a side here, and sometimes using HAVE_X in C code is useful, so I prefer always-defined.
    -reply
    -dist-epoch <relative-time> | prev [–]
    -I wonder if some sort of "taint" analysis could be used - you generate a byte of randomness and trace it back through code until you see all the sources of entropy which were used as input.
    -Hard problem in general, but with LLMs surely this is possible now, either with them inspecting or by them providing some sort of "formal" proof - this function calls this function which reads this buffer which gets input from here...
     reply
     Consider applying for YC's Fall 2026 batch! Applications are open till July 27.
     Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact
    
    Extracted text as captured
    Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
    nullc 2 days ago | parent | context | favorite | on: When random.bytes() runs but doesn't work
    This writeup isn't very good and misses/misunderstands the programming error that leads to the flaw.
    I'm commenting because I think it's important to understand the issue.
    The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.
    The fundamental cause is a mixup between a value test and a definedness test.
    Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault/error conditions.
    The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef. So "#define MICROPY_HW_ENABLE_RNG (0)" deactivated the micropython implementation but failed to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn't fire because MICROPY_HW_ENABLE_RNG was _defined_).
    This was easier to miss because the usages weren't only in different files-- they were in different repositories.
    There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input). But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...
    In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)
    RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output. I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG. It's something of a "color of your bits" issue ( https://ansuz.sooke.bc.ca/entry/23 ).
    The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.
    The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play. I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).
    Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).
    help
    egwor 2 days ago | next [–]
    Although I might disagree with the style of the write up, I think it is right that there are weak engineering practices applied:
    - not enough information provided in commit message.
    - this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware)
    - limited (automated) testing
    - potential confusion by the author over C concepts
    - additional complexity which could have been hidden behind a cleaner interface rather than using defines
    I agree about your points about:
    - defines over multiple repo's made this more complicated that was necessary.
    - fault injections would have been the appropriate way to test this (or maybe mocks)
    - testing of randomness needs careful design, e.g. RNG seeding can be a good way
    Taking a step back, we as a community of excellence need to emphasise that this isn't a criticism about the person/author. There needs to be a clear analysis of what went wrong whilst being kind to the person/people involved.
    reply
    inigyou 2 days ago | prev | next [–]
    At no point did I ever think that setting "enable HW RNG" to 0 "provided a more aggressive handling of fault/error conditions". I always assumed it disabled the HW RNG.
    reply
    nullc 2 days ago | parent | next [–]
    https://github.com/switck/libngu/blob/537519a829259622ea6b03...
    vs
    https://github.com/Coldcard/micropython/blob/4107246f8a08080...
    reply
    dale_glass 1 day ago | root | parent | next [–]
    The my_random_bytes implementation doesn't look good at all at first glance. Let's see:
    1. I believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.

    Excerpt only. The complete copy is held offline and backs quotations on this site. The original publication remains the canonical public source.

  4. source content difference between and source content +32 -0

    The thread gained several new comments: killerstorm on the libngu random.c failsafe, dale_glass criticising the my_random_bytes implementation, and nullc correcting the MIN() misreading, noting the STM32 guidance on duplicate RNG reads, and urging restraint on publishing exploitation details while coins remain recoverable.

    seen · Captured here 9,890 chars
    What changed from the previous capture 32 lines
     - testing of randomness needs careful design, e.g. RNG seeding can be a good way
     Taking a step back, we as a community of excellence need to emphasise that this isn't a criticism about the person/author. There needs to be a clear analysis of what went wrong whilst being kind to the person/people involved.
     reply
    +killerstorm <relative-time> | prev | next [–]
    +Well, if the developer read libngu's random.c he would have noticed that it uses rng_get() and that it's asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered "wait, what?").
    +So I'd say the root cause that the developer didn't check what random.bytes() does under the hood or how libngu expects random to be generated.
    +reply
     inigyou <relative-time> | prev | next [–]
     At no point did I ever think that setting "enable HW RNG" to 0 "provided a more aggressive handling of fault/error conditions". I always assumed it disabled the HW RNG.
     reply
     vs
     https://github.com/Coldcard/micropython/blob/4107246f8a08080...
     reply
    +dale_glass <relative-time> | root | parent | next [–]
    +The my_random_bytes implementation doesn't look good at all at first glance. Let's see:
    +1. I believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
    +2. "here" is a terrible name for a length
    +3. It does a memcpy of a minimum of 4 bytes to the destination, even if count is lower. It'll also overflow longer buffers with a length not divisible by 4.
    +reply
    +nullc <relative-time> | root | parent | next [–]
    +I have absolutely no interest in defending this code-- and already considered it untrusthworthy before any of this. But in the interest of accuracy:
    +> It does a memcpy of a minimum of 4 bytes
    +This is a common misreading of MIN(). MIN(4,x) is a number that is a MAXIMUM of 4, not a minimum.
    +Count is the number of bytes remaining in the buffer. The input to the copy is a 4-byte word. min(4,count) will produce a number 0-4 which is always equal to or less than count. The copy will not overflow the buffer or overrun the input: If count is 3, for example, then here will be 3 and it will copy 3 bytes.
    +> believe it errors out if the HWRNG returns the same value twice. That's actually a thing that can legitimately happen. "0" is also a legitimate output.
    +It's been a while since I looked but I believe the STM32 manual advises you to throw away data when this happens, because the rng is updated async with the processor and reads that are too fast will produce 0s or duplicate values. Entropy loss from doing so is generally negligible. Were it me I'd read enough into a cryptographic hash to render the output cryptographically close to uniform and not have to worry about it further. (particularly since some uses of cryptographic numbers are extremely sensitive to even small biases)
    +I do wonder how fatal MP_EFAULT actually is...-- on a device like this being jumpy at failing the RNG is reasonable, but if it bricks the device (for example) that would be too much for a condition that (IIRC) the datasheet says can happen. If that error is worse than causing a reboot then it might be the case that their emergency fix deployment might have the effect of causing problems by deploying never-actually-tested code into the wild. When I initially looked at this code before finding the flaw I was somewhat surprised that this test didn't produce spurious failures.
    +[Maybe someone who isn't traveling and on vacation might want to trace out that error condition, -- and check my vague recollection of the STM32 datasheets, as I could be remembering some other part]
    +reply
    +inigyou <relative-time> | root | parent | next [–]
    +There have been cases where a small statistical bias like avoiding duplicate bytes can be amplified to a full break. I don't think this problem is applicable to a bitcoin private key though.
    +reply
    +nullc <relative-time> | root | parent | next [–]
    +Right, that's particularly a concern for DSA nonces. Coldcard uses RFC 6979 however, and duplicates at the 32-bit word level is a different matter than at the byte level.
    +reply
    +mlcrypto <relative-time> | prev | next [–]
    +The article clearly indicates the size of the changes: "The commit message is 5 characters and is simply the word “runs.” The commit changes 1534 lines of code making the ratio 5/1534 = ~0.003"
    +reply
     smithcoin <relative-time> | prev | next [–]
     Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?
    +reply
    +nullc <relative-time> | parent | next [–]
    +Hopefully people are being a little circumspect right now with explotation instructions because there are still vulnerable coins out there (particularly from mk4+ wallets, as well ones with passwords that are not strong enough) which have not been taken or rescued yet and may still be rescued by owners.
     reply
     nullc <relative-time> | prev | next [–]
     Aside, I'm somewhat surprised that MISRA (as of 2012 at least) doesn't have a directive related to definedness vs value errors.
    
    Extracted text as captured
    Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
    nullc 12 hours ago | parent | context | favorite | on: When random.bytes() runs but doesn't work
    This writeup isn't very good and misses/misunderstands the programming error that leads to the flaw.
    I'm commenting because I think it's important to understand the issue.
    The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.
    The fundamental cause is a mixup between a value test and a definedness test.
    Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault/error conditions.
    The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef. So "#define MICROPY_HW_ENABLE_RNG (0)" deactivated the micropython implementation but failed to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn't fire because MICROPY_HW_ENABLE_RNG was _defined_).
    This was easier to miss because the usages weren't only in different files-- they were in different repositories.
    There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input). But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...
    In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)
    RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output. I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG. It's something of a "color of your bits" issue ( https://ansuz.sooke.bc.ca/entry/23 ).
    The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.
    The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play. I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).
    Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).
    help
    egwor 8 hours ago | next [–]
    Although I might disagree with the style of the write up, I think it is right that there are weak engineering practices applied:
    - not enough information provided in commit message.
    - this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware)
    - limited (automated) testing
    - potential confusion by the author over C concepts
    - additional complexity which could have been hidden behind a cleaner interface rather than using defines
    I agree about your points about:
    - defines over multiple repo's made this more complicated that was necessary.
    - fault injections would have been the appropriate way to test this (or maybe mocks)
    - testing of randomness needs careful design, e.g. RNG seeding can be a good way
    Taking a step back, we as a community of excellence need to emphasise that this isn't a criticism about the person/author. There needs to be a clear analysis of what went wrong whilst being kind to the person/people involved.
    reply
    killerstorm 1 hour ago | prev | next [–]
    Well, if the developer read libngu's random.c he would have noticed that it uses rng_get() and that it's asking for MICROPY_HW_ENABLE_RNG (albeit the failsafe is defective, reading it should have at least triggered "wait, what?").
    So I'd say the root cause that the developer didn't check what random.bytes() does under the hood or how libngu expects random to be generated.
    reply
    inigyou 7 hours ago | prev | next [–]
    At no point did I ever think that setting "enable HW RNG" to 0 "provided a more aggressive handling of fault/error conditions". I always assumed it disabled the HW RNG.
    reply
    nullc 5 hours ago | parent | next [–]
    https://github.com/switck/libngu/blob/537519a829259622ea6b03...
    vs
    https://github.com/Coldcard/micropython/blob/4107246f8a08080...

    Excerpt only. The complete copy is held offline and backs quotations on this site. The original publication remains the canonical public source.

  5. Earliest copy held
    seen · Captured here 5,791 chars
    Extracted text as captured
    Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
    nullc 7 hours ago | parent | context | favorite | on: When random.bytes() runs but doesn't work
    This writeup isn't very good and misses/misunderstands the programming error that leads to the flaw.
    I'm commenting because I think it's important to understand the issue.
    The article would have you think that the change in question was a tiny change to a flag to make it compile, but in reality the commit in question is a 1533 line addition of the entire RNG infrastructure.
    The fundamental cause is a mixup between a value test and a definedness test.
    Coldcard attempted to replace the micropython wrapper on the hardware TRNG, apparently in order to provide a more aggressive handling of fault/error conditions.
    The micropython hwrng code is gated by an #if check, the replacement HWRNG code is gated by an ifndef. So "#define MICROPY_HW_ENABLE_RNG (0)" deactivated the micropython implementation but failed to activate the internal one (which was #ifndef MICROPY_HW_ENABLE_RNG ... which didn't fire because MICROPY_HW_ENABLE_RNG was _defined_).
    This was easier to miss because the usages weren't only in different files-- they were in different repositories.
    There is a more abstract point to make that in cryptographic software the absence of a secure randomness source (the STM32 TRNG) should never fall back to an insecure source (a trivial PRNG which might have only had on the order of 20-bits of uncertainty in its input). But the code that had the fallback was micropython which was not authored by the coldcard creators and is presumably not intended for cryptographic applications...
    In later code (for MK4+ devices) the issue was further masked without being corrected by xor-ing in another insecure PRNG seeded by 32-bits from another TRNG. ... itself acting like an additional insecure fallback. (Why it first hashes 64-bits of TRNG output then throws away half the entropy is a mystery...)
    RNG failures can be difficult to detect because the real randomness and a PRNG are indistinguishable by any simple tests of the output. I understand the coldcard developers ran extensive tests on the randomness generated by these devices-- they may well have been just testing the PRNG. It's something of a "color of your bits" issue ( https://ansuz.sooke.bc.ca/entry/23 ).
    The same sort of issue happens at multiple levels of the stack, e.g. IIRC the STM32 TRNG itself does some kind of whitening that could have the same effect of concealing an RNG failure.
    The existence of insecure fake randomness in the code at all was a red flag that had been noted previously -- though the really bad one was hidden away in the micropython code and not even obviously at play. I think this is a rare case of a bug that would be more easily found from binary analysis than review of the source code (e.g. no access to the STM32 TRNG at all).
    Without these fallbacks the failure to use the TRNG would have been immediately detected by the developers (e.g. when every attempt generated the same seed), and a review could be validated by fault injection (NOP out the hwrng and verify that the test fails).
    help
    egwor 3 hours ago | next [–]
    Although I might disagree with the style of the write up, I think it is right that there are weak engineering practices applied:
    - not enough information provided in commit message.
    - this change ought to have been split into smaller chunks (e.g. introduce indirection whilst code continues to use hardware)
    - limited (automated) testing
    - potential confusion by the author over C concepts
    - additional complexity which could have been hidden behind a cleaner interface rather than using defines
    I agree about your points about:
    - defines over multiple repo's made this more complicated that was necessary.
    - fault injections would have been the appropriate way to test this (or maybe mocks)
    - testing of randomness needs careful design, e.g. RNG seeding can be a good way
    Taking a step back, we as a community of excellence need to emphasise that this isn't a criticism about the person/author. There needs to be a clear analysis of what went wrong whilst being kind to the person/people involved.
    reply
    inigyou 1 hour ago | prev | next [–]
    At no point did I ever think that setting "enable HW RNG" to 0 "provided a more aggressive handling of fault/error conditions". I always assumed it disabled the HW RNG.
    reply
    nullc 6 minutes ago | parent | next [–]
    https://github.com/switck/libngu/blob/537519a829259622ea6b03...
    vs
    https://github.com/Coldcard/micropython/blob/4107246f8a08080...
    reply
    smithcoin 6 hours ago | prev | next [–]
    Have you found a write up you’d recommend on how the threat actors actually exploited the vulnerabilities?
    reply

    Excerpt only. The complete copy is held offline and backs quotations on this site. The original publication remains the canonical public source.

1 presentation-noise difference. Sidebar, ticker and other page chrome churn that our review classified as not being a change to what the source says.
  • +6 -6 Only comment ordering changed: the existing killerstorm and mlcrypto comments moved to different positions in the thread. No comment text was added, removed or altered.
How to check this yourself

The excerpts and plain unified diffs above show the text this project held and how it changed. To verify a quotation, compare it against the page itself or against the Internet Archive's copies, which are independent of this project.

Complete captures are held offline rather than mirrored here, so this page shows diffs and excerpts. If a quotation is ever disputed, the full copy can be produced. Ask.