Skip to content

Conversation

@Komzpa
Copy link

@Komzpa Komzpa commented Nov 29, 2025

Summary

  • add portable HardwareRNG helper that taps NRF52 crypto RNG, ESP32/TRNG, RP2040 hwrand, Portduino host RNG, and modem-provided entropy as available
  • stir hardware bytes into CryptRNG during key generation to avoid repeated keys on NRF devices
  • route NRF52, Portduino, and RP2040 platform setup through the new helper so runtime random seeds also benefit
  • expose RadioLibInterface::randomBytes so modem entropy can be mixed in when radios are initialized

🤝 Attestations

  • I have tested that my proposed changes behave as described.
  • I have tested that my proposed changes do not cause any obvious regressions on the following devices:
    • Heltec (Lora32) V3
    • LilyGo T-Deck
    • LilyGo T-Beam
    • RAK WisBlock 4631
    • Seeed Studio T-1000E tracker card
    • Other (please specify below)

Copy link
Contributor

Copilot AI left a 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 HardwareRNG namespace with fill() and seed() 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.
Copy link

Copilot AI Nov 29, 2025

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.

Suggested change
* @return true if len bytes were produced, false otherwise.
* @return true if length bytes were produced, false otherwise.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +104
uint32_t seed = rp2040.hwrand32();
HardwareRNG::seed(seed);
Copy link

Copilot AI Nov 29, 2025

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.

Suggested change
uint32_t seed = rp2040.hwrand32();
HardwareRNG::seed(seed);
uint32_t seed = 0;
if (!HardwareRNG::seed(seed)) {
seed = rp2040.hwrand32();
}

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +231
uint32_t seed = TCPPort;
HardwareRNG::seed(seed);
randomSeed(seed);
Copy link

Copilot AI Nov 29, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +439 to +440
uint32_t seed = static_cast<uint32_t>(time(NULL));
HardwareRNG::seed(seed);
Copy link

Copilot AI Nov 29, 2025

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.

Suggested change
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
}

Copilot uses AI. Check for mistakes.
if (value < 0) {
return false;
}
buffer[i] = static_cast<uint8_t>(value & 0xFF);
Copy link

Copilot AI Nov 29, 2025

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);

Suggested change
buffer[i] = static_cast<uint8_t>(value & 0xFF);
buffer[i] = static_cast<uint8_t>(value);

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +52
RadioLibInterface *radio = RadioLibInterface::instance;
if (!radio) {
return false;
}
Copy link

Copilot AI Nov 29, 2025

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.

Copilot uses AI. Check for mistakes.
uint32_t seed = 0;
if (!HardwareRNG::seed(seed)) {
LOG_WARN("Hardware RNG seed unavailable, using PRNG fallback");
seed = random();
Copy link

Copilot AI Nov 29, 2025

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.

Suggested change
seed = random();
// Use a hardware timer value as a fallback seed for better entropy
seed = micros();

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

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.

Comment on lines +131 to +136
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;
}
Copy link

Copilot AI Nov 29, 2025

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.

Suggested change
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 uses AI. Check for mistakes.
@jp-bennett
Copy link
Collaborator

I particularly like that this is just stirring in another potential randomness source. Will go through it in more detail as I am able.

@jp-bennett jp-bennett added the enhancement New feature or request label Nov 30, 2025
uint32_t seed = 0;
if (!HardwareRNG::seed(seed)) {
LOG_WARN("Hardware RNG seed unavailable, using PRNG fallback");
seed = random();
Copy link
Collaborator

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);
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants