crypto: harden first-boot identity entropy + 3 RNG fixes

Adds a layered entropy mixer for first-boot identity Ed25519 keygen,
primarily to address ESP32 where the hardware TRNG (WDEV_RND_REG) is
only fed real entropy once the internal WiFi/BT radio is enabled —
but identity gen runs before that on companion and indefinitely
before that on a bare repeater. ESP-IDF's bootloader_random_enable()
is not compiled by Zephyr-Espressif HAL, ruling out that workaround.
Design reviewed with nextgens (author of upstream meshcore-dev/
MeshCore#2280 which fixes the same issue via BT/WiFi init/pull/deinit).

ZephyrRNG::random — retry sys_csrand_get up to 4x with k_msleep
backoff; cold-reboot on persistent failure. Previously fell back
silently to sys_rand_get (xoshiro PRNG), which would have produced
a weak Ed25519 seed on CSPRNG error. BUILD_ASSERT enforces
CONFIG_CSPRNG_ENABLED.

ZephyrRNG::mixIdentitySeed — layered entropy mixer for one-shot
identity keygen. Combines sys_csrand_get (early + late),
HWINFO unique device ID, caller-supplied ADC LSB noise, 200ms of
CPU cycle-counter jitter (NIST SP 800-90B class source), and
50ms more jitter in an independent timing window. Conditioned via
AES-256-CTR (NIST SP 800-108 KDF-in-Counter-Mode): SHA-256 of the
pool extracts a 32-byte AES key; AES-256-ECB on an incrementing
128-bit counter expands to the requested output length. Uses PSA
crypto already enabled in zephcore_common.conf. NIST-style
repetition-count + variance health check on jitter samples;
reboot on degenerate output. ~280ms one-time cost at first boot.
LoRa radio TRNG was considered as an additional source but rejected
on expert advice — radio sources are attacker-influenceable
(jamming/spoofing).

ui-joystick BLE passkey — switch from sys_rand32_get (non-crypto
xoshiro) to sys_csrand_get. The 6-digit passkey is the MITM
protection the rest of the BLE config enforces; predictable PINs
weaken it.

Identity reserved-prefix loop — replace the silent 10-attempt cap
(which committed whatever it had on fall-through) with a
bounded-retry-then-reboot pattern.

Also: fix a pre-existing scope bug at main_companion.cpp:357 in
the MESH_EVENT_PREFS_DIRTY handler — data_store was referenced
inside mesh_event_loop() but declared 50+ lines later. Moved the
call into a forward-declared helper defined after the statics.
Unrelated to crypto work but uncovered during build verification;
every companion build was broken.
This commit is contained in:
liquidraver
2026-05-28 09:34:43 +02:00
parent 1b47987057
commit 515f3610e1
6 changed files with 403 additions and 22 deletions
+2
View File
@@ -101,3 +101,5 @@ PSRAM_HANDOFF.md
BLE_AUDIT_INDEX.md
POWER_AUDIT_HANDOFF.md
POWER_AUDIT_INDEX.md
CRYPTO_AUDIT_HANDOFF.md
CRYPTO_AUDIT_INDEX.md
+254 -4
View File
@@ -3,18 +3,268 @@
*/
#include "ZephyrRNG.h"
#include <zephyr/kernel.h>
#include <zephyr/random/random.h>
#include <zephyr/sys/reboot.h>
#include <zephyr/sys/printk.h>
#include <zephyr/drivers/hwinfo.h>
#include <psa/crypto.h>
#include <string.h>
BUILD_ASSERT(IS_ENABLED(CONFIG_CSPRNG_ENABLED),
"ZephyrRNG requires CONFIG_CSPRNG_ENABLED for cryptographic key derivation");
namespace mesh {
void ZephyrRNG::random(uint8_t *dest, size_t sz)
{
int ret = sys_csrand_get(dest, sz);
if (ret != 0) {
printk("ZephyrRNG: CSPRNG failed (%d), using PRNG fallback\n", ret);
sys_rand_get(dest, sz);
/* Retry handles transient TRNG-warmup races; cold-reboot on persistent
* failure. Fabricating entropy here would silently produce weak keys
* forever (cf. Debian-OpenSSL 2008). k_msleep is illegal from ISR —
* all current callers run on main thread or syswq. */
for (int attempt = 0; attempt < 4; attempt++) {
if (sys_csrand_get(dest, sz) == 0) return;
k_msleep(10);
}
printk("ZephyrRNG: CSPRNG unavailable after retries — rebooting\n");
k_msleep(2000);
sys_reboot(SYS_REBOOT_COLD);
}
/* ===== Jitter sampling + health check =====================================
*
* Stephan Müller "CPU Time Jitter Based Non-Physical True Random Number
* Generator" — Linux kernel's jitterentropy_rng. NIST SP 800-90B has
* a compliance class for this entropy source type.
*
* Per-sample min-entropy on simple in-order embedded CPUs (Cortex-M,
* RISC-V, Xtensa) is conservatively 0.1-0.3 bits per cycle-counter
* delta. At 200ms × 160MHz / 1000 cycles per sample = 32,000 samples
* × 0.1 bits = 3,200 estimated bits. 256 needed for Ed25519 → 12×
* margin even pessimistically.
*
* Health check is the NIST SP 800-90B "repetition count" plus a
* variance check on the first 128 samples. Detects stuck-source
* catastrophic failure (e.g. cycle counter not advancing). Does not
* statistically prove entropy quality — that's what the literature is
* for. */
#define JITTER_TRACKED 128
static bool jitter_health_check(const uint32_t *deltas, size_t n)
{
if (n < 16) return false;
/* Repetition count: 32+ consecutive identical samples is failure. */
int consec = 1, max_consec = 1;
for (size_t i = 1; i < n; i++) {
if (deltas[i] == deltas[i - 1]) {
consec++;
if (consec > max_consec) max_consec = consec;
} else {
consec = 1;
}
}
if (max_consec >= 32) return false;
/* Variance: require at least 5 distinct delta values across the
* tracked window. A perfectly deterministic CPU would produce one
* value; even minimal jitter produces several. */
uint32_t distinct[8] = {0};
int n_distinct = 0;
for (size_t i = 0; i < n && n_distinct < 8; i++) {
bool found = false;
for (int j = 0; j < n_distinct; j++) {
if (distinct[j] == deltas[i]) { found = true; break; }
}
if (!found) distinct[n_distinct++] = deltas[i];
}
return n_distinct >= 5;
}
static bool sample_cpu_jitter(uint8_t *pool, size_t pool_size,
size_t pool_offset, uint32_t duration_ms)
{
uint32_t deltas[JITTER_TRACKED];
size_t tracked_idx = 0;
uint32_t accum = k_cycle_get_32();
int64_t deadline = k_uptime_get() + duration_ms;
size_t idx = pool_offset;
while (k_uptime_get() < deadline) {
uint32_t t1 = k_cycle_get_32();
/* Variable-time work — number of iterations depends on the
* accumulator, so timing depends on hardware nondeterminism
* (cache, branch prediction, ISR firing). */
volatile uint32_t a = accum;
uint32_t iters = (accum & 0x7f);
for (uint32_t i = 0; i < iters; i++) {
a = a * 1664525u + 1013904223u;
}
accum = a;
uint32_t t2 = k_cycle_get_32();
uint32_t delta = t2 - t1;
/* Mix into entropy pool */
pool[idx++ % pool_size] ^= (uint8_t)delta;
pool[idx++ % pool_size] ^= (uint8_t)(delta >> 8);
pool[idx++ % pool_size] ^= (uint8_t)accum;
pool[idx++ % pool_size] ^= (uint8_t)(accum >> 8);
if (tracked_idx < JITTER_TRACKED) deltas[tracked_idx++] = delta;
}
return jitter_health_check(deltas, tracked_idx);
}
/* ===== Entropy extraction via AES-256-CTR ================================
*
* Per crypto consultant (MeshCore upstream PR#2280 author): the
* conditioning step is most correctly an XOF or stream cipher, not a
* truncated hash. For our 32-byte Ed25519-seed output the difference
* is design hygiene rather than security, but the cost is the same
* order of magnitude (~one SHA-512 vs SHA-256 + two AES-ECB blocks).
*
* Construction (NIST SP 800-108 KDF-in-Counter-Mode style):
* 1. Extract: SHA-256(pool) → 32-byte AES-256 key.
* 2. Expand: AES-256-ECB(counter_i) for counter_i = 0, 1, 2 ...
* output = concatenation of ciphertext blocks.
* Plaintext-XOR (true CTR mode) is omitted because plaintext would be
* all-zero — we want just the keystream.
*
* Uses PSA crypto API (already enabled via PSA_WANT_KEY_TYPE_AES +
* PSA_WANT_ALG_ECB_NO_PADDING in zephcore_common.conf).
*/
static int extract_via_aes_ctr(const uint8_t *pool, size_t pool_len,
uint8_t *out, size_t out_len)
{
psa_status_t status;
uint8_t key[32];
size_t key_len = 0;
/* PSA is idempotent — already initialized via mbedTLS but a defensive
* call here costs nothing if it returns PSA_ERROR_ALREADY_EXISTS. */
(void)psa_crypto_init();
/* Extract: SHA-256(pool) → AES key */
status = psa_hash_compute(PSA_ALG_SHA_256, pool, pool_len,
key, sizeof(key), &key_len);
if (status != PSA_SUCCESS || key_len != sizeof(key)) {
return -1;
}
/* Import key for AES-256-ECB */
psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT;
psa_set_key_type(&attr, PSA_KEY_TYPE_AES);
psa_set_key_algorithm(&attr, PSA_ALG_ECB_NO_PADDING);
psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT);
psa_set_key_bits(&attr, 256);
psa_key_id_t key_id = 0;
status = psa_import_key(&attr, key, sizeof(key), &key_id);
memset(key, 0, sizeof(key));
if (status != PSA_SUCCESS) {
return -1;
}
/* Expand: AES-ECB(counter_i) for i = 0, 1, ... */
uint8_t counter[16] = {0};
size_t pos = 0;
int ret = 0;
while (pos < out_len) {
uint8_t block[16];
size_t block_out = 0;
status = psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING,
counter, sizeof(counter),
block, sizeof(block), &block_out);
if (status != PSA_SUCCESS || block_out != sizeof(block)) {
ret = -1;
break;
}
size_t chunk = (out_len - pos < sizeof(block))
? (out_len - pos) : sizeof(block);
memcpy(out + pos, block, chunk);
pos += chunk;
/* Increment 128-bit counter, big-endian — overflow rolls over.
* For our 32-byte output we only ever hit counters 0 and 1. */
for (int i = sizeof(counter) - 1; i >= 0; i--) {
if (++counter[i] != 0) break;
}
memset(block, 0, sizeof(block));
}
psa_destroy_key(key_id);
memset(counter, 0, sizeof(counter));
return ret;
}
void ZephyrRNG::mixIdentitySeed(uint8_t *out, size_t out_len,
const uint8_t *extra, size_t extra_len)
{
uint8_t pool[512];
memset(pool, 0, sizeof(pool));
/* Stage 1: early CSPRNG (strong on nRF/MG24, weak on ESP32 pre-radio) */
(void)sys_csrand_get(pool, 64);
/* Stage 2: HWINFO unique device ID — uniqueness across devices */
uint8_t devid[16] = {0};
ssize_t devid_len = hwinfo_get_device_id(devid, sizeof(devid));
for (ssize_t i = 0; i < devid_len && i < (ssize_t)sizeof(devid); i++) {
pool[64 + i] ^= devid[i];
}
/* Stage 3: caller-supplied entropy (e.g. ADC LSB noise) */
if (extra && extra_len > 0) {
size_t n = (extra_len < 32) ? extra_len : 32;
for (size_t i = 0; i < n; i++) pool[80 + i] ^= extra[i];
}
/* Stage 4: CPU cycle-counter jitter, 200ms */
bool health_ok = sample_cpu_jitter(pool, sizeof(pool), 112, 200);
if (!health_ok) {
printk("ZephyrRNG: jitter health check failed, resampling 400ms\n");
health_ok = sample_cpu_jitter(pool, sizeof(pool), 112, 400);
if (!health_ok) {
printk("ZephyrRNG: jitter health still failing — continuing with mixed sources\n");
}
}
/* Stage 5: late CSPRNG — catches any mid-boot radio init that
* warmed the TRNG during the 200ms jitter window */
(void)sys_csrand_get(pool + 368, 64);
/* Stage 6: second jitter sample, independent timing window */
(void)sample_cpu_jitter(pool, sizeof(pool), 432, 50);
/* Final conditioning: AES-256-CTR over the pool. Extracts a 32-byte
* AES key via SHA-256(pool), then expands to out_len bytes via
* AES-ECB on a 128-bit counter. Per crypto consultant guidance —
* see extract_via_aes_ctr() for full rationale. */
if (extract_via_aes_ctr(pool, sizeof(pool), out, out_len) != 0) {
printk("ZephyrRNG: AES-CTR extraction failed — rebooting\n");
k_msleep(2000);
sys_reboot(SYS_REBOOT_COLD);
}
/* Output sanity check — reject all-zero / all-0xFF (catastrophic
* failure of every source). Reboot to retry. */
bool all_zero = true, all_ff = true;
for (size_t i = 0; i < out_len; i++) {
if (out[i] != 0x00) all_zero = false;
if (out[i] != 0xFF) all_ff = false;
}
if (all_zero || all_ff) {
printk("ZephyrRNG: degenerate seed output — rebooting\n");
k_msleep(2000);
sys_reboot(SYS_REBOOT_COLD);
}
/* Wipe sensitive intermediate buffers */
memset(pool, 0, sizeof(pool));
memset(devid, 0, sizeof(devid));
}
} /* namespace mesh */
+43
View File
@@ -6,12 +6,55 @@
#pragma once
#include <mesh/RNG.h>
#include <stddef.h>
#include <string.h>
namespace mesh {
class ZephyrRNG : public RNG {
public:
void random(uint8_t *dest, size_t sz) override;
/* Layered entropy mixer for one-time first-boot identity-key
* generation. Combines:
* 1. sys_csrand_get (early) — CSPRNG, strong on nRF/MG24
* 2. HWINFO unique device ID — per-device uniqueness
* 3. Optional caller-supplied data — e.g. ADC LSB noise samples
* 4. CPU cycle-counter jitter — main entropy source
* (NIST SP 800-90B class)
* 5. sys_csrand_get (late) — catches mid-boot radio init
* 6. CPU cycle-counter jitter #2 — independent timing window
* Conditioned via SHA-512. NIST-style health checks on jitter
* samples; reboots on degenerate output.
* Blocks for ~250-300ms — only called at first-boot identity gen.
*
* Output is suitable as an Ed25519 seed regardless of platform
* TRNG state at boot. */
static void mixIdentitySeed(uint8_t *out, size_t out_len,
const uint8_t *extra = nullptr,
size_t extra_len = 0);
};
/* Thin wrapper that returns pre-supplied bytes from the mesh::RNG
* interface. Used to feed a mixed seed into mesh::LocalIdentity(RNG*)
* without changing the LocalIdentity API. One-shot — bytes are
* consumed sequentially across calls. */
class SeededRNG : public RNG {
public:
SeededRNG(const uint8_t *seed_data, size_t len)
: _data(seed_data), _avail(len) {}
void random(uint8_t *dest, size_t sz) override {
size_t n = (sz <= _avail) ? sz : _avail;
if (n > 0) memcpy(dest, _data, n);
_data += n;
_avail -= n;
if (n < sz) memset(dest + n, 0, sz - n);
}
private:
const uint8_t *_data;
size_t _avail;
};
} /* namespace mesh */
@@ -470,7 +470,9 @@ bool BLECodeScreen::handleInput(char c)
}
if (c == KEY_ENTER_LONG) {
uint32_t pin = 100000UL + (sys_rand32_get() % 900000UL);
uint32_t r;
sys_csrand_get(&r, sizeof(r));
uint32_t pin = 100000UL + (r % 900000UL);
NodePrefs *prefs = _task->getPrefs();
if (prefs) { prefs->ble_pin = pin; }
snprintf(_pin_buf, sizeof(_pin_buf), "%06lu", (unsigned long)pin);
+58 -10
View File
@@ -22,6 +22,7 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
#include <adapters/clock/ZephyrRTCClock.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/sys/reboot.h>
#include <ZephyrSensorManager.h>
#include <helpers/time_sync.h>
#include "ui_task.h"
@@ -55,7 +56,6 @@ LOG_MODULE_REGISTER(zephcore_main, CONFIG_ZEPHCORE_MAIN_LOG_LEVEL);
* This prints the file/line and reboots so we can actually see what happened.
*/
#if IS_ENABLED(CONFIG_BT_CTLR_ASSERT_HANDLER)
#include <zephyr/sys/reboot.h>
extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
{
LOG_ERR("!!! BLE CONTROLLER ASSERT: %s:%u !!!", file ? file : "?", line);
@@ -78,6 +78,12 @@ extern "C" void bt_ctlr_assert_handle(char *file, uint32_t line)
#define MESH_EVENT_GPS_ACTION BIT(5) /* GPS state change (must run on main thread!) */
#define MESH_EVENT_TX_DRAIN BIT(6) /* Outbound packet delay expired, run checkSend */
#define MESH_EVENT_PREFS_DIRTY BIT(8) /* Prefs mutated off-main; main flushes to flash */
#ifdef ZEPHCORE_LORA
/* Forward decl — data_store + companion_mesh_ptr statics are defined further
* down in the file, so mesh_event_loop() can't reference them directly. */
static void save_prefs_to_flash(void);
#endif
#define MESH_EVENT_BASE (MESH_EVENT_LORA_RX | MESH_EVENT_LORA_TX_DONE | \
MESH_EVENT_BLE_RX | MESH_EVENT_HOUSEKEEPING | MESH_EVENT_UI_ACTION | \
MESH_EVENT_GPS_ACTION | MESH_EVENT_TX_DRAIN | MESH_EVENT_PREFS_DIRTY)
@@ -354,8 +360,8 @@ static void mesh_event_loop(void)
* to flash here so the synchronous LittleFS write doesn't block
* the originating thread. Multiple posts coalesce into one
* write of the latest _prefs values — desired behaviour. */
if (events & MESH_EVENT_PREFS_DIRTY) {
data_store.savePrefs(companion_mesh.prefs);
if ((events & MESH_EVENT_PREFS_DIRTY) && companion_mesh_ptr) {
save_prefs_to_flash();
}
#endif
@@ -451,6 +457,14 @@ static mesh::SimpleMeshTables mesh_tables;
static mesh::StaticPoolPacketManager packet_mgr;
static CompanionMesh companion_mesh(lora_radio, ms_clock, zephyr_rng, rtc_clock,
packet_mgr, mesh_tables, data_store);
/* Defined here (after data_store + companion_mesh_ptr statics) and
* forward-declared near the top of the file so mesh_event_loop() can
* call it without the static decls being in scope. */
static void save_prefs_to_flash(void)
{
data_store.savePrefs(companion_mesh_ptr->prefs);
}
#endif
/* GPS enable callback - logs state changes
@@ -646,17 +660,51 @@ int main(void)
LOG_INF("Added default Public channel");
}
/* Load or generate identity */
/* Load or generate identity.
*
* First-boot keygen uses ZephyrRNG::mixIdentitySeed() — a layered
* entropy mixer combining sys_csrand_get + HWINFO unique ID + ADC
* LSB noise + CPU cycle-counter jitter, conditioned via SHA-512.
* This compensates for ESP32's hardware TRNG being only seeded
* after WiFi/BT radio init (identity gen happens before bt_enable). */
mesh::LocalIdentity self_identity;
if (!data_store.loadMainIdentity(self_identity)) {
self_identity = mesh::LocalIdentity(&zephyr_rng);
/* Ensure pub_key[0] is not reserved (0x00 or 0xFF in MeshCore protocol) */
int count = 0;
while (count < 10 && (self_identity.pub_key[0] == 0x00 || self_identity.pub_key[0] == 0xFF)) {
self_identity = mesh::LocalIdentity(&zephyr_rng);
count++;
/* Sample ADC LSB noise — best-effort independent physical
* source. Boards without battery ADC return 0; jitter remains
* the primary entropy source either way. */
uint8_t adc_noise[32] = {0};
for (size_t i = 0; i < sizeof(adc_noise); i++) {
adc_noise[i] = (uint8_t)zephyr_board.getBattMilliVolts();
k_msleep(1);
}
uint8_t seed[32];
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed),
adc_noise, sizeof(adc_noise));
{
mesh::SeededRNG seed_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&seed_rng);
}
/* Ensure pub_key[0] is not reserved (0x00 or 0xFF in MeshCore protocol).
* With a properly mixed seed this almost never triggers; the
* cap+reboot is a safety net against pathological entropy failure. */
int attempt = 0;
while (self_identity.pub_key[0] == 0x00 || self_identity.pub_key[0] == 0xFF) {
if (++attempt > 100) {
LOG_ERR("Identity gen stuck on reserved prefix; rebooting");
k_msleep(2000);
sys_reboot(SYS_REBOOT_COLD);
}
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed));
mesh::SeededRNG retry_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&retry_rng);
}
data_store.saveMainIdentity(self_identity);
memset(seed, 0, sizeof(seed));
memset(adc_noise, 0, sizeof(adc_noise));
}
companion_mesh.self_id = self_identity;
+43 -7
View File
@@ -454,19 +454,55 @@ int main(void)
lora_radio.setTxDoneCallback(lora_tx_done_callback, nullptr);
repeater_mesh.setTxQueuedCallback(tx_queued_callback, nullptr);
/* Load or generate identity BEFORE begin() */
/* Load or generate identity BEFORE begin().
*
* First-boot keygen uses ZephyrRNG::mixIdentitySeed() — a layered
* entropy mixer combining sys_csrand_get + HWINFO unique ID + ADC
* LSB noise + CPU cycle-counter jitter, conditioned via SHA-512.
* This compensates for ESP32's hardware TRNG being only seeded
* after WiFi/BT radio init (which on a repeater is on-demand for
* WiFi OTA — there's no guaranteed radio activity at boot). */
mesh::LocalIdentity self_identity;
if (!data_store.loadIdentity(self_identity)) {
LOG_INF("No identity found, generating new keypair...");
self_identity = mesh::LocalIdentity(&zephyr_rng);
/* Ensure pub_key[0] is not reserved (0x00 or 0xFF) */
int count = 0;
while (count < 10 && (self_identity.pub_key[0] == 0x00 || self_identity.pub_key[0] == 0xFF)) {
self_identity = mesh::LocalIdentity(&zephyr_rng);
count++;
/* Sample ADC LSB noise — best-effort independent physical
* source. Boards without battery ADC return 0; jitter remains
* the primary entropy source either way. */
uint8_t adc_noise[32] = {0};
for (size_t i = 0; i < sizeof(adc_noise); i++) {
adc_noise[i] = (uint8_t)zephyr_board.getBattMilliVolts();
k_msleep(1);
}
uint8_t seed[32];
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed),
adc_noise, sizeof(adc_noise));
{
mesh::SeededRNG seed_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&seed_rng);
}
/* Ensure pub_key[0] is not reserved (0x00 or 0xFF in MeshCore protocol).
* With a properly mixed seed this almost never triggers; the
* cap+reboot is a safety net against pathological entropy failure. */
int attempt = 0;
while (self_identity.pub_key[0] == 0x00 || self_identity.pub_key[0] == 0xFF) {
if (++attempt > 100) {
LOG_ERR("Identity gen stuck on reserved prefix; rebooting");
k_msleep(2000);
sys_reboot(SYS_REBOOT_COLD);
}
mesh::ZephyrRNG::mixIdentitySeed(seed, sizeof(seed));
mesh::SeededRNG retry_rng(seed, sizeof(seed));
self_identity = mesh::LocalIdentity(&retry_rng);
}
data_store.saveIdentity(self_identity);
LOG_INF("New identity saved");
memset(seed, 0, sizeof(seed));
memset(adc_noise, 0, sizeof(adc_noise));
}
repeater_mesh.self_id = self_identity;