From 4ac8742ce0bd9d09fbd93cb51681aea7e1b738bb Mon Sep 17 00:00:00 2001 From: Nick Dunklee Date: Tue, 23 Jun 2026 11:28:55 -0600 Subject: [PATCH 01/10] feat: use RAK hardware crypto on advert processing While working on some sensor code implementations, I ran into some hard crashes that root-caused to the 4KB loop task stack being exhausted. Looking for various optimization schemes resulted in this relatively low-lift fix. RAK3401 and RAK4631 both support hardware crypto. Rather than loading in one of the two software crypto libs, we can just use the onboard hardware. This is faster, should consume less power, and in testing used a scant up to 700 bytes in the run loop vs 2.5-3KB per advert. This change **only** affects advert verification processing, which currently consumes a significant chunk of the 4KB run loop. I figured such a change should likely be implemented in phases. After soaking, this hardware crypto verification process could be implemented across the entire MeshCore cryptographic function on RAK nodes. Also possible other nodes have available hardware crypto, however, I have not checked, so future improvements may also exist there. Tested on: - RAK3401 RAK 1W - RAK4631 19001 --- src/Identity.cpp | 20 +++++++++++++++++++- variants/rak3401/platformio.ini | 1 + variants/rak4631/platformio.ini | 1 + 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Identity.cpp b/src/Identity.cpp index ea546274..2ab1cefa 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -4,6 +4,11 @@ #include #include +#ifdef USE_CC310_ED25519 +#include +#include "nrf_cc310/include/crys_ec_edw_api.h" +#endif + namespace mesh { Identity::Identity() { @@ -15,7 +20,20 @@ Identity::Identity(const char* pub_hex) { } bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const { -#if 0 +#ifdef USE_CC310_ED25519 + // nRF52840 CryptoCell CC310 hardware Ed25519 verification. The software + // implementations need ~3KB of stack (which can overflow the Adafruit core's + // 4KB loop task stack from the advert receive path); the hardware path + // needs much less, around 600-700bytes. The CC310 workspace is static, faster, + // should save power at scale as well. + static CRYS_ECEDW_TempBuff_t cc310_tmp; + nRFCrypto.begin(); + CRYSError_t rc = CRYS_ECEDW_Verify((uint8_t*)sig, CRYS_ECEDW_SIGNATURE_BYTES, + (uint8_t*)pub_key, CRYS_ECEDW_MOD_SIZE_IN_BYTES, + (uint8_t*)message, (size_t)msg_len, &cc310_tmp); + nRFCrypto.end(); + return rc == CRYS_OK; +#elif 0 // NOTE: memory corruption bug was found in this function!! return ed25519_verify(sig, message, msg_len, pub_key); #else diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 20a8a548..9537fc7f 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -13,6 +13,7 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX with SKY66122 FEM + -D USE_CC310_ED25519=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak3401> + diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 2bbba314..f6b634ad 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -22,6 +22,7 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D USE_CC310_ED25519=1 -D ENV_INCLUDE_RAK12035=1 -UENV_INCLUDE_BME680 -D ENV_INCLUDE_BME680_BSEC=1 From 7a2764ebd64d74812e91d351b6008631368b1376 Mon Sep 17 00:00:00 2001 From: Nick Dunklee Date: Wed, 24 Jun 2026 15:58:09 -0600 Subject: [PATCH 02/10] Added Heltec t096 and seeed t1000-e Tested Heltec t096 and Seeed t1000-e, both support this hardware feature. --- variants/heltec_t096/platformio.ini | 1 + variants/t1000-e/platformio.ini | 1 + 2 files changed, 2 insertions(+) diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index e820bf58..4da11c0c 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -29,6 +29,7 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D USE_CC310_ED25519=1 -D PIN_VEXT_EN=26 ; Vext is connected to VDD which is also connected to TFT & GPS -D PIN_VEXT_EN_ACTIVE=HIGH -D PIN_GPS_RX=25 diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 43a3d93f..9218c578 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D RF_SWITCH_TABLE -D RX_BOOSTED_GAIN=true + -D USE_CC310_ED25519=1 -D P_LORA_BUSY=7 ; P0.7 -D P_LORA_SCLK=11 ; P0.11 -D P_LORA_NSS=12 ; P0.12 From 5c534c4399a01bcef6681eaa7fb62fafb6da8fcf Mon Sep 17 00:00:00 2001 From: Jody Bentley Date: Mon, 29 Jun 2026 14:49:11 -0400 Subject: [PATCH 03/10] thinknode_m6: don't drive GPS REINIT pin (fixes GPS never detected) The M6's L76K GPS streams NMEA at 9600 baud on its own, but the firmware reported no GPS. Root cause: pin 29 (PIN_GPS_RESET / the module's REINIT line) was driven HIGH, which holds the L76K silent so it never emits any sentences and detection fails. variant.cpp drove pin 29 HIGH at boot, and because PIN_GPS_RESET was defined, MicroNMEALocationProvider also drove it HIGH in begin(). Bench testing on a sealed M6 (passive NMEA capture, no logic analyzer) confirmed: pin 29 driven HIGH = 0 bytes; pin 29 floating = full NMEA stream. Define GPS_RESET (-1) so the location provider never touches pin 29, and stop driving it in initVariant. This matches the Meshtastic M6 variant, which leaves the same pin as a floating input. --- variants/thinknode_m6/variant.cpp | 6 ++++-- variants/thinknode_m6/variant.h | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/variants/thinknode_m6/variant.cpp b/variants/thinknode_m6/variant.cpp index c88f387d..79930e2d 100644 --- a/variants/thinknode_m6/variant.cpp +++ b/variants/thinknode_m6/variant.cpp @@ -30,6 +30,8 @@ void initVariant() { digitalWrite(PIN_GPS_STANDBY, HIGH); pinMode(PIN_GPS_EN, OUTPUT); digitalWrite(PIN_GPS_EN, HIGH); - pinMode(PIN_GPS_RESET, OUTPUT); - digitalWrite(PIN_GPS_RESET, HIGH); + // PIN_GPS_RESET (pin 29 / REINIT) is intentionally left floating (input). + // Driving it HIGH holds the L76K silent, so it never streams NMEA and the + // firmware reports "no GPS". Letting it float lets the module run, matching + // the Meshtastic M6 variant. See GPS_RESET (-1) in variant.h. } diff --git a/variants/thinknode_m6/variant.h b/variants/thinknode_m6/variant.h index 70fd6506..0a4025f3 100644 --- a/variants/thinknode_m6/variant.h +++ b/variants/thinknode_m6/variant.h @@ -102,7 +102,12 @@ #define PIN_GPS_RX (2) #define PIN_GPS_TX (3) #define PIN_GPS_EN (6) // EN -#define PIN_GPS_RESET (29) +#define PIN_GPS_RESET (29) // REINIT - must FLOAT; driving it (esp. HIGH) silences the L76K +// The M6's L76K streams NMEA on its own and must not have its REINIT pin driven. +// Tell the location provider there is no reset pin so it never touches pin 29 +// (driving it HIGH holds the module silent). Matches Meshtastic, which leaves +// this pin as an input. See variant.cpp (pin 29 is intentionally not configured). +#define GPS_RESET (-1) #define PIN_GPS_STANDBY (30) // STANDBY #define PIN_GPS_PPS (31) #define GPS_BAUD_RATE 9600 From a9d25740390a5ad425cd71057e107fb3821fd4b2 Mon Sep 17 00:00:00 2001 From: Nick Dunklee Date: Tue, 14 Jul 2026 08:35:27 -0600 Subject: [PATCH 04/10] feat: add more crypto After soaking for a bit on the adverts without issue on multiple nodes, I added more hardware crypto. Supported nodes is unchanged in this PR addition, but if others can verify, they can easily be added. Some info on the CC310: https://docs.nordicsemi.com/r/bundle/ps_nrf9151/page/cryptocell.html **Added:** - AES-128 packet encryption/decryption now use hardware crypto - HMAC-SHA-256 authentication now uses hardware crypto - ACK hash computation and channel ID derivation now use hardware crypto - RNG (random number generator) now uses hardware crypto rather than radio noise + weak software RNG (which can have issues if there's no surrounding radio noise.) NIST SP 800-90B certified. - Runs hardware self-tests on startup - Runs continuous health tests during operation - Uses thermal noise/shot noise for randomness **Unchanged:** - calcSharedSecret remains software - it would be a split hw/sw solution and added complexity for likely not a lot of gains. This only happens when establishing a new contact, so not too frequent to be worth it. - ed25519_create_keypair remains software. This is only called when a node is first initialized. It does use the hardware RNG change, however, so better randomization. Tested on (so far): - Heltec t096 Build test on: - Heltec t096 companion ble - t1000e companion ble - RAK 4631 repeater - RAK 3401 companion BLE - Heltec v3 companion wifi --- src/Utils.cpp | 98 ++++++++++++++++++++++++- src/helpers/radiolib/RadioLibWrappers.h | 11 +++ 2 files changed, 108 insertions(+), 1 deletion(-) diff --git a/src/Utils.cpp b/src/Utils.cpp index 186c8720..9cd44ce2 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -2,6 +2,13 @@ #include #include +#ifdef USE_CC310_ED25519 +#include +#include "nrf_cc310/include/crys_hash.h" +#include "nrf_cc310/include/crys_hmac.h" +#include "nrf_cc310/include/ssi_aes.h" +#endif + #ifdef ARDUINO #include #endif @@ -15,19 +22,58 @@ uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) { } void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) { +#ifdef USE_CC310_ED25519 + static CRYS_HASH_Result_t result; + nRFCrypto.begin(); + CRYS_HASH(CRYS_HASH_SHA256_mode, (uint8_t*)msg, (size_t)msg_len, result); + nRFCrypto.end(); + memcpy(hash, result, hash_len); +#else SHA256 sha; sha.update(msg, msg_len); sha.finalize(hash, hash_len); +#endif } void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len) { +#ifdef USE_CC310_ED25519 + static CRYS_HASHUserContext_t ctx; + static CRYS_HASH_Result_t result; + nRFCrypto.begin(); + CRYS_HASH_Init(&ctx, CRYS_HASH_SHA256_mode); + CRYS_HASH_Update(&ctx, (uint8_t*)frag1, (size_t)frag1_len); + CRYS_HASH_Update(&ctx, (uint8_t*)frag2, (size_t)frag2_len); + CRYS_HASH_Finish(&ctx, result); + nRFCrypto.end(); + memcpy(hash, result, hash_len); +#else SHA256 sha; sha.update(frag1, frag1_len); sha.update(frag2, frag2_len); sha.finalize(hash, hash_len); +#endif } int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { +#ifdef USE_CC310_ED25519 + static SaSiAesUserContext_t ctx; + SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE }; + uint8_t* dp = dest; + const uint8_t* sp = src; + size_t dummy_out = 0; + + nRFCrypto.begin(); + SaSi_AesInit(&ctx, SASI_AES_DECRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE); + SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData)); + while (sp - src < src_len) { + SaSi_AesBlock(&ctx, (uint8_t*)sp, 16, dp); + dp += 16; sp += 16; + } + SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out); + SaSi_AesFree(&ctx); + nRFCrypto.end(); + return sp - src; +#else AES128 aes; uint8_t* dp = dest; const uint8_t* sp = src; @@ -39,9 +85,34 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s } return sp - src; // will always be multiple of 16 +#endif } int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { +#ifdef USE_CC310_ED25519 + static SaSiAesUserContext_t ctx; + SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE }; + uint8_t* dp = dest; + size_t dummy_out = 0; + + nRFCrypto.begin(); + SaSi_AesInit(&ctx, SASI_AES_ENCRYPT, SASI_AES_MODE_ECB, SASI_AES_PADDING_NONE); + SaSi_AesSetKey(&ctx, SASI_AES_USER_KEY, &keyData, sizeof(keyData)); + while (src_len >= 16) { + SaSi_AesBlock(&ctx, (uint8_t*)src, 16, dp); + dp += 16; src += 16; src_len -= 16; + } + if (src_len > 0) { // remaining partial block — zero-pad to 16 bytes + uint8_t tmp[16] = {}; + memcpy(tmp, src, src_len); + SaSi_AesBlock(&ctx, tmp, 16, dp); + dp += 16; + } + SaSi_AesFinish(&ctx, 0, NULL, 0, NULL, &dummy_out); + SaSi_AesFree(&ctx); + nRFCrypto.end(); + return dp - dest; +#else AES128 aes; uint8_t* dp = dest; @@ -58,15 +129,27 @@ int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s dp += 16; } return dp - dest; // will always be multiple of 16 +#endif } int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { int enc_len = encrypt(shared_secret, dest + CIPHER_MAC_SIZE, src, src_len); +#ifdef USE_CC310_ED25519 + static CRYS_HMACUserContext_t hmac_ctx; + static CRYS_HASH_Result_t hmac_result; + nRFCrypto.begin(); + CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE); + CRYS_HMAC_Update(&hmac_ctx, dest + CIPHER_MAC_SIZE, enc_len); + CRYS_HMAC_Finish(&hmac_ctx, hmac_result); + nRFCrypto.end(); + memcpy(dest, hmac_result, CIPHER_MAC_SIZE); +#else SHA256 sha; sha.resetHMAC(shared_secret, PUB_KEY_SIZE); sha.update(dest + CIPHER_MAC_SIZE, enc_len); sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, dest, CIPHER_MAC_SIZE); +#endif return CIPHER_MAC_SIZE + enc_len; } @@ -75,12 +158,25 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin if (src_len <= CIPHER_MAC_SIZE) return 0; // invalid src bytes uint8_t hmac[CIPHER_MAC_SIZE]; +#ifdef USE_CC310_ED25519 + { + static CRYS_HMACUserContext_t hmac_ctx; + static CRYS_HASH_Result_t hmac_result; + nRFCrypto.begin(); + CRYS_HMAC_Init(&hmac_ctx, CRYS_HASH_SHA256_mode, (uint8_t*)shared_secret, PUB_KEY_SIZE); + CRYS_HMAC_Update(&hmac_ctx, (uint8_t*)(src + CIPHER_MAC_SIZE), src_len - CIPHER_MAC_SIZE); + CRYS_HMAC_Finish(&hmac_ctx, hmac_result); + nRFCrypto.end(); + memcpy(hmac, hmac_result, CIPHER_MAC_SIZE); + } +#else { SHA256 sha; sha.resetHMAC(shared_secret, PUB_KEY_SIZE); sha.update(src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE); sha.finalizeHMAC(shared_secret, PUB_KEY_SIZE, hmac, CIPHER_MAC_SIZE); } +#endif if (memcmp(hmac, src, CIPHER_MAC_SIZE) == 0) { return decrypt(shared_secret, dest, src + CIPHER_MAC_SIZE, src_len - CIPHER_MAC_SIZE); } @@ -150,4 +246,4 @@ int Utils::parseTextParts(char* text, const char* parts[], int max_num, char sep return num; } -} \ No newline at end of file +} diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 3091832f..f390199f 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -3,6 +3,10 @@ #include #include +#ifdef USE_CC310_ED25519 +#include +#endif + class RadioLibWrapper : public mesh::Radio { protected: PhysicalLayer* _radio; @@ -80,8 +84,15 @@ public: RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { } void random(uint8_t* dest, size_t sz) override { +#ifdef USE_CC310_ED25519 + // CC310 TRNG is higher quality and environment-independent vs radio RSSI noise. + nRFCrypto.begin(); + nRFCrypto.Random.generate(dest, (uint16_t)sz); + nRFCrypto.end(); +#else for (int i = 0; i < sz; i++) { dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF); } +#endif } }; From 874f0c9ff8ea72db540ac1e1e8a7164b2ff7c49f Mon Sep 17 00:00:00 2001 From: Nick Dunklee Date: Thu, 16 Jul 2026 21:44:35 -0600 Subject: [PATCH 05/10] Minor ifdef rename as it now covers more than just Ed25519 for hardware encryption. Now `USE_CC310_HW_CRYPTO` --- src/Identity.cpp | 4 ++-- src/Utils.cpp | 14 +++++++------- src/helpers/radiolib/RadioLibWrappers.h | 4 ++-- variants/heltec_t096/platformio.ini | 2 +- variants/rak3401/platformio.ini | 2 +- variants/rak4631/platformio.ini | 2 +- variants/t1000-e/platformio.ini | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Identity.cpp b/src/Identity.cpp index 2ab1cefa..25419fd4 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -4,7 +4,7 @@ #include #include -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO #include #include "nrf_cc310/include/crys_ec_edw_api.h" #endif @@ -20,7 +20,7 @@ Identity::Identity(const char* pub_hex) { } bool Identity::verify(const uint8_t* sig, const uint8_t* message, int msg_len) const { -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO // nRF52840 CryptoCell CC310 hardware Ed25519 verification. The software // implementations need ~3KB of stack (which can overflow the Adafruit core's // 4KB loop task stack from the advert receive path); the hardware path diff --git a/src/Utils.cpp b/src/Utils.cpp index 9cd44ce2..d4bc8c45 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -2,7 +2,7 @@ #include #include -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO #include #include "nrf_cc310/include/crys_hash.h" #include "nrf_cc310/include/crys_hmac.h" @@ -22,7 +22,7 @@ uint32_t RNG::nextInt(uint32_t _min, uint32_t _max) { } void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_len) { -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO static CRYS_HASH_Result_t result; nRFCrypto.begin(); CRYS_HASH(CRYS_HASH_SHA256_mode, (uint8_t*)msg, (size_t)msg_len, result); @@ -36,7 +36,7 @@ void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* msg, int msg_l } void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int frag1_len, const uint8_t* frag2, int frag2_len) { -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO static CRYS_HASHUserContext_t ctx; static CRYS_HASH_Result_t result; nRFCrypto.begin(); @@ -55,7 +55,7 @@ void Utils::sha256(uint8_t *hash, size_t hash_len, const uint8_t* frag1, int fra } int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO static SaSiAesUserContext_t ctx; SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE }; uint8_t* dp = dest; @@ -89,7 +89,7 @@ int Utils::decrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s } int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO static SaSiAesUserContext_t ctx; SaSiAesUserKeyData_t keyData = { (uint8_t*)shared_secret, CIPHER_KEY_SIZE }; uint8_t* dp = dest; @@ -135,7 +135,7 @@ int Utils::encrypt(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* s int Utils::encryptThenMAC(const uint8_t* shared_secret, uint8_t* dest, const uint8_t* src, int src_len) { int enc_len = encrypt(shared_secret, dest + CIPHER_MAC_SIZE, src, src_len); -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO static CRYS_HMACUserContext_t hmac_ctx; static CRYS_HASH_Result_t hmac_result; nRFCrypto.begin(); @@ -158,7 +158,7 @@ int Utils::MACThenDecrypt(const uint8_t* shared_secret, uint8_t* dest, const uin if (src_len <= CIPHER_MAC_SIZE) return 0; // invalid src bytes uint8_t hmac[CIPHER_MAC_SIZE]; -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO { static CRYS_HMACUserContext_t hmac_ctx; static CRYS_HASH_Result_t hmac_result; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index f390199f..4cbbb055 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -3,7 +3,7 @@ #include #include -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO #include #endif @@ -84,7 +84,7 @@ public: RadioNoiseListener(PhysicalLayer& radio): _radio(&radio) { } void random(uint8_t* dest, size_t sz) override { -#ifdef USE_CC310_ED25519 +#ifdef USE_CC310_HW_CRYPTO // CC310 TRNG is higher quality and environment-independent vs radio RSSI noise. nRFCrypto.begin(); nRFCrypto.Random.generate(dest, (uint16_t)sz); diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index fae7d642..2f440e83 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -29,7 +29,7 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D USE_CC310_ED25519=1 + -D USE_CC310_HW_CRYPTO=1 -D PIN_VEXT_EN=26 ; Vext is connected to VDD which is also connected to TFT & GPS -D PIN_VEXT_EN_ACTIVE=HIGH -D PIN_GPS_RX=25 diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 9537fc7f..ea53b2dc 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -13,7 +13,7 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX with SKY66122 FEM - -D USE_CC310_ED25519=1 + -D USE_CC310_HW_CRYPTO=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak3401> + diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index f6b634ad..f94f7e30 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -22,7 +22,7 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D USE_CC310_ED25519=1 + -D USE_CC310_HW_CRYPTO=1 -D ENV_INCLUDE_RAK12035=1 -UENV_INCLUDE_BME680 -D ENV_INCLUDE_BME680_BSEC=1 diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 9218c578..d5e3bb53 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -18,7 +18,7 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D RF_SWITCH_TABLE -D RX_BOOSTED_GAIN=true - -D USE_CC310_ED25519=1 + -D USE_CC310_HW_CRYPTO=1 -D P_LORA_BUSY=7 ; P0.7 -D P_LORA_SCLK=11 ; P0.11 -D P_LORA_NSS=12 ; P0.12 From 783b21bb9ff3770851a634e5c18eb77384ba7e0b Mon Sep 17 00:00:00 2001 From: Nick Dunklee Date: Thu, 23 Jul 2026 14:02:34 -0600 Subject: [PATCH 06/10] Global nrf52 hardware crypto, removed from individual configs Select nodes had this flag enabled, testing by the community and hardware specs indicate this can be enabled global for all node types using this chipset. Any nodes down the line that may be quirky can be individually disabled with `-U USE_CC310_HW_CRYPTO`. --- platformio.ini | 1 + variants/heltec_t096/platformio.ini | 1 - variants/rak3401/platformio.ini | 1 - variants/rak4631/platformio.ini | 1 - variants/t1000-e/platformio.ini | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index f3ada133..c3254940 100644 --- a/platformio.ini +++ b/platformio.ini @@ -91,6 +91,7 @@ build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM -D LFS_NO_ASSERT=1 -D EXTRAFS=1 + -D USE_CC310_HW_CRYPTO=1 lib_deps = ${arduino_base.lib_deps} https://github.com/oltaco/CustomLFS#0.2.2 diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index 2f440e83..0a062f00 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -29,7 +29,6 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D USE_CC310_HW_CRYPTO=1 -D PIN_VEXT_EN=26 ; Vext is connected to VDD which is also connected to TFT & GPS -D PIN_VEXT_EN_ACTIVE=HIGH -D PIN_GPS_RX=25 diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index ea53b2dc..20a8a548 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -13,7 +13,6 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX with SKY66122 FEM - -D USE_CC310_HW_CRYPTO=1 build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak3401> + diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 58055ab4..31b507b4 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -22,7 +22,6 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D USE_CC310_HW_CRYPTO=1 -D ENV_INCLUDE_RAK12035=1 -UENV_INCLUDE_BME680 -D ENV_INCLUDE_BME680_BSEC=1 diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index d5e3bb53..43a3d93f 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -18,7 +18,6 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D RF_SWITCH_TABLE -D RX_BOOSTED_GAIN=true - -D USE_CC310_HW_CRYPTO=1 -D P_LORA_BUSY=7 ; P0.7 -D P_LORA_SCLK=11 ; P0.11 -D P_LORA_NSS=12 ; P0.12 From 78723d25654f7955adff59b434ccbe3bffc37967 Mon Sep 17 00:00:00 2001 From: Mike Damon Date: Thu, 30 Jul 2026 10:58:40 -0400 Subject: [PATCH 07/10] LR1110: fix startReceive() passing an IRQ bit as the RX timeout CustomLR1110::startReceive() passed RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED (1<<4 = 16) as RadioLib's first argument, which is the RX *timeout*, not an IRQ mask. At the LR11x0's 30.52us tick that armed the receiver for ~488us, so it dropped out of RX before any packet could arrive and the node received nothing at all -- while transmitting normally. Symptoms on a SenseCAP T1000-E: tx_air_secs rising, rx_air_secs stuck at 0, recv_errors 0, and the noise floor pinned at the -120 clamp because getCurrentRSSI() never sampled a live receiver. Pass RADIOLIB_LR11X0_RX_TIMEOUT_INF (continuous RX), which is what LR11x0::startReceive() itself uses, keeping the PREAMBLE_DETECTED flag in the reported IRQ flags as intended. Introduced in ea5d7c8b ("LR1110: add PREAMBLE_DETECTED to reported irq flags"). Verified on two T1000-E units: with only the repeater fixed it began receiving (last_rssi -29, SNR 17.0) while the unfixed companion stayed deaf; fixing both brought up the link in each direction. --- src/helpers/radiolib/CustomLR1110.h | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index c481cf5e..56a9aa6e 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -36,8 +36,15 @@ class CustomLR1110 : public LR1110 { bool getRxBoostedGainMode() const { return _rx_boosted; } int16_t startReceive() override { - // include the PREAMBLE_DETECTED irq bit in reported flags - return LR1110::startReceive(RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); + // include the PREAMBLE_DETECTED irq bit in reported flags. + // + // NOTE: the first argument is the RX *timeout*, not an IRQ mask. Passing + // RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED (1<<4 = 16) here armed the receiver + // with a 16-tick timeout -- at the LR11x0's 30.52us tick that is a ~488us + // receive window, so the radio dropped out of RX before any packet could + // arrive and the node never received anything. It must stay RX_TIMEOUT_INF + // (continuous RX), which is what LR11x0::startReceive() itself passes. + return LR1110::startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); } bool isReceiving() { From ae1b610a9405793142b3be3c76c348da7cb44797 Mon Sep 17 00:00:00 2001 From: me Date: Sat, 1 Aug 2026 22:20:20 -0700 Subject: [PATCH 08/10] fix: M5Stack Unit C6L merged.bin fails to boot (flash_mode qio->dio) Board manifest for esp32-c6-devkitm-1 defaults build.flash_mode to qio, which this module's flash chip does not support -- causes a boot crash-loop (repeated USB-Serial-JTAG reconnects) on real hardware. Override with board_build.flash_mode = dio in the common M5Stack_Unit_C6L section so it applies to all envs (ble/usb/repeater/room_server). --- variants/m5stack_unit_c6l/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index 190add04..ea4d852d 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -2,6 +2,7 @@ extends = esp32c6_base board = esp32-c6-devkitm-1 board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +board_build.flash_mode = dio ; board manifest defaults to qio, which causes a boot crash-loop on this module build_flags = ${esp32c6_base.build_flags} ${sensor_base.build_flags} From 5d940a1dc9347c7451e1e07c059b58f6cc3cad9c Mon Sep 17 00:00:00 2001 From: Huw Duddy <37787853+oltaco@users.noreply.github.com> Date: Mon, 3 Aug 2026 22:57:34 +1000 Subject: [PATCH 09/10] Clean up comments in startReceive method Removed unnecessary comments regarding RX timeout and IRQ mask. --- src/helpers/radiolib/CustomLR1110.h | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 56a9aa6e..75674a1d 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -37,13 +37,6 @@ class CustomLR1110 : public LR1110 { int16_t startReceive() override { // include the PREAMBLE_DETECTED irq bit in reported flags. - // - // NOTE: the first argument is the RX *timeout*, not an IRQ mask. Passing - // RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED (1<<4 = 16) here armed the receiver - // with a 16-tick timeout -- at the LR11x0's 30.52us tick that is a ~488us - // receive window, so the radio dropped out of RX before any packet could - // arrive and the node never received anything. It must stay RX_TIMEOUT_INF - // (continuous RX), which is what LR11x0::startReceive() itself passes. return LR1110::startReceive(RADIOLIB_LR11X0_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0); } @@ -99,4 +92,4 @@ class CustomLR1110 : public LR1110 { } uint8_t getSpreadingFactor() const { return spreadingFactor; } -}; \ No newline at end of file +}; From 0fd11ed223b5095aed83482c170637b040af2c29 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 4 Aug 2026 01:00:31 +1000 Subject: [PATCH 10/10] * bounds check added for anon_req reply_path_len --- examples/simple_repeater/MyMesh.cpp | 28 ++++++++++++---------------- examples/simple_repeater/MyMesh.h | 3 +-- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 09f74cbe..c7e651a1 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -147,11 +147,10 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data & 63; - reply_path_hash_size = (*data >> 6) + 1; - data++; + reply_path_len = *data++; + if (!mesh::Packet::isValidPathLen(reply_path_len)) return 0; // reject - bad encoding - memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + mesh::Packet::writePath(reply_path, data, reply_path_len); // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag @@ -166,11 +165,10 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data & 63; - reply_path_hash_size = (*data >> 6) + 1; - data++; + reply_path_len = *data++; + if (!mesh::Packet::isValidPathLen(reply_path_len)) return 0; // reject - bad encoding - memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + mesh::Packet::writePath(reply_path, data, reply_path_len); // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag @@ -186,11 +184,10 @@ uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data & 63; - reply_path_hash_size = (*data >> 6) + 1; - data++; + reply_path_len = *data++; + if (!mesh::Packet::isValidPathLen(reply_path_len)) return 0; // reject - bad encoding - memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + mesh::Packet::writePath(reply_path, data, reply_path_len); // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag @@ -574,7 +571,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; - reply_path_len = -1; + reply_path_len = 0xFF; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { @@ -594,13 +591,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); - } else if (reply_path_len < 0) { + } else if (reply_path_len == 0xFF) { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); if (reply) sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - uint8_t path_len = ((reply_path_hash_size - 1) << 6) | (reply_path_len & 63); - if (reply) sendDirect(reply, reply_path, path_len, SERVER_RESPONSE_DELAY); + if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); } } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index aa7d30b0..19022b77 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -92,8 +92,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; uint8_t reply_path[MAX_PATH_SIZE]; - int8_t reply_path_len; - uint8_t reply_path_hash_size; + uint8_t reply_path_len; TransportKeyStore key_store; RegionMap region_map, temp_map; RegionEntry* load_stack[8];