Derive wallet addresses and sign with them (Android) #72

Merged
paulhitt merged 1 commits from feat/wallet-keys-and-address into main 2026-09-04 20:49:36 +00:00
1 Commits
Author SHA1 Message Date
Paul HittandClaude Opus 5 2e8c6776e2 Derive wallet addresses and sign with them (Android)
`HdAccount` turns a recovery phrase into the BIP-44 path every other Solana
wallet uses, the Base58 address somebody can be paid at, and the key that signs
for it.

🔴 The property everything else rests on is that the signing key OWNS the
address that was displayed — same path, same curve, same code. If those ever
disagreed the wallet would sign with a key that does not own the address it
showed. The transaction being rejected by the network is the GOOD outcome; the
bad one is signing for an address somebody else controls. Tested across five
indices, and separately by signing a message and verifying it under the address.

🔴 Android can sign, and iOS's constraint does not apply. AccountKdf notes that
a raw Ed25519 seed needs EdECPrivateKeySpec, which Android exposes only from
API 33 — but the engine carries its own RFC 8032, so this works on any
supported device. The wallet is not blocked on a platform floor.

🔴 A fresh address per receive, by default rather than on request. Reusing one
lets anybody watching a public chain group every payment somebody ever received
— the single linkability a wallet can actually prevent, because the chain will
not forget.

⚠️ `looksLikeAddress` says WELL FORMED, never CORRECT, and the difference is
money. A Solana address carries no checksum, so one mistyped character that
still decodes to 32 bytes is a perfectly valid address nobody holds the key
for. Bitcoin's Base58Check would catch it; this cannot, and neither can the
network. There is a passing test that goes and FINDS such a near-miss, so the
limit is recorded rather than implied — and it fails if the format ever gains a
checksum, which is the point at which the warnings around typed addresses can be
revisited.

⚠️ A test bug found by the test failing rather than passing: `Ed25519.verify`
takes (publicKey, signature, message) and I had passed (publicKey, message,
signature). All three are ByteArray, so it compiled perfectly. Noted in the file
— it is a decent argument for asserting the signature verifies at all rather
than trusting that a key which derives must be a key that signs.

12 tests. App builds, every JVM suite green.

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