Android can show an address, and the Account screen looks like the app
Two gaps, one in each direction. Android could ENCODE an address and had no way to DERIVE one, so it could not show yours and nobody on another relay could be given it -- the cross-relay work was iOS-only in practice. The engine was missing `localAccountSalt` and a way to get both sign-in secrets from one stretch; asking for the account key separately re-runs 650,000 PBKDF2 iterations for a value already in hand. The salt is pinned against the JavaScript engine, which is normative -- a Kotlin test asserting what Kotlin computes would prove only that Kotlin is self-consistent, and the failure to guard against is three engines disagreeing, which shows up as one person having two addresses depending on which phone they used. The address is stored in the vault because it can only be derived at sign-in: the passphrase exists for that one moment and is never kept, so a restored session had nothing to recompute from. Caught by the empty state saying so on the emulator rather than showing a blank. A QR encoder, written rather than depended on. Android has no encoder in the platform, and this is one fully specified algorithm for one fixed job -- turning a short ASCII address into a bitmap. It is verified by DECODING: thirteen codes, every version from 1 to 10 at exact capacity, read back by CoreImage's detector, which shares no line with it. That found the bug that mattered. The symbol was structurally perfect -- finders, timing, alignment, data placement and mask all confirmed correct against a reference symbol -- and nothing could read it, because the fifteen format bits were written least-significant first. It fails identically at every version with no clue where. The Account screen was a flat stack of fields, buttons and paragraphs with dividers between them, so a security setting carried the same weight as a text box and "Delete account" the same as "Save display name". Cards now, matching the Contacts screen that already had them, with the destructive pair on their own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
28d7485c26
commit
e46d51430a
@@ -0,0 +1,47 @@
|
||||
package io.flamenet.e2e
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* The salt for an account on a relay that keeps its own accounts.
|
||||
*
|
||||
* 🔴 **Pinned against the JavaScript engine, which is normative** — the value
|
||||
* below was produced by `engine/src/fnid.js`'s `localAccountSalt`, not by this
|
||||
* implementation. A Kotlin test that asserts what Kotlin computes proves only
|
||||
* that Kotlin is self-consistent; the failure this guards against is the three
|
||||
* engines disagreeing, which surfaces as one person having two addresses
|
||||
* depending on which phone they signed in from.
|
||||
*
|
||||
* ⚠️ Changing the expected value here is never the fix. It is a wire constant:
|
||||
* a different salt is a different account key, and a different account key is
|
||||
* a different person to everyone who pinned the old one.
|
||||
*/
|
||||
class LocalAccountSaltTest {
|
||||
|
||||
@Test
|
||||
fun `matches the javascript engine`() {
|
||||
assertEquals(
|
||||
"331bfb32ce724a978dda86f671ac324a",
|
||||
AccountKdf.localAccountSalt("fnreviewalpha").toHex(),
|
||||
)
|
||||
}
|
||||
|
||||
/** Normalisation is part of the contract, not a convenience. */
|
||||
@Test
|
||||
fun `case and surrounding space do not change the salt`() {
|
||||
val plain = AccountKdf.localAccountSalt("fnreviewalpha").toHex()
|
||||
assertEquals(plain, AccountKdf.localAccountSalt("FNReviewAlpha").toHex())
|
||||
assertEquals(plain, AccountKdf.localAccountSalt(" fnreviewalpha ").toHex())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `it is the length the KDF params ask for`() {
|
||||
assertEquals(
|
||||
AccountKdf.Params.PBKDF2_V1.saltBytes,
|
||||
AccountKdf.localAccountSalt("someone").size,
|
||||
)
|
||||
}
|
||||
|
||||
private fun ByteArray.toHex() = joinToString("") { "%02x".format(it) }
|
||||
}
|
||||
Reference in New Issue
Block a user