package crypto import ( "bytes" "testing" ) // gnarlTestSecret creates a Hamadryad secret from a seed string for testing. func gnarlTestSecret(seed string) Hamadryad { var secret Hamadryad mid := GMid([]byte(seed)) copy(secret[:], mid[:]) return secret } func TestGnarlHeaderLen(t *testing.T) { if GnarlHeaderLen != 64 { t.Fatalf("GnarlHeaderLen = %d, want 64", GnarlHeaderLen) } if GnarlNonceLen != 10 { t.Fatalf("GnarlNonceLen = %d, want 10", GnarlNonceLen) } } func TestGnarlSealOpenRoundTrip(t *testing.T) { var secret Hamadryad h := GHash([]byte("test-shared-secret")) copy(secret[:], h[:]) identity := GMid([]byte("sender-spore-fingerprint")) nonce := GnarlNonceFromCounter(0, 1) plaintext := []byte("hello from the Bethe lattice") pkt := GnarlSeal(secret, identity, nonce, plaintext) if pkt.Nonce != nonce { t.Fatal("nonce mismatch") } if pkt.Identity != identity { t.Fatal("identity mismatch") } if len(pkt.Ciphertext) != len(plaintext) { t.Fatalf("ciphertext len = %d, want %d", len(pkt.Ciphertext), len(plaintext)) } if bytes.Equal(pkt.Ciphertext, plaintext) { t.Fatal("ciphertext == plaintext") } got, err := GnarlOpen(secret, pkt) if err != nil { t.Fatalf("GnarlOpen: %v", err) } if !bytes.Equal(got, plaintext) { t.Fatalf("decrypted = %q, want %q", got, plaintext) } } func TestGnarlSealOpenEmptyPlaintext(t *testing.T) { secret := gnarlTestSecret("empty-test") identity := GMid([]byte("id")) nonce := GnarlNonceFromCounter(0, 0) pkt := GnarlSeal(secret, identity, nonce, []byte{}) if len(pkt.Ciphertext) != 0 { t.Fatalf("ciphertext len = %d for empty plaintext", len(pkt.Ciphertext)) } got, err := GnarlOpen(secret, pkt) if err != nil { t.Fatalf("GnarlOpen empty: %v", err) } if len(got) != 0 { t.Fatalf("decrypted len = %d, want 0", len(got)) } } func TestGnarlSealOpenLargePayload(t *testing.T) { secret := gnarlTestSecret("large") identity := GMid([]byte("sender")) nonce := GnarlNonceFromCounter(1, 42) plaintext := make([]byte, 1400) for i := range plaintext { plaintext[i] = byte(i * 7) } pkt := GnarlSeal(secret, identity, nonce, plaintext) got, err := GnarlOpen(secret, pkt) if err != nil { t.Fatal(err) } if !bytes.Equal(got, plaintext) { t.Fatal("large payload round-trip failed") } } func TestGnarlMarshalUnmarshalRoundTrip(t *testing.T) { secret := gnarlTestSecret("marshal-test") identity := GMid([]byte("me")) nonce := GnarlNonceFromCounter(0, 99) plaintext := []byte("wire format test") pkt := GnarlSeal(secret, identity, nonce, plaintext) wire := MarshalGnarlPacket(pkt) if len(wire) != GnarlHeaderLen+len(plaintext) { t.Fatalf("wire len = %d, want %d", len(wire), GnarlHeaderLen+len(plaintext)) } pkt2, err := UnmarshalGnarlPacket(wire) if err != nil { t.Fatal(err) } if pkt2.Nonce != pkt.Nonce { t.Fatal("nonce mismatch after unmarshal") } if pkt2.Identity != pkt.Identity { t.Fatal("identity mismatch after unmarshal") } if pkt2.AuthTag != pkt.AuthTag { t.Fatal("auth tag mismatch after unmarshal") } if !bytes.Equal(pkt2.Ciphertext, pkt.Ciphertext) { t.Fatal("ciphertext mismatch after unmarshal") } got, err := GnarlOpen(secret, pkt2) if err != nil { t.Fatal(err) } if !bytes.Equal(got, plaintext) { t.Fatal("decrypt after unmarshal failed") } } func TestGnarlOpenWrongSecret(t *testing.T) { secret1 := gnarlTestSecret("key-1") secret2 := gnarlTestSecret("key-2") identity := GMid([]byte("id")) nonce := GnarlNonceFromCounter(0, 1) pkt := GnarlSeal(secret1, identity, nonce, []byte("secret data")) _, err := GnarlOpen(secret2, pkt) if err == nil { t.Fatal("wrong secret accepted") } } func TestGnarlOpenTamperedCiphertext(t *testing.T) { secret := gnarlTestSecret("tamper-test") identity := GMid([]byte("id")) nonce := GnarlNonceFromCounter(0, 1) pkt := GnarlSeal(secret, identity, nonce, []byte("important data")) pkt.Ciphertext[0] ^= 0x01 _, err := GnarlOpen(secret, pkt) if err == nil { t.Fatal("tampered ciphertext accepted") } } func TestGnarlOpenTamperedIdentity(t *testing.T) { secret := gnarlTestSecret("id-tamper") identity := GMid([]byte("real-sender")) nonce := GnarlNonceFromCounter(0, 1) pkt := GnarlSeal(secret, identity, nonce, []byte("data")) pkt.Identity[0] ^= 0xFF _, err := GnarlOpen(secret, pkt) if err == nil { t.Fatal("tampered identity accepted") } } func TestGnarlOpenTamperedNonce(t *testing.T) { secret := gnarlTestSecret("nonce-tamper") identity := GMid([]byte("sender")) nonce := GnarlNonceFromCounter(0, 1) pkt := GnarlSeal(secret, identity, nonce, []byte("data")) pkt.Nonce[0] ^= 0x01 _, err := GnarlOpen(secret, pkt) if err == nil { t.Fatal("tampered nonce accepted") } } func TestGnarlOpenNilPacket(t *testing.T) { var secret Hamadryad _, err := GnarlOpen(secret, nil) if err == nil { t.Fatal("nil packet accepted") } } func TestUnmarshalGnarlPacketTooShort(t *testing.T) { _, err := UnmarshalGnarlPacket(make([]byte, GnarlHeaderLen-1)) if err == nil { t.Fatal("short packet accepted") } } func TestUnmarshalGnarlPacketHeaderOnly(t *testing.T) { pkt, err := UnmarshalGnarlPacket(make([]byte, GnarlHeaderLen)) if err != nil { t.Fatalf("header-only unmarshal: %v", err) } if len(pkt.Ciphertext) != 0 { t.Fatalf("ciphertext len = %d for header-only", len(pkt.Ciphertext)) } } func TestGnarlNonceFromCounter(t *testing.T) { nonce := GnarlNonceFromCounter(0x1234, 0xDEADBEEF) if nonce[0] != 0xEF || nonce[1] != 0xBE || nonce[2] != 0xAD || nonce[3] != 0xDE { t.Fatalf("counter bytes wrong: %x", nonce[:8]) } if nonce[8] != 0x34 || nonce[9] != 0x12 { t.Fatalf("epoch bytes wrong: %x", nonce[8:10]) } } func TestGnarlSealDeterministic(t *testing.T) { secret := gnarlTestSecret("det") identity := GMid([]byte("id")) nonce := GnarlNonceFromCounter(0, 0) pkt1 := GnarlSeal(secret, identity, nonce, []byte("hello")) pkt2 := GnarlSeal(secret, identity, nonce, []byte("hello")) if !bytes.Equal(pkt1.Ciphertext, pkt2.Ciphertext) { t.Fatal("Seal not deterministic") } if pkt1.AuthTag != pkt2.AuthTag { t.Fatal("auth tag not deterministic") } } func TestGnarlSealDifferentNonces(t *testing.T) { secret := gnarlTestSecret("nonce-diff") identity := GMid([]byte("id")) n1 := GnarlNonceFromCounter(0, 1) n2 := GnarlNonceFromCounter(0, 2) plaintext := []byte("same message") pkt1 := GnarlSeal(secret, identity, n1, plaintext) pkt2 := GnarlSeal(secret, identity, n2, plaintext) if bytes.Equal(pkt1.Ciphertext, pkt2.Ciphertext) { t.Fatal("different nonces produced same ciphertext") } if pkt1.AuthTag == pkt2.AuthTag { t.Fatal("different nonces produced same auth tag") } }