Skip to content

native: the seed goes down as the word the firmware reads - #723

Merged
A13xB0 merged 1 commit into
mainfrom
native-seed-fits-the-firmware
Sep 8, 2026
Merged

A13xB0 merged 1 commit into
mainfrom
native-seed-fits-the-firmware

Conversation

@A13xB0

@A13xB0 A13xB0 commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Not the radio. On the native build radio_new_identity() is the host variant's own HostRNG(g_identity_seed), and the identity is a pure function of --seed; RadioLib's randomByte() never enters it (that path is the board variants', which #556 handled). Run by hand with four seeds, the same binary gives four keys, as the issue found.

What destroys the seed is the parser. bridge/main.cpp reads --seed with strtoul into a uint32_t. Where a long is 64 bits (Linux, macOS) the cast keeps the low word and every node differs. Where it is 32 bits, every Windows target, strtoul saturates at ULONG_MAX on overflow. The engine strides seeds by 0x9E3779B97F4A7C15, so every node past the first carries a seed above 2^32, and all of them arrived as 0xFFFFFFFF. Node 0's seed is the bare run seed, which fits: the one node apart. It reproduces across releases because both shipped tags carry the line, and across fixtures because it does not depend on the scenario.

The fix is on our side of the command line: the seed goes down as its low 32 bits, which is all the firmware ever read. No identity changes on Linux or macOS, and the published binaries are right without a rebuild. A test pins that every seed the engine can produce goes down as a number a 32-bit parser cannot saturate on, and that two nodes of one run still get two. MeshBench/meshcore-native#21 widens the parser to 64 bits for anyone running the binary by hand.

Not verified on Windows from here: this is a code-reading conclusion with the exact arithmetic signature the issue measured (index 0 survives, every other node collapses to one value). The Linux nodefs on this machine holds 375 distinct identities for a 375-node run, which is what the low-word behaviour predicts.

Found on the way, filed as #720 (every native node draws the same radio noise, so every fast_rng is seeded identically) and #721 (identities keyed on node index, so node 0 of every fixture is the same node).

Closes #712

🤖 Generated with Claude Code

https://claude.ai/code/session_01Q9HbD44EKWWTRYgxbFGxf6

57 of 58 native nodes on Windows booted with one identity, private key
included. The bridge parses --seed with strtoul into a uint32_t; where a
long is 64 bits the cast keeps the low word, and where it is 32 bits, every
Windows target, strtoul saturates at ULONG_MAX, so every node whose seed
was above 2^32 was seeded 0xFFFFFFFF. Node 0's seed is the run seed, which
fits, and was the one node apart.

The seed now goes down as its low 32 bits, which is all the firmware ever
read: no identity changes on Linux or macOS, and the published binaries
are right without a rebuild. MeshBench/meshcore-native#21 widens the
parser for anyone running the binary by hand.

Closes #712

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q9HbD44EKWWTRYgxbFGxf6
@A13xB0 A13xB0 added the fable approved Reviewed by Fable and found sound label Sep 8, 2026
@A13xB0
A13xB0 merged commit f48155f into main Sep 8, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fable approved Reviewed by Fable and found sound

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Every native node shares one MeshCore identity, so no companion ever learns a contact

2 participants