-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Reduce key duplication by enabling hardware RNG #8803
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces a portable HardwareRNG helper to improve random number generation across platforms by tapping into hardware RNG sources (NRF52 crypto RNG, ESP32 TRNG, RP2040 hwrand, Portduino host RNG) and optionally mixing in modem-provided entropy. The goal is to reduce key duplication issues on NRF devices by stirring hardware bytes into CryptRNG during key generation.
Key changes:
- Added
HardwareRNGnamespace withfill()andseed()functions for platform-agnostic hardware entropy access - Integrated hardware entropy into
CryptoEngine::generateKeyPair()to strengthen key generation - Exposed
RadioLibInterface::randomBytes()to allow modem entropy mixing when radios are initialized - Updated NRF52, RP2040, and Portduino platform initialization to use the new
HardwareRNG::seed()helper
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/mesh/HardwareRNG.h |
New header defining the HardwareRNG interface with fill() and seed() functions |
src/mesh/HardwareRNG.cpp |
Implementation providing platform-specific hardware RNG access with optional radio entropy mixing |
src/mesh/RadioLibInterface.h |
Added public randomBytes() method declaration for exposing modem entropy |
src/mesh/RadioLibInterface.cpp |
Implemented randomBytes() to fetch entropy from LoRa modem via RadioLib API |
src/mesh/CryptoEngine.cpp |
Integrated HardwareRNG to stir 64 bytes of hardware entropy into CryptRNG during key generation |
src/platform/nrf52/main-nrf52.cpp |
Replaced direct nRFCrypto RNG calls with HardwareRNG::seed() |
src/platform/rp2xx0/main-rp2xx0.cpp |
Added HardwareRNG::seed() call alongside existing hwrand32 usage |
src/platform/portduino/PortduinoGlue.cpp |
Added HardwareRNG::seed() calls in both simulated and native modes |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| /** | ||
| * Request randomness sourced from the LoRa modem, if supported by the active RadioLib interface. | ||
| * @return true if len bytes were produced, false otherwise. |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation comment has a typo: "@return true if len bytes were produced" should be "@return true if length bytes were produced" to match the actual parameter name.
| * @return true if len bytes were produced, false otherwise. | |
| * @return true if length bytes were produced, false otherwise. |
| uint32_t seed = rp2040.hwrand32(); | ||
| HardwareRNG::seed(seed); |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The HardwareRNG::seed() function signature is bool seed(uint32_t &seedOut), which expects a reference to populate with a generated seed. However, this code is passing an already-generated seed value as an argument. This is a mismatch in usage - the function is designed to produce a seed, not consume one.
The correct usage should be:
uint32_t seed = 0;
if (!HardwareRNG::seed(seed)) {
seed = rp2040.hwrand32(); // fallback
}
randomSeed(seed);This way, HardwareRNG::seed() can populate the seed variable with hardware entropy (including potential radio mixing), and if it fails, you fall back to just the RP2040 hardware RNG.
| uint32_t seed = rp2040.hwrand32(); | |
| HardwareRNG::seed(seed); | |
| uint32_t seed = 0; | |
| if (!HardwareRNG::seed(seed)) { | |
| seed = rp2040.hwrand32(); | |
| } |
| uint32_t seed = TCPPort; | ||
| HardwareRNG::seed(seed); | ||
| randomSeed(seed); |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the RP2040 issue, the HardwareRNG::seed() function signature is bool seed(uint32_t &seedOut), which expects a reference to populate with a generated seed. However, this code is passing an already-set seed value (TCPPort).
The correct usage should be:
uint32_t seed = 0;
if (!HardwareRNG::seed(seed)) {
seed = TCPPort; // fallback
}
randomSeed(seed);This allows HardwareRNG::seed() to populate the seed with hardware entropy first, falling back to TCPPort only if hardware entropy is unavailable.
| uint32_t seed = static_cast<uint32_t>(time(NULL)); | ||
| HardwareRNG::seed(seed); |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the simulated mode case, the HardwareRNG::seed() function signature expects a reference to populate with a generated seed, not consume an already-set value.
The correct usage should be:
uint32_t seed = 0;
if (!HardwareRNG::seed(seed)) {
seed = static_cast<uint32_t>(time(NULL)); // fallback
}
randomSeed(seed);This allows HardwareRNG::seed() to generate and populate the seed with hardware entropy first, falling back to time(NULL) only if hardware entropy is unavailable.
| uint32_t seed = static_cast<uint32_t>(time(NULL)); | |
| HardwareRNG::seed(seed); | |
| uint32_t seed = 0; | |
| if (!HardwareRNG::seed(seed)) { | |
| seed = static_cast<uint32_t>(time(NULL)); // fallback | |
| } |
| if (value < 0) { | ||
| return false; | ||
| } | ||
| buffer[i] = static_cast<uint8_t>(value & 0xFF); |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The bitwise AND operation value & 0xFF is redundant when casting to uint8_t. The cast to uint8_t already truncates the value to 8 bits, making the mask unnecessary.
Consider simplifying to: buffer[i] = static_cast<uint8_t>(value);
| buffer[i] = static_cast<uint8_t>(value & 0xFF); | |
| buffer[i] = static_cast<uint8_t>(value); |
| RadioLibInterface *radio = RadioLibInterface::instance; | ||
| if (!radio) { | ||
| return false; | ||
| } |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Accessing the static RadioLibInterface::instance pointer without synchronization could lead to race conditions if HardwareRNG::fill() is called from multiple threads simultaneously, or if it's called while the radio interface is being initialized or destroyed.
While the current code checks for null and handles the case gracefully, consider documenting the thread-safety expectations of this function, or adding appropriate synchronization if fill() can be called from multiple threads.
| uint32_t seed = 0; | ||
| if (!HardwareRNG::seed(seed)) { | ||
| LOG_WARN("Hardware RNG seed unavailable, using PRNG fallback"); | ||
| seed = random(); |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The seed variable is initialized to 0, but when HardwareRNG::seed(seed) returns false, the fallback uses random() to generate a new seed but assigns it to a local variable seed. However, at this point, randomSeed() has not yet been called, so random() will use an uninitialized or default seed state, potentially producing the same value across reboots.
Consider calling random() with a hardware-derived value first, or using a different fallback mechanism such as reading from an uninitialized memory location or system timer.
| seed = random(); | |
| // Use a hardware timer value as a fallback seed for better entropy | |
| seed = micros(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually seems like good feedback. We might consider an XOR of the deviceID, MAC address, and micros() as the fallback seed.
| if (!filled) { | ||
| // As a last resort, fall back to std::random_device. This should only be reached | ||
| // if a platform-specific source was unavailable. | ||
| fillWithRandomDevice(buffer, length); | ||
| filled = true; | ||
| } |
Copilot
AI
Nov 29, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The fallback logic at lines 131-136 is unreachable code. All platform-specific branches (lines 95-129) set filled = true, so the condition if (!filled) at line 131 can never be true. This fallback code will never execute.
Consider removing this dead code block, or restructuring the logic if there's an intention to handle edge cases where platform-specific sources might fail.
| if (!filled) { | |
| // As a last resort, fall back to std::random_device. This should only be reached | |
| // if a platform-specific source was unavailable. | |
| fillWithRandomDevice(buffer, length); | |
| filled = true; | |
| } |
|
I particularly like that this is just stirring in another potential randomness source. Will go through it in more detail as I am able. |
| uint32_t seed = 0; | ||
| if (!HardwareRNG::seed(seed)) { | ||
| LOG_WARN("Hardware RNG seed unavailable, using PRNG fallback"); | ||
| seed = random(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This actually seems like good feedback. We might consider an XOR of the deviceID, MAC address, and micros() as the fallback seed.
| filled = true; | ||
| } | ||
| #else | ||
| fillWithRandomDevice(buffer, length); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How good/bad of a source of entropy is std::random_device on various platforms? STM32 will fall over into this code, for instance. I think we want to just return false here instead.
Summary
🤝 Attestations