Audit H3: the safety number covers the post-quantum identity too

Mirror of iOS, byte-identical by construction: both keys folded under
one label when both sides have a PQ identity, classical pairs unchanged,
and the exact digits pinned against an independent implementation so the
two platforms cannot drift into showing different numbers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-08-29 09:02:09 -04:00
co-authored by Claude Opus 4.8
parent b0c5a108b2
commit cbe2292d3c
2 changed files with 82 additions and 0 deletions
@@ -0,0 +1,53 @@
package io.flamenet.e2e
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
/**
* Audit H3: a substituted post-quantum identity must change the digits two
* people read aloud. Cross-platform: these values are the same ones the iOS
* SafetyNumberPQTests use, so a drift between the two engines fails here.
*/
class SafetyNumberPQTest {
private val aliceIK = ByteArray(32) { 0x11 }
private val bobIK = ByteArray(32) { 0x22 }
private val alicePQ = ByteArray(32) { 0xA1.toByte() }
private val bobPQ = ByteArray(32) { 0xB2.toByte() }
@Test fun `both sides see the same number`() {
assertEquals(
SafetyNumber.compute(aliceIK, alicePQ, bobIK, bobPQ),
SafetyNumber.compute(bobIK, bobPQ, aliceIK, alicePQ))
}
@Test fun `swapping the PQ identity changes the number`() {
assertNotEquals(
SafetyNumber.compute(aliceIK, alicePQ, bobIK, bobPQ),
SafetyNumber.compute(aliceIK, alicePQ, bobIK, ByteArray(32) { 0xEE.toByte() }))
}
@Test fun `a classical pair keeps the classical number`() {
assertEquals(SafetyNumber.compute(aliceIK, bobIK),
SafetyNumber.compute(aliceIK, null, bobIK, bobPQ))
assertEquals(SafetyNumber.compute(aliceIK, bobIK),
SafetyNumber.compute(aliceIK, alicePQ, bobIK, null))
}
/**
* The exact digits, from an INDEPENDENT implementation of the rule — the
* same constant the iOS suite pins. This is what makes both platforms
* agree with the spec rather than merely with each other; a silent drift
* would show two people different numbers and read as an attack.
*/
@Test fun `the number matches the independently computed value`() {
assertEquals(
"526042701003577849302816285461391061377406450730104222477184",
SafetyNumber.compute(aliceIK, alicePQ, bobIK, bobPQ))
}
@Test fun `the PQ number differs from the classical one`() {
assertNotEquals(SafetyNumber.compute(aliceIK, bobIK),
SafetyNumber.compute(aliceIK, alicePQ, bobIK, bobPQ))
}
}