Files
zeekay 9e6a6cab8c refactor(hash,primitives): route SP 800-185 + Lagrange onto luxfi/mlwe v0.2.1
Byte-preserving de-dup: Corona's SP 800-185 transcript surface
(hash/sha3.go; deletes the local hash/sp800_185.go) and
ComputeLagrangeCoefficients (primitives/shamir.go) now consume the
shared github.com/luxfi/mlwe base — the single Module-LWE primitive
surface for the Lux stack (Corona + Pulsar).

Routed onto mlwe:
  - hash/sha3.go: cSHAKE256 / KMAC256 / TupleHash256 / EncodeString
    -> github.com/luxfi/mlwe/transcript. Corona retains ONLY its
    domain-separation tags; the byte encodings live in mlwe/transcript.
  - primitives/shamir.go: ComputeLagrangeCoefficients delegates the
    field arithmetic + interpolation to github.com/luxfi/mlwe/share
    (NewPrimeField + Lagrange at point 0); Corona keeps the adapter
    that lifts each scalar into the R_q constant-term ring.Poly.

Deliberately NOT routed (byte-incompatible / audited):
  - Lattigo 48-bit ring stays Lattigo (re-rolling audited Lattigo forbidden).
  - The seeded Shamir deal (ShamirSecretSharing) stays Corona-specific
    (its sampler is byte-incompatible with mlwe.Split).

Corona remains Module-LWE (Ringtail/Raccoon line).

Byte-parity gate: every Corona KAT oracle emits byte-identical output
old (Corona-local impls) == new (mlwe-routed). Go byte-parity is the
correctness gate; the C++ cross-runtime byte gate is a post-merge CI
confirmation (see merge commit CI-GATE note).
2026-06-28 13:02:56 -07:00

173 lines
6.1 KiB
Go

// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package hash
import (
"bytes"
"testing"
)
// TestSuiteIDs ensures the two suites declare distinct, stable IDs.
func TestSuiteIDs(t *testing.T) {
sha3 := NewCoronaSHA3()
bl3 := NewCoronaBLAKE3()
if sha3.ID() != "Corona-SHA3" {
t.Errorf("SHA3 suite ID: want Corona-SHA3, got %q", sha3.ID())
}
if bl3.ID() != "Corona-BLAKE3" {
t.Errorf("BLAKE3 suite ID: want Corona-BLAKE3, got %q", bl3.ID())
}
if sha3.ID() == bl3.ID() {
t.Error("SHA3 and BLAKE3 suites must declare distinct IDs")
}
if Default().ID() != DefaultID {
t.Errorf("Default ID: want %q got %q", DefaultID, Default().ID())
}
}
// TestDefaultIsSHA3 — production default must be SHA3.
func TestDefaultIsSHA3(t *testing.T) {
if Default().ID() != "Corona-SHA3" {
t.Fatalf("production default must be SHA3; got %q", Default().ID())
}
}
// TestResolveNil — nil resolves to default.
func TestResolveNil(t *testing.T) {
if Resolve(nil).ID() != DefaultID {
t.Errorf("Resolve(nil) must return the production default")
}
bl3 := NewCoronaBLAKE3()
if Resolve(bl3).ID() != bl3.ID() {
t.Errorf("Resolve(suite) must return that suite")
}
}
// TestSuitesProduceDifferentBytes — two suites with distinct IDs must
// produce distinct bytes for the same input.
func TestSuitesProduceDifferentBytes(t *testing.T) {
sha3 := NewCoronaSHA3()
bl3 := NewCoronaBLAKE3()
t1 := sha3.TranscriptHash([]byte("a"), []byte("b"))
t2 := bl3.TranscriptHash([]byte("a"), []byte("b"))
if t1 == t2 {
t.Errorf("TranscriptHash collision across SHA3/BLAKE3 — wrong domain separation")
}
if bytes.Equal(sha3.Hc([]byte("x")), bl3.Hc([]byte("x"))) {
t.Errorf("Hc collision across SHA3/BLAKE3")
}
if bytes.Equal(sha3.Hu([]byte("x"), 32), bl3.Hu([]byte("x"), 32)) {
t.Errorf("Hu collision across SHA3/BLAKE3")
}
if bytes.Equal(sha3.PRF([]byte("k"), []byte("m"), 32), bl3.PRF([]byte("k"), []byte("m"), 32)) {
t.Errorf("PRF collision across SHA3/BLAKE3")
}
if bytes.Equal(sha3.MAC([]byte("k"), []byte("m"), 32), bl3.MAC([]byte("k"), []byte("m"), 32)) {
t.Errorf("MAC collision across SHA3/BLAKE3")
}
}
// TestPRFAndMACDifferByCustomization — same suite, same key, same
// message, but PRF and MAC must produce distinct bytes.
func TestPRFAndMACDifferByCustomization(t *testing.T) {
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
key := []byte("00000000000000000000000000000000") // 32 bytes
msg := []byte("same-message")
prf := s.PRF(key, msg, 32)
mac := s.MAC(key, msg, 32)
if bytes.Equal(prf, mac) {
t.Errorf("%s: PRF and MAC must differ by customization tag", s.ID())
}
}
}
// TestHcAndHuDifferByCustomization — same suite, same input, different
// tags → different bytes.
func TestHcAndHuDifferByCustomization(t *testing.T) {
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
hc := s.Hc([]byte("transcript"))
hu := s.Hu([]byte("transcript"), 32)
if bytes.Equal(hc, hu) {
t.Errorf("%s: Hc and Hu must differ by tag", s.ID())
}
}
}
// TestTranscriptHashAvoidsConcatenationCollisions — TupleHash framing
// must reject two distinct lists whose naive concatenation is identical.
func TestTranscriptHashAvoidsConcatenationCollisions(t *testing.T) {
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
a := s.TranscriptHash([]byte("abc"), []byte(""))
b := s.TranscriptHash([]byte("a"), []byte("bc"))
if a == b {
t.Errorf("%s: TranscriptHash collided on different field splits — framing broken", s.ID())
}
c := s.TranscriptHash([]byte("ab"), []byte("c"))
if a == c || b == c {
t.Errorf("%s: TranscriptHash collided on different field splits", s.ID())
}
}
}
// TestPairwiseCanonicalOrdering — DerivePairwise must produce the same
// bytes for (i, j) and (j, i).
func TestPairwiseCanonicalOrdering(t *testing.T) {
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
kex := []byte("0123456789abcdef0123456789abcdef")
ab := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 7, 3, 2, 5, 32)
ba := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 7, 3, 5, 2, 32)
if !bytes.Equal(ab, ba) {
t.Errorf("%s: DerivePairwise must be symmetric in (i, j)", s.ID())
}
}
}
// TestPairwiseDistinctEras — different (era, generation) MUST yield
// different pairwise material.
func TestPairwiseDistinctEras(t *testing.T) {
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
kex := []byte("0123456789abcdef0123456789abcdef")
base := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 7, 3, 2, 5, 32)
era2 := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 8, 3, 2, 5, 32)
gen2 := s.DerivePairwise(kex, []byte("chain"), []byte("group"), 7, 4, 2, 5, 32)
ch2 := s.DerivePairwise(kex, []byte("CHAIN"), []byte("group"), 7, 3, 2, 5, 32)
if bytes.Equal(base, era2) {
t.Errorf("%s: era change did not affect pairwise output", s.ID())
}
if bytes.Equal(base, gen2) {
t.Errorf("%s: generation change did not affect pairwise output", s.ID())
}
if bytes.Equal(base, ch2) {
t.Errorf("%s: chain_id change did not affect pairwise output", s.ID())
}
}
}
// NIST SP 800-185 primitive vectors (left/right_encode, KMAC256,
// TupleHash256) are owned and tested by github.com/luxfi/mlwe/transcript
// (transcript_test.go: cSHAKE256 NIST Sample #3, the §2.3 encoders, and
// the KMAC/TupleHash spec-construction KATs). Corona no longer carries a
// parallel copy of those primitive tests — it tests only its own suite
// composition (tags, domain separation, determinism) below.
// TestSuiteDeterminism — same input, two calls, identical bytes.
func TestSuiteDeterminism(t *testing.T) {
for _, s := range []HashSuite{NewCoronaSHA3(), NewCoronaBLAKE3()} {
a := s.TranscriptHash([]byte("a"), []byte("b"))
b := s.TranscriptHash([]byte("a"), []byte("b"))
if a != b {
t.Errorf("%s: TranscriptHash not deterministic", s.ID())
}
if !bytes.Equal(s.Hc([]byte("x")), s.Hc([]byte("x"))) {
t.Errorf("%s: Hc not deterministic", s.ID())
}
if !bytes.Equal(s.Hu([]byte("x"), 32), s.Hu([]byte("x"), 32)) {
t.Errorf("%s: Hu not deterministic", s.ID())
}
}
}