Files
flamenet-messenger-android/engine
Paul HittandClaude Opus 5 4ee0170dcc Wallet engine primitives in Kotlin: BIP-39, Base58, SLIP-0010
The foundation Files and Wallet rest on. PARITY.md was right that this is a
build rather than a port of a screen — these had no Kotlin counterpart at all.

🔴 Pinned to the STANDARDS, not to the Swift engine. BIP-39's own vectors,
SLIP-0010's own ed25519 vectors, and the published word-list SHA-256 — the same
things the Swift side is held to. Checking one implementation against the other
would only prove they agree, and two implementations agreeing on the wrong
answer is exactly how a wallet ends up empty: a phrase written down on an
iPhone has to restore on an Android handset, and on somebody else's software
years from now.

Every failure this file guards is SILENT in production. A wrong checksum bit, a
lost leading zero, a missed NFKD — none of them error. They derive a different,
perfectly valid, empty wallet, and the way somebody finds out is restoring
their phrase and seeing nothing there.

Two Kotlin-specific decisions, both money-relevant:

🔴 PBKDF2-HMAC-SHA512 is hand-written over `Mac` rather than taken from
`SecretKeyFactory("PBKDF2WithHmacSHA512")`. The JCA path takes a CharArray and
the provider decides how to encode it — AccountKdf already carries that caveat
for the account KDF. For a wallet the consequence is worse than a failed
sign-in: a phrase containing anything outside ASCII would stretch to different
bytes under a different provider and derive a different, empty, perfectly valid
wallet with no error anywhere. Taking the bytes ourselves removes the provider
from the decision. (It is also absent below API 26.)

🔴 Base58 is the digit loop, not `BigInteger`. The obvious Kotlin version is
`BigInteger(1, bytes).toString(58)` with a remapped alphabet, and it is wrong in
the one way that matters: BigInteger discards leading zeros, so a key beginning
0x00 loses a character and encodes to another key's address.

⚠️ One test was WRONG on the first run and the implementation was right. The
all-ones 24-word vector carried the seed from a different BIP-39 vector. It was
resolved by computing the value independently — PBKDF2-HMAC-SHA512, NFKD, 2048
rounds — rather than by adopting whatever the implementation produced, and the
file says so. A pinned value taken from the code under test pins the bug.

⚠️ The vendored word list is byte-identical to the iOS one and verified on every
load, not once at build: it ships inside the app and a check that only ran in CI
would not notice one that arrived corrupted.

23 tests. App builds, every JVM suite green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 14:12:49 -04:00
..