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>
82 lines
3.3 KiB
Kotlin
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.
|
|
}
|