This is not a formal audit, but I believe it is enough to support the OP’s own warning.
When dice/coin/directional input is confirmed, the code hashes user input like this:
for (uint8_t i = 0; i < stringSize; i++)
if (inputString[i] == -1) break;
else data[i] = usedCharSet[inputString[i]];
setSeedFromHashedData(data, stringSize); // bug: should be actual length, not max buffer size
stringSize is the maximum (100 / 255 / 128), not the number of rolls entered. Unfilled bytes are zero-initialized, so the seed is:
SHA256(actual_rolls || zero_padding)The UI entropy estimate uses the real input length, but the hash includes padding. That mismatch is a real bug: seeds depend on mode-specific padding, and users may believe they are hashing only what they typed.
The fix is: Track
actualLen and pass that to
setSeedFromHashedData().
Also, Dogecoin derivation is non-standard (crypto_functions.cpp)
Bitcoin/ETH/Nostr:
mnemonic_to_seed(mnemonic_from_data(seedBits, seedSize), ...);
hdnode_from_seed(masterNodeSeed, 64, ...);
Dogecoin:
hdnode_from_seed(seedBits, seedSize, ...); // raw 32-byte hash, no BIP-39 PBKDF2
Default path is m/0'/3'/0', not BIP-44 m/44'/3'/0'. A Doge address from this tool will not match a typical BIP-39 wallet importing the same mnemonic. That is a fund-loss risk if users assume standard compatibility.
Also, ChaCha DRBG used without initialization (default_rng.cpp)
Vanity address search seeds a ChaCha DRBG via:
chacha_drbg_reseed(&chachaDrbgContext, seed, seedSize, nullptr, 0);
chacha_drbg_init() is never called. CHACHA_DRBG_CTX is not zeroed in the constructor, so the first chacha_drbg_update() runs against an uninitialized ChaCha state. Vanity grinding RNG is therefore not cleanly seeded from user entropy alone.
Suggested fix is, Zero the context in the constructor and call chacha_drbg_init() before first use.
Moreover, clearSeed() does not wipe mnemonics (context_update_functions.cpp). Returning to the main menu calls clearSeed(), which only updates ContextUpdate::SEED. The contextData.mnemonic string (up to 24 BIP-39 or 25 Monero words) stays in memory with no secure wipe. For a device marketed on minimal secret retention, that is a meaningful gap.
Also, Vanity search silently replaces the user’s seed (vanity_input_page.cpp). After a vanity match, onForward() overwrites the session seed with the brute-forced seed.
updateContextData(ContextUpdate::SEED | ContextUpdate::SEED_SIZE, generatedData.seedData);
The original dice/coin entropy is gone. If the user does not realize vanity replaces the seed, they may believe they still have an entropy-backed wallet when they do not.
Also, there is a Unbounded encryption allocation (encryption_page.cpp).
inputDataSize is a uint32_t with no upper cap. Pressing UP keeps increasing it until malloc(inputDataSize) fails or exhausts N64 RAM (~4 MB). That is a practical DoS on real hardware, even if not remotely exploitable.
Important Note: I played with my AI Agent and this post is designed by AI.