The engine had no head-signature function at all and Trust.head() threw the signature away, so a fork was an accusation rather than evidence. KeyLog.verifyHead now checks the same string the relay signs, the issuer key is pinned in the vault on first sight, and a head that fails under the pin raises the standing fork alarm. Four tests pin the string and the refusals — including a head signed by a different key, which is the substitution the pin exists to stop. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
47 lines
1.8 KiB
Kotlin
47 lines
1.8 KiB
Kotlin
package io.flamenet.e2e
|
|
|
|
import kotlin.test.Test
|
|
import kotlin.test.assertFalse
|
|
import kotlin.test.assertTrue
|
|
|
|
/**
|
|
* Audit M1: a log head is only evidence if its signature verifies under the
|
|
* PINNED issuer key. These pin the exact string the relay signs
|
|
* (`fnkt-v1|<size>|<root>`) so the two implementations cannot drift apart —
|
|
* a mismatch there would silently turn every head into "unverifiable".
|
|
*/
|
|
class KeyLogHeadSignatureTest {
|
|
|
|
private val issuer = Ed25519.generate()
|
|
|
|
private fun sign(size: Int, root: String): ByteArray =
|
|
Ed25519.sign(issuer.seed, "fnkt-v1|$size|$root".toByteArray(Charsets.UTF_8))
|
|
|
|
@Test fun `a genuine head verifies`() {
|
|
val root = Bytes.b64(ByteArray(32) { 7 })
|
|
assertTrue(KeyLog.verifyHead(12, root, sign(12, root), issuer.publicKey))
|
|
}
|
|
|
|
@Test fun `a head signed for a different size is refused`() {
|
|
val root = Bytes.b64(ByteArray(32) { 7 })
|
|
assertFalse(KeyLog.verifyHead(13, root, sign(12, root), issuer.publicKey),
|
|
"a relay must not be able to replay a signature onto another size")
|
|
}
|
|
|
|
@Test fun `a head signed for a different root is refused`() {
|
|
val real = Bytes.b64(ByteArray(32) { 7 })
|
|
val forged = Bytes.b64(ByteArray(32) { 9 })
|
|
assertFalse(KeyLog.verifyHead(12, forged, sign(12, real), issuer.publicKey),
|
|
"swapping the root is exactly the fork this signature exists to catch")
|
|
}
|
|
|
|
@Test fun `a head signed by another key is refused`() {
|
|
val other = Ed25519.generate()
|
|
val root = Bytes.b64(ByteArray(32) { 7 })
|
|
val bySomeoneElse = Ed25519.sign(
|
|
other.seed, "fnkt-v1|12|$root".toByteArray(Charsets.UTF_8))
|
|
assertFalse(KeyLog.verifyHead(12, root, bySomeoneElse, issuer.publicKey),
|
|
"the PIN is what decides; a freshly served key must not substitute")
|
|
}
|
|
}
|