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. }