Files
zap/handshake/wire_constants_test.go
zeekay ae739655bb feat: post-quantum identity + connection layer
- identity_pq.go: PQ identity primitives
- conn_pq.go: PQ-aware connection wrapping
- node_codec.go: protocol codec for PQ-aware nodes
- handshake/: PQ handshake protocol
- docs/: design notes
- coverage_*_test.go: comprehensive test suite
- go.mod: bump crypto, drop zap-proto/http (decomplected), add circl/accel
2026-06-01 13:47:19 -07:00

162 lines
4.8 KiB
Go

// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package handshake
import (
"bytes"
"testing"
)
// TestMagicIsZPQ1 pins the §6.0 magic prefix to its exact ASCII
// bytes. Any change here invalidates every deployed peer — this is
// what protects against accidentally renaming the wire identifier.
func TestMagicIsZPQ1(t *testing.T) {
want := []byte{'Z', 'P', 'Q', '1'}
if !bytes.Equal(Magic[:], want) {
t.Fatalf("Magic = %v, want %v", Magic, want)
}
if MagicLen != 4 {
t.Fatalf("MagicLen = %d, want 4", MagicLen)
}
}
// TestProtocolLabelString pins LblProtocol to "ZAP-PQ-v1". Bumping
// the protocol version (§18) requires changing this string AND the
// magic prefix — leaving them mismatched would let an old peer
// silently downgrade the new one.
func TestProtocolLabelString(t *testing.T) {
if string(LblProtocol) != "ZAP-PQ-v1" {
t.Fatalf("LblProtocol = %q, want %q", string(LblProtocol), "ZAP-PQ-v1")
}
}
// TestSignContextString pins SignCtx to "lux-zap-pq-v1". This is the
// FIPS 204 ctx string baked into every AUTH signature; changing it
// would invalidate every cached identity and break cross-protocol
// replay resistance.
func TestSignContextString(t *testing.T) {
if string(SignCtx) != "lux-zap-pq-v1" {
t.Fatalf("SignCtx = %q, want %q", string(SignCtx), "lux-zap-pq-v1")
}
}
// TestFrameTypeByteMapping pins each §6 frame type to its wire byte
// value. Renumbering would break every running peer.
func TestFrameTypeByteMapping(t *testing.T) {
cases := []struct {
t FrameType
want byte
}{
{FrameHello, 0x01},
{FrameKEMInit, 0x02},
{FrameKEMReply, 0x03},
{FrameAuth, 0x04},
{FrameData, 0x05},
{FrameRekey, 0x06},
{FrameAlert, 0x07},
{FrameHelloPSK, 0x08},
}
for _, c := range cases {
if byte(c.t) != c.want {
t.Errorf("FrameType %d = 0x%02x, want 0x%02x", c.t, byte(c.t), c.want)
}
}
}
// TestProfileConstantsDistinct ensures the chain security profiles
// are unique values — a collision would conflate refusal semantics.
func TestProfileConstantsDistinct(t *testing.T) {
seen := make(map[Profile]string)
cases := []struct {
p Profile
name string
}{
{ProfileStrictPQ, "StrictPQ"},
{ProfilePermissive, "Permissive"},
{ProfileFIPS, "FIPS"},
}
for _, c := range cases {
if other, dup := seen[c.p]; dup {
t.Errorf("Profile %s collides with %s at value %d", c.name, other, c.p)
}
seen[c.p] = c.name
}
}
// TestPQModeConstantsDistinct: every PQMode value MUST map to a
// distinct wire byte.
func TestPQModeConstantsDistinct(t *testing.T) {
seen := make(map[PQMode]string)
cases := []struct {
m PQMode
name string
}{
{PQModeClassicalPermitted, "ClassicalPermitted"},
{PQModePQRequired, "PQRequired"},
{PQModePQOnly, "PQOnly"},
}
for _, c := range cases {
if other, dup := seen[c.m]; dup {
t.Errorf("PQMode %s collides with %s at value %d", c.name, other, c.m)
}
seen[c.m] = c.name
}
}
// TestCipherSuiteRegistryByteMapping pins the §3.2 reserved byte
// allocations.
func TestCipherSuiteRegistryByteMapping(t *testing.T) {
if byte(SuiteX25519MLKEM) != 0x01 {
t.Errorf("SuiteX25519MLKEM = 0x%02x, want 0x01", byte(SuiteX25519MLKEM))
}
if byte(SuiteReservedLo) != 0x00 {
t.Errorf("SuiteReservedLo = 0x%02x, want 0x00", byte(SuiteReservedLo))
}
if byte(SuiteReservedHi) != 0xFF {
t.Errorf("SuiteReservedHi = 0x%02x, want 0xFF", byte(SuiteReservedHi))
}
}
// TestAuthRoleByteMapping: the §6.4 role bytes are ASCII 'I' / 'R'
// per spec.
func TestAuthRoleByteMapping(t *testing.T) {
if byte(RoleInitiator) != 'I' {
t.Errorf("RoleInitiator = 0x%02x, want 0x49 ('I')", byte(RoleInitiator))
}
if byte(RoleResponder) != 'R' {
t.Errorf("RoleResponder = 0x%02x, want 0x52 ('R')", byte(RoleResponder))
}
}
// TestKeyAndNonceSizes pins the lengths used by AES-256-GCM. A drift
// here would break interop with peers built against the spec.
func TestKeyAndNonceSizes(t *testing.T) {
if AEADKeyLen != 32 {
t.Fatalf("AEADKeyLen = %d, want 32", AEADKeyLen)
}
if AEADNonceLen != 12 {
t.Fatalf("AEADNonceLen = %d, want 12", AEADNonceLen)
}
if AEADTagLen != 16 {
t.Fatalf("AEADTagLen = %d, want 16", AEADTagLen)
}
if NonceSaltLen+NonceCtrLen != AEADNonceLen {
t.Fatalf("salt + ctr (%d) must equal AEAD nonce length (%d)", NonceSaltLen+NonceCtrLen, AEADNonceLen)
}
}
// TestSpecPubKeyAndSigLengths pins ML-KEM / ML-DSA / X25519 sizes
// against the FIPS 203/204 wire encodings.
func TestSpecPubKeyAndSigLengths(t *testing.T) {
if X25519PubLen != 32 || X25519SecLen != 32 || X25519SharedLen != 32 {
t.Fatal("X25519 sizes drifted")
}
if MLKEM768PubLen != 1184 || MLKEM768CTLen != 1088 || MLKEM768SharedLen != 32 {
t.Fatal("ML-KEM-768 sizes drifted")
}
if MLDSA65PubLen != 1952 || MLDSA65SigLen != 3309 {
t.Fatal("ML-DSA-65 sizes drifted")
}
}