From f02bf0763042e120da69d5c4e1c779467dff203a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Thu, 19 Mar 2026 13:17:24 -1000 Subject: [PATCH] [core] Use SplitMix32 PRNG for random_uint32() across platforms Replace platform-specific random_uint32() implementations with a unified SplitMix32 PRNG seeded from random_bytes(). This provides consistent, fast, non-cryptographic random numbers on all platforms. ESP8266 retains os_random() as it is already faster on that platform. Benchmarked on real hardware (ns/call, before -> after): - RP2040: 2815 -> 333 (8.5x faster) - ESP32: 871 -> 354 (2.5x faster) - BK72xx: 1073 -> 882 (1.2x faster, replaces weak rand()) - RTL87xx: 330 -> 283 (1.2x faster, replaces weak rand()) - ESP8266: 763 (unchanged, keeps os_random()) random_bytes() remains platform-specific hardware RNG for crypto use. --- esphome/components/esp32/helpers.cpp | 1 - esphome/components/host/helpers.cpp | 9 --------- esphome/components/libretiny/helpers.cpp | 2 -- esphome/components/rp2040/helpers.cpp | 9 --------- esphome/components/zephyr/core.cpp | 1 - esphome/core/helpers.cpp | 19 +++++++++++++++++++ esphome/core/helpers.h | 7 ++++++- 7 files changed, 25 insertions(+), 23 deletions(-) diff --git a/esphome/components/esp32/helpers.cpp b/esphome/components/esp32/helpers.cpp index 76f1c59c73..654a35b473 100644 --- a/esphome/components/esp32/helpers.cpp +++ b/esphome/components/esp32/helpers.cpp @@ -14,7 +14,6 @@ namespace esphome { -uint32_t random_uint32() { return esp_random(); } bool random_bytes(uint8_t *data, size_t len) { esp_fill_random(data, len); return true; diff --git a/esphome/components/host/helpers.cpp b/esphome/components/host/helpers.cpp index fdad4f5cb6..7e8849b3e1 100644 --- a/esphome/components/host/helpers.cpp +++ b/esphome/components/host/helpers.cpp @@ -8,8 +8,6 @@ #include #endif #include -#include -#include #include "esphome/core/defines.h" #include "esphome/core/log.h" @@ -18,13 +16,6 @@ namespace esphome { static const char *const TAG = "helpers.host"; -uint32_t random_uint32() { - std::random_device dev; - std::mt19937 rng(dev()); - std::uniform_int_distribution dist(0, std::numeric_limits::max()); - return dist(rng); -} - bool random_bytes(uint8_t *data, size_t len) { FILE *fp = fopen("/dev/urandom", "r"); if (fp == nullptr) { diff --git a/esphome/components/libretiny/helpers.cpp b/esphome/components/libretiny/helpers.cpp index ffbd181c54..52332ef53d 100644 --- a/esphome/components/libretiny/helpers.cpp +++ b/esphome/components/libretiny/helpers.cpp @@ -8,8 +8,6 @@ namespace esphome { -uint32_t random_uint32() { return rand(); } - bool random_bytes(uint8_t *data, size_t len) { lt_rand_bytes(data, len); return true; diff --git a/esphome/components/rp2040/helpers.cpp b/esphome/components/rp2040/helpers.cpp index ad69192af9..36267619a5 100644 --- a/esphome/components/rp2040/helpers.cpp +++ b/esphome/components/rp2040/helpers.cpp @@ -16,15 +16,6 @@ namespace esphome { -uint32_t random_uint32() { - uint32_t result = 0; - for (uint8_t i = 0; i < 32; i++) { - result <<= 1; - result |= rosc_hw->randombit; - } - return result; -} - bool random_bytes(uint8_t *data, size_t len) { while (len-- != 0) { uint8_t result = 0; diff --git a/esphome/components/zephyr/core.cpp b/esphome/components/zephyr/core.cpp index d7c77fdd2c..a3b0471ebc 100644 --- a/esphome/components/zephyr/core.cpp +++ b/esphome/components/zephyr/core.cpp @@ -75,7 +75,6 @@ IRAM_ATTR InterruptLock::~InterruptLock() { irq_unlock(state_); } // Zephyr LwIPLock is defined inline as a no-op in helpers.h -uint32_t random_uint32() { return rand(); } // NOLINT(cert-msc30-c, cert-msc50-cpp) bool random_bytes(uint8_t *data, size_t len) { sys_rand_get(data, len); return true; diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index 00b447ebf2..be400bfef2 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -156,6 +156,25 @@ uint32_t fnv1_hash(const char *str) { return hash; } +// SplitMix32 PRNG — MurmurHash3 finalizer with golden-ratio increment. +// Provides fast, uniform, non-cryptographic random numbers. +// Seeded lazily from the platform's hardware RNG via random_bytes(). +// ESP8266 uses os_random() instead (defined in esp8266/helpers.cpp). +#ifndef USE_ESP8266 +static uint32_t splitmix32_state; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +uint32_t random_uint32() { + if (splitmix32_state == 0) { + random_bytes(reinterpret_cast(&splitmix32_state), sizeof(splitmix32_state)); + } + splitmix32_state += 0x9e3779b9u; + uint32_t z = splitmix32_state; + z = (z ^ (z >> 16)) * 0x85ebca6bu; + z = (z ^ (z >> 13)) * 0xc2b2ae35u; + return z ^ (z >> 16); +} +#endif + float random_float() { return static_cast(random_uint32()) / static_cast(UINT32_MAX); } // Strings diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index a703b5a5f3..6bf8371f58 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -842,10 +842,15 @@ template inline constexpr ESPHOME_ALWAYS_INLINE Ret } /// Return a random 32-bit unsigned integer. +/// Not thread-safe. Must only be called from the main loop. +/// Not suitable for cryptographic use; use random_bytes() instead. uint32_t random_uint32(); /// Return a random float between 0 and 1. +/// Not thread-safe. Must only be called from the main loop. +/// Not suitable for cryptographic use; use random_bytes() instead. float random_float(); -/// Generate \p len number of random bytes. +/// Generate \p len number of random bytes from the platform's hardware RNG. +/// Thread-safe. Suitable for cryptographic use. bool random_bytes(uint8_t *data, size_t len); ///@}