Audit M1: verify the signed log head against a pinned issuer key

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>
This commit is contained in:
Paul Hitt
2026-08-29 08:57:44 -04:00
co-authored by Claude Opus 4.8
parent e435cd710d
commit b0c5a108b2
5 changed files with 120 additions and 1 deletions
@@ -0,0 +1,46 @@
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")
}
}