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.

libngu PR #64 patch

libngu-pr-64-patch

https://github.com/switck/libngu/pull/64.patch

Latest reviewed change

source content difference between and

The published patch series grew from three to four commits, adding a compile-gate commit that selects the STM32 backend when the stm module is disabled.

seen +43 -3 full history below
 From 5c1e8546e4e02c854585e4559aedb9ed4eecd315 Mon Sep 17 00:00:00 2001
 From: "Javier G. Montoya S" <[email protected]>
 Date: Thu, 6 Aug 2026 22:56:59 -0400
-Subject: [PATCH 1/3] random: harden entropy backend boundaries
+Subject: [PATCH 1/4] random: harden entropy backend boundaries
 ---
  ngu/random.c           | 93 +++++++++++-------------------------------
  ngu/random.h           |  5 ++-

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

Organisation
switck
Evidence role
Repository patch
Published
not established
Source changes
2
Detected differences
2
Unreviewed
0
Copies held
3

Patch content for #64. Kept separately from the conversation and review-state capture.

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. source content difference between and Current source content +43 -3

    The published patch series grew from three to four commits, adding a compile-gate commit that selects the STM32 backend when the stm module is disabled.

    seen · Captured here 17,837 chars
    What changed from the previous capture 46 lines
     From 5c1e8546e4e02c854585e4559aedb9ed4eecd315 Mon Sep 17 00:00:00 2001
     From: "Javier G. Montoya S" <[email protected]>
     Date: Thu, 6 Aug 2026 22:56:59 -0400
    -Subject: [PATCH 1/3] random: harden entropy backend boundaries
    +Subject: [PATCH 1/4] random: harden entropy backend boundaries
     ---
      ngu/random.c           | 93 +++++++++++-------------------------------
      ngu/random.h           |  5 ++-
     From 0316d2fd20c0ad691e01c8037f116f7e4b0e0f41 Mon Sep 17 00:00:00 2001
     From: "Javier G. Montoya S" <[email protected]>
     Date: Thu, 6 Aug 2026 23:45:05 -0400
    -Subject: [PATCH 2/3] test: gate random backend contracts
    +Subject: [PATCH 2/4] test: gate random backend contracts
     ---
      README.md                                     | 29 ++++++++++++++-
      ngu/ngu_tests/Makefile                        | 36 +++++++++++++++++--
     From ab26f081d94874d2e56b0fd9ce25dc03d0802e87 Mon Sep 17 00:00:00 2001
     From: "Javier G. Montoya S" <[email protected]>
     Date: Fri, 7 Aug 2026 18:07:32 -0400
    -Subject: [PATCH 3/3] test: gate external STM32 rng_get provider
    +Subject: [PATCH 3/4] test: gate external STM32 rng_get provider
     ---
      README.md              |  9 +++++----
      ngu/ngu_tests/Makefile | 11 ++++++++---
      		! $(GATE_ESP) 2>"$$log"; \
      		grep -q 'NGU_ESP32_RNG_IS_TRUE_RANDOM=1 required' "$$log"; \
      		! $(GATE_ESP) -DNGU_ESP32_RNG_IS_TRUE_RANDOM=0 2>"$$log"; \
    +From 376441e3f49f6b3d41555f4bb5f2f4fe441a23aa Mon Sep 17 00:00:00 2001
    +From: "Javier G. Montoya S" <[email protected]>
    +Date: Sun, 9 Aug 2026 20:42:00 -0400
    +Subject: [PATCH 4/4] random: select STM32 backend when stm module is disabled
    +---
    + ngu/ngu_tests/Makefile | 5 +++++
    + ngu/random_backend.h   | 2 +-
    + 2 files changed, 6 insertions(+), 1 deletion(-)
    +diff --git a/ngu/ngu_tests/Makefile b/ngu/ngu_tests/Makefile
    +index 4af1a33..1050f0a 100644
    +--- a/ngu/ngu_tests/Makefile
    +@@ -48,6 +48,7 @@ GATE_CC = $(CC) -fsyntax-only -std=gnu99 -Wall -Wextra -Werror -I..
    + GATE_SOURCE = gate-stubs/random_backend_gate.c
    + GATE_STM_BASE = $(GATE_CC) -DMICROPY_PY_STM=1 $(GATE_SOURCE)
    + GATE_STM = $(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=1
    ++GATE_STM_MODULE_DISABLED = $(GATE_CC) -DMICROPY_PY_STM=0 $(GATE_SOURCE)
    + GATE_ESP = $(GATE_CC) -Igate-stubs -DESP_PLATFORM=1 $(GATE_SOURCE)
    + gates:
    +@@ -65,6 +66,10 @@ gates:
    + 		grep -q 'MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required' "$$log"; \
    + 		$(GATE_STM); \
    + 		$(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=0 -DNGU_STM32_EXTERNAL_RNG_GET=1; \
    ++		! $(GATE_STM_MODULE_DISABLED) 2>"$$log"; \
    ++		grep -q 'MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required' "$$log"; \
    ++		$(GATE_STM_MODULE_DISABLED) -DMICROPY_HW_ENABLE_RNG=1; \
    ++		$(GATE_STM_MODULE_DISABLED) -DNGU_STM32_EXTERNAL_RNG_GET=1; \
    + 		! $(GATE_ESP) 2>"$$log"; \
    + 		grep -q 'NGU_ESP32_RNG_IS_TRUE_RANDOM=1 required' "$$log"; \
    + 		! $(GATE_ESP) -DNGU_ESP32_RNG_IS_TRUE_RANDOM=0 2>"$$log"; \
    +diff --git a/ngu/random_backend.h b/ngu/random_backend.h
    +index 8d4a47e..52f91b3 100644
    +--- a/ngu/random_backend.h
    +@@ -23,7 +23,7 @@ static bool chip_trng_read(uint32_t *out)
    +     return true;
    + }
    +-#elif MICROPY_PY_STM
    ++#elif defined(MICROPY_PY_STM)
    + # if MICROPY_HW_ENABLE_RNG != 1 && NGU_STM32_EXTERNAL_RNG_GET != 1
    + #  error "MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required"
    + # endif
    
    Extracted text as captured
    From 5c1e8546e4e02c854585e4559aedb9ed4eecd315 Mon Sep 17 00:00:00 2001
    From: "Javier G. Montoya S" <[email protected]>
    Date: Thu, 6 Aug 2026 22:56:59 -0400
    Subject: [PATCH 1/4] random: harden entropy backend boundaries
    ---
     ngu/random.c           | 93 +++++++++++-------------------------------
     ngu/random.h           |  5 ++-
     ngu/random_backend.h   | 60 +++++++++++++++++++++++++++
     var/mpconfigvariant.mk |  3 +-
     4 files changed, 89 insertions(+), 72 deletions(-)
     create mode 100644 ngu/random_backend.h
    diff --git a/ngu/random.c b/ngu/random.c
    index 49fd469..f074632 100644
    --- a/ngu/random.c
    +++ b/ngu/random.c
    @@ -1,63 +1,15 @@
    -//
    -// random - RNG stuff
     //
    -// - common interface to TRNG specific to your chip
    -// - whitening
    -// - pick new privkeys
    +// random - cryptographic random number generation
    +//
    +// Cifra Hash_DRBG is seeded from the target entropy source. Generated bytes
    +// are also XORed with fresh source words after basic source-failure checks.
     //
     #include "py/runtime.h"
     #include "py/mperrno.h"
    -#include <string.h>
    -#include <stdlib.h>
    -#include <stdio.h>
     #include "my_assert.h"
     #include "cifra/drbg.h"
     #include "cifra/ext/handy.h"
    -
    -// ESP32 code
    -#ifdef ESP_PLATFORM
    -# include "esp_system.h"
    -# define CHIP_TRNG_SETUP()

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

  2. source content difference between and source content +104 -206

    The published patch series was force-pushed, replacing the entropy-health commit with the backend-boundary commit and adding an external STM32 rng_get gate commit.

    seen · Captured here 15,879 chars
    What changed from the previous capture 310 lines
    -From c09eff66214b800f4b632b5aa7a76bb261d2e75a Mon Sep 17 00:00:00 2001
    +From 5c1e8546e4e02c854585e4559aedb9ed4eecd315 Mon Sep 17 00:00:00 2001
     From: "Javier G. Montoya S" <[email protected]>
    -Date: Thu, 6 Aug 2026 17:39:07 -0400
    -Subject: [PATCH 1/3] random: reject two-word entropy cycles
    +Date: Thu, 6 Aug 2026 22:56:59 -0400
    +Subject: [PATCH 1/3] random: harden entropy backend boundaries
     ---
    - ngu/entropy_health.h                | 31 +++++++++++++++++++
    - ngu/ngu_tests/Makefile              | 10 ++++--
    - ngu/ngu_tests/entropy_health_test.c | 47 +++++++++++++++++++++++++++++
    - ngu/random.c                        |  8 ++---
    - 4 files changed, 90 insertions(+), 6 deletions(-)
    - create mode 100644 ngu/entropy_health.h
    - create mode 100644 ngu/ngu_tests/entropy_health_test.c
    -diff --git a/ngu/entropy_health.h b/ngu/entropy_health.h
    -new file mode 100644
    -index 0000000..787ee1f
    -+++ b/ngu/entropy_health.h
    -@@ -0,0 +1,31 @@
    -+//
    -+// entropy_health.h - detect exact repetition in recent entropy words
    -+//
    -+#pragma once
    -+
    -+#include <stdbool.h>
    -+#include <stdint.h>
    -+
    -+typedef struct {
    -+    uint32_t history[2];
    -+    uint8_t count;
    -+} entropy_health_t;
    -+
    -+// Reject a word matching either of the previous two accepted words. This
    -+// catches a stuck source and exact two-word cycles. Rejections do not advance
    -+// history. This is a narrow runtime check, not entropy-source validation.
    -+static inline bool entropy_health_accept(entropy_health_t *health, uint32_t word)
    -+{
    -+    for (uint8_t i = 0; i < health->count; i++) {
    -+        if (word == health->history[i]) {
    -+            return false;
    -+        }
    -+    }
    -+
    -+    health->history[1] = health->history[0];
    -+    health->history[0] = word;
    -+    if (health->count < 2) {
    -+        health->count++;
    -+    }
    -+    return true;
    -+}
    -diff --git a/ngu/ngu_tests/Makefile b/ngu/ngu_tests/Makefile
    -index 6b5e26a..97198b0 100644
    -+++ b/ngu/ngu_tests/Makefile
    -@@ -1,8 +1,8 @@
    - MPY = ../../ngu-micropython
    - PY = python3
    --.PHONY: test tests hash-drbg-cavp
    --test tests: hash-drbg-cavp
    -+.PHONY: test tests hash-drbg-cavp entropy-health
    -+test tests: hash-drbg-cavp entropy-health
    - 	test x$(VIRTUAL_ENV) != x		# need virtualenv
    - 	$(PY) test_aes.py
    - 	$(MPY) test_aes_gen.py
    -@@ -43,6 +43,12 @@ hash-drbg-cavp: $(CIFRA_DRBG_SRCS)
    - 		$(CIFRA_DRBG_CC) -o "$$bin" $(CIFRA_DRBG_SRCS); \
    - 		"$$bin" hashdrbg-sha256 hashdrbg-sha256-addnl
    -+entropy-health: entropy_health_test.c ../entropy_health.h
    -+	@set -e; bin=$$(mktemp /tmp/libngu-entropy-health.XXXXXX); \
    -+		trap 'rm -f "$$bin"' 0; \
    -+		$(CC) -std=c99 -Wall -Wextra -Werror -I.. -o "$$bin" entropy_health_test.c; \
    -+		"$$bin"
    -+
    - # runs the test compiled in, not here
    - # or, on target
    - #	 import ngu_tests.run
    -diff --git a/ngu/ngu_tests/entropy_health_test.c b/ngu/ngu_tests/entropy_health_test.c
    -new file mode 100644
    -index 0000000..579de14
    -+++ b/ngu/ngu_tests/entropy_health_test.c
    -@@ -0,0 +1,47 @@
    -+//
    -+// entropy_health_test.c - deterministic entropy repetition checks
    -+//
    -+#include <stdbool.h>
    -+#include <stdio.h>
    -+
    -+#include "entropy_health.h"
    -+
    -+static int failures;
    -+
    -+static void check(bool condition, const char *description)
    -+{
    -+    if (!condition) {
    -+        printf("FAIL: %s\n", description);
    -+        failures++;
    -+    }
    -+}
    -+
    -+int main(void)
    -+{
    -+    entropy_health_t health = { 0 };
    -+    entropy_health_t period_three = { 0 };
    -+    entropy_health_t zero = { 0 };
    -+
    -+    // Zero policy belongs to the entropy backend wrapper. This helper only
    -+    // checks repetition, so zero is not confused with uninitialized history.
    -+    check(entropy_health_accept(&zero, 0), "first zero is valid history");
    -+    check(!entropy_health_accept(&zero, 0), "repeated zero rejected");
    -+
    -+    check(entropy_health_accept(&health, 0x111), "first word accepted");
    -+    check(entropy_health_accept(&health, 0x222), "second distinct word accepted");
    -+    check(!entropy_health_accept(&health, 0x222), "adjacent repeat rejected");
    -+    check(!entropy_health_accept(&health, 0x111), "two-word cycle rejected");
    -+    check(entropy_health_accept(&health, 0x333), "fresh word accepted after rejection");
    -+    check(!entropy_health_accept(&health, 0x222), "rejection does not advance history");
    -+
    -+    check(entropy_health_accept(&period_three, 0xaaa), "period-three first word accepted");
    -+    check(entropy_health_accept(&period_three, 0xbbb), "period-three second word accepted");
    -+    check(entropy_health_accept(&period_three, 0xccc), "period-three third word accepted");
    -+    check(entropy_health_accept(&period_three, 0xaaa), "period-three cycle is not detected");
    -+
    -+    if (failures) {
    -+        return 1;
    -+    }
    -+    puts("PASS - entropy health");
    -+    return 0;
    -+}
    + ngu/random.c           | 93 +++++++++++-------------------------------
    + ngu/random.h           |  5 ++-
    + ngu/random_backend.h   | 60 +++++++++++++++++++++++++++
    + var/mpconfigvariant.mk |  3 +-
    + 4 files changed, 89 insertions(+), 72 deletions(-)
    + create mode 100644 ngu/random_backend.h
     diff --git a/ngu/random.c b/ngu/random.c
    -index 16c0d3f..9d8e29b 100644
    +index 49fd469..f074632 100644
     --- a/ngu/random.c
     +++ b/ngu/random.c
    -@@ -13,6 +13,7 @@
    - #include "my_assert.h"
    - #include "cifra/drbg.h"
    - #include "cifra/ext/handy.h"
    -+#include "entropy_health.h"
    - // ESP32 code
    - #ifdef ESP_PLATFORM
    -@@ -61,7 +62,7 @@ static uint32_t linux_trng_32(void)
    - static cf_hash_drbg_sha256 drbg;
    - static bool drbg_ready;
    --static uint32_t last_chip;
    -+static entropy_health_t chip_health;
    - #define DRBG_ENTROPY_WORDS 32
    -@@ -69,11 +70,10 @@ static uint32_t checked_chip_trng(void)
    - {
    -     uint32_t chip = CHIP_TRNG_32();
    --    if(!chip || chip == last_chip) {
    --        // maybe TRNG is not clocked? Fail hard
    -+    if(!chip || !entropy_health_accept(&chip_health, chip)) {
    -+        // The source may be unclocked, stuck, or alternating between two words.
    -         mp_raise_OSError(MP_EFAULT);
    -     }
    --    last_chip = chip;
    -     return chip;
    - }
    -From b67a967b248c1a928f3cca5d0ddbcf77424fe278 Mon Sep 17 00:00:00 2001
    -From: "Javier G. Montoya S" <[email protected]>
    -Date: Thu, 6 Aug 2026 22:56:59 -0400
    -Subject: [PATCH 2/3] random: harden entropy backend boundaries
    - ngu/random.c           | 95 ++++++++++++------------------------------
    - ngu/random.h           |  5 ++-
    - ngu/random_backend.h   | 60 ++++++++++++++++++++++++++
    - var/mpconfigvariant.mk |  3 +-
    - 4 files changed, 91 insertions(+), 72 deletions(-)
    - create mode 100644 ngu/random_backend.h
    -diff --git a/ngu/random.c b/ngu/random.c
    -index 9d8e29b..db06b52 100644
    -+++ b/ngu/random.c
    -@@ -1,64 +1,17 @@
    +@@ -1,63 +1,15 @@
     -//
     -// random - RNG stuff
      //
     +// random - cryptographic random number generation
     +//
     +// Cifra Hash_DRBG is seeded from the target entropy source. Generated bytes
    -+// are also XORed with fresh source words after the source passes narrow
    -+// runtime health checks.
    ++// are also XORed with fresh source words after basic source-failure checks.
      //
      #include "py/runtime.h"
      #include "py/mperrno.h"
      #include "my_assert.h"
      #include "cifra/drbg.h"
      #include "cifra/ext/handy.h"
    - #include "entropy_health.h"
     -
     -// ESP32 code
     -#ifdef ESP_PLATFORM
     -# define CHIP_TRNG_SETUP()
     -# define CHIP_TRNG_32()         rng_get()
     -
    --# if MICROPY_HW_ENABLE_RNG == 0
    +-# if MICROPY_HW_ENABLE_RNG == 0 && NGU_STM32_EXTERNAL_RNG_GET != 1
     -# error "get a HW TRNG plz"
     -# endif
     -#endif
     +#include "random_backend.h"
      static cf_hash_drbg_sha256 drbg;
      static bool drbg_ready;
    -@@ -66,25 +19,35 @@ static entropy_health_t chip_health;
    +@@ -65,26 +17,35 @@ static uint32_t last_chip;
      #define DRBG_ENTROPY_WORDS 32
     -static uint32_t checked_chip_trng(void)
     +static bool checked_chip_trng_read(uint32_t *out)
      {
     -    uint32_t chip = CHIP_TRNG_32();
     +    uint32_t chip;
    --    if(!chip || !entropy_health_accept(&chip_health, chip)) {
    --        // The source may be unclocked, stuck, or alternating between two words.
    +-    if(!chip || chip == last_chip) {
    +-        // maybe TRNG is not clocked? Fail hard
     -        mp_raise_OSError(MP_EFAULT);
    -+    if(!chip_trng_read(&chip) || !chip ||
    -+            !entropy_health_accept(&chip_health, chip)) {
    ++    if(!chip_trng_read(&chip) || !chip || chip == last_chip) {
     +        return false;
          }
    +     last_chip = chip;
     +    *out = chip;
     +    return true;
     +}
     +        }
          }
          if(drbg_ready) {
    -@@ -111,8 +74,6 @@ static void drbg_setup(const void *seed, size_t seed_len)
    +@@ -111,8 +72,6 @@ static void drbg_setup(const void *seed, size_t seed_len)
      void my_random_bytes(uint8_t *dest, uint32_t count)
      {
     -    CHIP_TRNG_SETUP();
          if(!drbg_ready) {
              drbg_setup(NULL, 0);
          }
    -@@ -135,8 +96,6 @@ void my_random_bytes(uint8_t *dest, uint32_t count)
    +@@ -135,8 +94,6 @@ void my_random_bytes(uint8_t *dest, uint32_t count)
      STATIC mp_obj_t random_uint32(void) {
          // full 32-bit values, not 30
     -    CHIP_TRNG_SETUP();
     -
          uint32_t rv;
          my_random_bytes((uint8_t *)&rv, sizeof(rv));
    -@@ -159,12 +118,10 @@ int _rand_below(int mx)
    +@@ -159,12 +116,10 @@ int _rand_below(int mx)
      {
          if(mx <= 1) return 0;
     -    int bl = _bit_length(mx);
     -
     diff --git a/ngu/random_backend.h b/ngu/random_backend.h
     new file mode 100644
    -index 0000000..2a1ba66
    +index 0000000..8d4a47e
     --- /dev/null
     +++ b/ngu/random_backend.h
     @@ -0,0 +1,60 @@
     +}
     +
     +#elif MICROPY_PY_STM
    -+# if MICROPY_HW_ENABLE_RNG == 0
    -+#  error "MICROPY_HW_ENABLE_RNG=1 required; otherwise rng_get() is pseudo-random"
    ++# if MICROPY_HW_ENABLE_RNG != 1 && NGU_STM32_EXTERNAL_RNG_GET != 1
    ++#  error "MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required"
     +# endif
    -+// ports/stm32/rng.c returns zero on a peripheral timeout. Reject every zero
    -+// because its API cannot distinguish a timeout from a valid sample.
    ++// MicroPython's ports/stm32/rng.c returns zero on a peripheral timeout. An
    ++// external provider uses the same interface, so reject zero for both paths.
     +extern uint32_t rng_get(void);
     +static bool chip_trng_read(uint32_t *out)
     +{
     +# error "No hardware or OS entropy source is defined for this target"
     +#endif
     diff --git a/var/mpconfigvariant.mk b/var/mpconfigvariant.mk
    -index e9983bf..fbbd479 100644
    +index e9983bf..6061938 100644
     --- a/var/mpconfigvariant.mk
     +++ b/var/mpconfigvariant.mk
     @@ -10,4 +10,5 @@ PROG = ngu-micropython
      MICROPY_SSL_AXTLS = 0
      MICROPY_SSL_MBEDTLS = 1
     -
    -+# ngu.random holds mutable DRBG and entropy-health state.
    ++# ngu.random holds mutable DRBG and source-check state.
     +MICROPY_PY_THREAD = 0
    -From 23d77bce24ffb559fbe8ac215a55e2757bc44edf Mon Sep 17 00:00:00 2001
    +From 0316d2fd20c0ad691e01c8037f116f7e4b0e0f41 Mon Sep 17 00:00:00 2001
     From: "Javier G. Montoya S" <[email protected]>
     Date: Thu, 6 Aug 2026 23:45:05 -0400
    -Subject: [PATCH 3/3] test: gate random backend contracts
    +Subject: [PATCH 2/3] test: gate random backend contracts
     ---
    - README.md                                     | 29 +++++++++++++++-
    - ngu/ngu_tests/Makefile                        | 34 +++++++++++++++++--
    + README.md                                     | 29 ++++++++++++++-
    + ngu/ngu_tests/Makefile                        | 36 +++++++++++++++++--
      ngu/ngu_tests/gate-stubs/esp_system.h         |  6 ++++
      .../gate-stubs/random_backend_gate.c          |  7 ++++
    - 4 files changed, 72 insertions(+), 4 deletions(-)
    + 4 files changed, 74 insertions(+), 4 deletions(-)
      create mode 100644 ngu/ngu_tests/gate-stubs/esp_system.h
      create mode 100644 ngu/ngu_tests/gate-stubs/random_backend_gate.c
     diff --git a/README.md b/README.md
    -index c4244fb..bdb7c23 100644
    +index c4244fb..d45a673 100644
     --- a/README.md
     +++ b/README.md
     @@ -29,7 +29,9 @@ Name is "Number Go Up" ... because need that.
     +target entropy source. Generated bytes are also XORed with fresh source words.
     +There is no software fallback:
     +
    -+- STM32 uses MicroPython's `rng_get()` only when the board enables
    -+  `MICROPY_HW_ENABLE_RNG`. The pinned MicroPython source otherwise provides a
    ++- STM32 uses MicroPython's `rng_get()` only when the board sets
    ++  `MICROPY_HW_ENABLE_RNG=1`. The pinned MicroPython source otherwise provides a
     +  pseudo-random implementation. A zero word is rejected because `rng_get()`
     +  also returns zero on a peripheral timeout.
     +- ESP32 uses `esp_random()` only when the build sets
     +  `NGU_ESP32_RNG_IS_TRUE_RANDOM=1`. This is the integrator's attestation that a
    -+  true entropy source remains active whenever random data is requested; without active RF
    -+  or another enabled entropy source, ESP-IDF documents the output as
    ++  true entropy source remains active whenever random data is requested. Without
    ++  active RF or another enabled entropy source, ESP-IDF documents the output as
     +  pseudo-random. See the [ESP-IDF RNG documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html).
     +- macOS and FreeBSD use `arc4random()`. Linux uses `getrandom(2)`. Unknown
     +  targets fail to build.
     +
    -+The runtime rejects zero, an immediate repeated word, and an exact two-word
    -+cycle. This is a narrow failure check, not a substitute for validating the raw
    -+entropy source on each hardware target.
    ++The runtime rejects zero and an immediate repeated word. This is a narrow
    ++failure check, not a substitute for validating the raw entropy source on each
    ++hardware target.
     +
     +`ngu.random.reseed()` accepts bytes-like values of at least 32 bytes and mixes
    -+them into the existing state. The module keeps mutable DRBG and health state,
    -+so calls must be serialized. Python calls require the VM's GIL when threads
    -+are enabled; direct C callers own that synchronization.
    ++them into the existing state. The module keeps mutable DRBG and source-check
    ++state, so calls must be serialized. Python calls require the VM's GIL when
    ++threads are enabled; direct C callers own that synchronization.
     diff --git a/ngu/ngu_tests/Makefile b/ngu/ngu_tests/Makefile
    -index 97198b0..1e43f3d 100644
    +index 6b5e26a..305b4dc 100644
     --- a/ngu/ngu_tests/Makefile
     +++ b/ngu/ngu_tests/Makefile
     @@ -1,8 +1,8 @@
      MPY = ../../ngu-micropython
      PY = python3
    --.PHONY: test tests hash-drbg-cavp entropy-health
    --test tests: hash-drbg-cavp entropy-health
    -+.PHONY: test tests hash-drbg-cavp entropy-health gates
    -+test tests: hash-drbg-cavp entropy-health gates
    +-.PHONY: test tests hash-drbg-cavp
    +-test tests: hash-drbg-cavp
    ++.PHONY: test tests hash-drbg-cavp gates
    ++test tests: hash-drbg-cavp gates
      	test x$(VIRTUAL_ENV) != x		# need virtualenv
      	$(PY) test_aes.py
      	$(MPY) test_aes_gen.py
     +	-Wno-unused-but-set-variable -DFULL_FAT_ASSERT \
      	-I$(CIFRA_SRC) -I$(CIFRA_SRC)/ext
      hash-drbg-cavp: $(CIFRA_DRBG_SRCS)
    -@@ -49,6 +50,33 @@ entropy-health: entropy_health_test.c ../entropy_health.h
    - 		$(CC) -std=c99 -Wall -Wextra -Werror -I.. -o "$$bin" entropy_health_test.c; \
    - 		"$$bin"
    +@@ -43,6 +44,35 @@ hash-drbg-cavp: $(CIFRA_DRBG_SRCS)
    + 		$(CIFRA_DRBG_CC) -o "$$bin" $(CIFRA_DRBG_SRCS); \
    + 		"$$bin" hashdrbg-sha256 hashdrbg-sha256-addnl
     +GATE_CC = $(CC) -fsyntax-only -std=gnu99 -Wall -Wextra -Werror -I..
     +GATE_SOURCE = gate-stubs/random_backend_gate.c
     +GATE_STM_BASE = $(GATE_CC) -DMICROPY_PY_STM=1 $(GATE_SOURCE)
     +		! $(GATE_STM_BASE) 2>"$$log"; \
     +		grep -q 'MICROPY_HW_ENABLE_RNG=1 required' "$$log"; \
     +		! $(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=0 2>"$$log"; \
    ++		grep -q 'MICROPY_HW_ENABLE_RNG=1 required' "$$log"; \
    ++		! $(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=2 2>"$$log"; \
     +		grep -q 'MICROPY_HW_ENABLE_RNG=1 required' "$$log"; \
     +		$(GATE_STM); \
     +		! $(GATE_ESP) 2>"$$log"; \
     +    uint32_t word;
     +    (void)chip_trng_read(&word);
     +}
    +From ab26f081d94874d2e56b0fd9ce25dc03d0802e87 Mon Sep 17 00:00:00 2001
    +From: "Javier G. Montoya S" <[email protected]>
    +Date: Fri, 7 Aug 2026 18:07:32 -0400
    +Subject: [PATCH 3/3] test: gate external STM32 rng_get provider
    +---
    + README.md              |  9 +++++----
    + ngu/ngu_tests/Makefile | 11 ++++++++---
    + 2 files changed, 13 insertions(+), 7 deletions(-)
    +diff --git a/README.md b/README.md
    +index d45a673..e079bd9 100644
    +--- a/README.md
    +@@ -56,10 +56,11 @@ make -f makefile.stm32
    + target entropy source. Generated bytes are also XORed with fresh source words.
    + There is no software fallback:
    +-- STM32 uses MicroPython's `rng_get()` only when the board sets
    +-  `MICROPY_HW_ENABLE_RNG=1`. The pinned MicroPython source otherwise provides a
    +-  pseudo-random implementation. A zero word is rejected because `rng_get()`
    +-  also returns zero on a peripheral timeout.
    ++- STM32 uses `rng_get()` only when the board enables MicroPython's hardware RNG
    ++  with `MICROPY_HW_ENABLE_RNG=1`, or supplies an external implementation and
    ++  sets `NGU_STM32_EXTERNAL_RNG_GET=1`. Without either source, the build fails. A
    ++  zero word is rejected; MicroPython's STM32 implementation uses zero to report
    ++  a peripheral timeout.
    + - ESP32 uses `esp_random()` only when the build sets
    +   `NGU_ESP32_RNG_IS_TRUE_RANDOM=1`. This is the integrator's attestation that a
    +   true entropy source remains active whenever random data is requested. Without
    +diff --git a/ngu/ngu_tests/Makefile b/ngu/ngu_tests/Makefile
    +index 305b4dc..4af1a33 100644
    +--- a/ngu/ngu_tests/Makefile
    +@@ -54,12 +54,17 @@ gates:
    + 	@set -e; log=$$(mktemp /tmp/libngu-rng-gates.XXXXXX); \
    + 		trap 'rm -f "$$log"' 0; \
    + 		! $(GATE_STM_BASE) 2>"$$log"; \
    +-		grep -q 'MICROPY_HW_ENABLE_RNG=1 required' "$$log"; \
    ++		grep -q 'MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required' "$$log"; \
    + 		! $(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=0 2>"$$log"; \
    +-		grep -q 'MICROPY_HW_ENABLE_RNG=1 required' "$$log"; \
    ++		grep -q 'MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required' "$$log"; \
    + 		! $(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=2 2>"$$log"; \
    +-		grep -q 'MICROPY_HW_ENABLE_RNG=1 required' "$$log"; \
    ++		grep -q 'MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required' "$$log"; \
    ++		! $(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=0 -DNGU_STM32_EXTERNAL_RNG_GET=0 2>"$$log"; \
    ++		grep -q 'MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required' "$$log"; \
    ++		! $(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=0 -DNGU_STM32_EXTERNAL_RNG_GET=2 2>"$$log"; \
    ++		grep -q 'MICROPY_HW_ENABLE_RNG=1 or NGU_STM32_EXTERNAL_RNG_GET=1 required' "$$log"; \
    + 		$(GATE_STM); \
    ++		$(GATE_STM_BASE) -DMICROPY_HW_ENABLE_RNG=0 -DNGU_STM32_EXTERNAL_RNG_GET=1; \
    + 		! $(GATE_ESP) 2>"$$log"; \
    + 		grep -q 'NGU_ESP32_RNG_IS_TRUE_RANDOM=1 required' "$$log"; \
    + 		! $(GATE_ESP) -DNGU_ESP32_RNG_IS_TRUE_RANDOM=0 2>"$$log"; \
    
    Extracted text as captured
    From 5c1e8546e4e02c854585e4559aedb9ed4eecd315 Mon Sep 17 00:00:00 2001
    From: "Javier G. Montoya S" <[email protected]>
    Date: Thu, 6 Aug 2026 22:56:59 -0400
    Subject: [PATCH 1/3] random: harden entropy backend boundaries
    ---
     ngu/random.c           | 93 +++++++++++-------------------------------
     ngu/random.h           |  5 ++-
     ngu/random_backend.h   | 60 +++++++++++++++++++++++++++
     var/mpconfigvariant.mk |  3 +-
     4 files changed, 89 insertions(+), 72 deletions(-)
     create mode 100644 ngu/random_backend.h
    diff --git a/ngu/random.c b/ngu/random.c
    index 49fd469..f074632 100644
    --- a/ngu/random.c
    +++ b/ngu/random.c
    @@ -1,63 +1,15 @@
    -//
    -// random - RNG stuff
     //
    -// - common interface to TRNG specific to your chip
    -// - whitening
    -// - pick new privkeys
    +// random - cryptographic random number generation
    +//
    +// Cifra Hash_DRBG is seeded from the target entropy source. Generated bytes
    +// are also XORed with fresh source words after basic source-failure checks.
     //
     #include "py/runtime.h"
     #include "py/mperrno.h"
    -#include <string.h>
    -#include <stdlib.h>
    -#include <stdio.h>
     #include "my_assert.h"
     #include "cifra/drbg.h"
     #include "cifra/ext/handy.h"
    -
    -// ESP32 code
    -#ifdef ESP_PLATFORM
    -# include "esp_system.h"
    -# define CHIP_TRNG_SETUP()

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

  3. Earliest copy held
    seen · Captured here 18,416 chars
    Extracted text as captured
    From c09eff66214b800f4b632b5aa7a76bb261d2e75a Mon Sep 17 00:00:00 2001
    From: "Javier G. Montoya S" <[email protected]>
    Date: Thu, 6 Aug 2026 17:39:07 -0400
    Subject: [PATCH 1/3] random: reject two-word entropy cycles
    ---
     ngu/entropy_health.h                | 31 +++++++++++++++++++
     ngu/ngu_tests/Makefile              | 10 ++++--
     ngu/ngu_tests/entropy_health_test.c | 47 +++++++++++++++++++++++++++++
     ngu/random.c                        |  8 ++---
     4 files changed, 90 insertions(+), 6 deletions(-)
     create mode 100644 ngu/entropy_health.h
     create mode 100644 ngu/ngu_tests/entropy_health_test.c
    diff --git a/ngu/entropy_health.h b/ngu/entropy_health.h
    new file mode 100644
    index 0000000..787ee1f
    --- /dev/null
    +++ b/ngu/entropy_health.h
    @@ -0,0 +1,31 @@
    +//
    +// entropy_health.h - detect exact repetition in recent entropy words
    +//
    +#pragma once
    +
    +#include <stdbool.h>
    +#include <stdint.h>
    +
    +typedef struct {
    +    uint32_t history[2];
    +    uint8_t count;
    +} entropy_health_t;
    +
    +// Reject a word matching either of the previous two accepted words. This
    +// catches a stuck source and exact two-word cycles. Rejections do not advance
    +// history. This is a narrow runtime check, not entropy-source validation.
    +static inline bool entropy_health_accept(entropy_health_t *health, uint32_t word)
    +{
    +    for (uint8_t i = 0; i < health->count; i++) {
    +        if (word == health->history[i]) {
    +            return false;
    +        }

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

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.