Files
flamenet-messenger-android/engine/src/test/kotlin/io/flamenet/e2e/AccountKdfTest.kt
T
Paul HittandClaude Fable 5.1 2fa430f21c API-style comments and ktlint formatting
Every comment is reduced to its first sentence plus @param, @return
and @throws tags. Comment lines: 20040 to 8526. ktlint 1.8 applied
with its default (official Kotlin) style across all modules; 262 files
reformatted. The 264 remaining ktlint findings are naming and filename
rules, which are code changes, and are left alone.

Comments and formatting only. 1208 tests pass, counted from the XML.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 20:02:07 -04:00

82 lines
3.3 KiB
Kotlin

package io.flamenet.e2e
import org.json.JSONObject
import java.io.File
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
/**
* The Kotlin account KDF against the vectors pinned from the enclave client.
*/
class AccountKdfTest {
private val vectors: JSONObject by lazy {
val candidates =
listOfNotNull(
System.getenv("FLAMENET_ACCOUNT_VECTORS"),
"../../engine/test/vectors/account-kdf-v1.json",
"../../../flamenet-e2e/test/vectors/account-kdf-v1.json",
"../../flamenet-e2e/test/vectors/account-kdf-v1.json",
).map { File(it) }
val file = candidates.firstOrNull { it.exists() }
// Fails rather than skips: a vector suite that quietly stops checking
// is worth less than none, because it still reports green.
assertTrue(
file != null,
"account KDF vectors not found. Looked in: " +
candidates.joinToString(", ") { it.absolutePath },
)
JSONObject(file!!.readText())
}
private fun hex(bytes: ByteArray) = bytes.joinToString("") { "%02x".format(it) }
private fun unhex(s: String) = ByteArray(s.length / 2) { s.substring(it * 2, it * 2 + 2).toInt(16).toByte() }
@Test
fun `derives exactly the published bytes for every case`() {
val kdf = vectors.getJSONObject("kdf")
val params = AccountKdf.Params.withId(kdf.getInt("id"))
assertEquals(kdf.getInt("iterations"), params.iterations)
assertEquals(kdf.getInt("saltBytes"), params.saltBytes)
val cases = vectors.getJSONArray("cases")
assertTrue(cases.length() > 0, "the vector file carries no cases")
for (i in 0 until cases.length()) {
val c = cases.getJSONObject(i)
val out =
AccountKdf.derive(
passphrase = c.getString("passphrase"),
salt = unhex(c.getString("saltHex")),
context = c.getString("context"),
params = params,
)
assertEquals(c.getString("expectedHex"), hex(out), "case: ${c.getString("name")}")
assertEquals(c.getString("expectedB64"), Bytes.b64(out), "case: ${c.getString("name")}")
}
}
@Test
fun `the context is what separates one secret from another`() {
val salt = unhex("000102030405060708090a0b0c0d0e0f")
val pass = "correct horse battery staple"
val auth = AccountKdf.authSecret(pass, salt)
val identity = AccountKdf.identityKey(pass, salt)
val account = AccountKdf.accountKeySeed(pass, salt)
// Same passphrase, same salt, same stretch, three unrelated secrets.
assertFalse(auth.contentEquals(identity), "auth and identity keys collided")
assertFalse(auth.contentEquals(account), "auth and account keys collided")
assertFalse(identity.contentEquals(account), "identity and account keys collided")
assertEquals(32, account.size)
}
// The tie between this KDF and FNID.encode, that a correctly derived seed
// also produces the same ADDRESS on every client, belongs here too, and
// cannot be written yet: the Kotlin FNID lives on the multi-homing branch
// and is not on main.
}