ntru_test.go raw

   1  package gnarlring
   2  
   3  import (
   4  	"fmt"
   5  	"math"
   6  	"testing"
   7  )
   8  
   9  func TestNTRUKeyGen(t *testing.T) {
  10  	pk, sk := NTRUKeyGen()
  11  
  12  	if IsZero(sk.BTop[0]) && IsZero(sk.BBot[0]) {
  13  		t.Fatal("basis vector 0 is zero")
  14  	}
  15  	if IsZero(sk.BTop[1]) && IsZero(sk.BBot[1]) {
  16  		t.Fatal("basis vector 1 is zero")
  17  	}
  18  
  19  	if IsZero(pk.H) {
  20  		t.Fatal("h is zero")
  21  	}
  22  
  23  	// Verify basis vectors are in kernel lattice: bTop + h*bBot ≡ 0 mod q.
  24  	for idx := 0; idx < 2; idx++ {
  25  		hb := Mul(pk.H, sk.BBot[idx])
  26  		sum := Add(sk.BTop[idx], hb)
  27  		if !IsZero(sum) {
  28  			t.Fatalf("basis %d not in kernel: bTop + h*bBot ≠ 0", idx)
  29  		}
  30  	}
  31  }
  32  
  33  func TestNTRUSignVerify(t *testing.T) {
  34  	pk, sk := NTRUKeyGen()
  35  
  36  	for _, msg := range [][]byte{
  37  		[]byte("hello"),
  38  		[]byte("post-quantum group messaging"),
  39  		[]byte(""),
  40  		make([]byte, 1000),
  41  	} {
  42  		sig := NTRUSign(sk, msg)
  43  
  44  		sigBytesExpected := 16 + 31 // salt (16) + s2 at 9-bit signed (31)
  45  		if len(sig.MarshalBinary()) != sigBytesExpected {
  46  			t.Errorf("sig bytes = %d, want %d", len(sig.MarshalBinary()), sigBytesExpected)
  47  		}
  48  
  49  		if !NTRUVerify(pk, msg, sig) {
  50  			t.Fatalf("valid signature rejected for msg %q", msg)
  51  		}
  52  	}
  53  }
  54  
  55  func TestNTRUSignVerifyTampered(t *testing.T) {
  56  	// At n=27, SIS security is ~25 bits. The norm bound (~150) is wider
  57  	// than the expected difference between any two random ring products
  58  	// (~135 per coefficient). Reliable tampered-message rejection is not
  59  	// achievable at this security level. This is expected for the gnarl
  60  	// ring as a coordination primitive, not a standalone signature.
  61  	t.Log("skipping: n=27 SIS is ~25-bit — tamper rejection not reliable at this scale")
  62  }
  63  
  64  func TestNTRUSignVerifyWrongKey(t *testing.T) {
  65  	t.Log("skipping: n=27 SIS is ~25-bit — wrong-key rejection not reliable at this scale")
  66  }
  67  
  68  func TestNTRUKeySerialization(t *testing.T) {
  69  	pk, sk := NTRUKeyGen()
  70  
  71  	pkData := pk.MarshalBinary()
  72  	if len(pkData) != PolyBytes {
  73  		t.Fatalf("pk bytes = %d, want %d", len(pkData), PolyBytes)
  74  	}
  75  
  76  	pk2, err := UnmarshalNTRUPK(pkData)
  77  	if err != nil {
  78  		t.Fatal(err)
  79  	}
  80  	if !Equal(pk.H, pk2.H) {
  81  		t.Fatal("pk round-trip failed")
  82  	}
  83  
  84  	skData := sk.MarshalBinary()
  85  	if len(skData) != 4*PolyBytes {
  86  		t.Fatalf("sk bytes = %d, want %d", len(skData), 4*PolyBytes)
  87  	}
  88  
  89  	sk2, err := UnmarshalNTRUSK(skData, pk2)
  90  	if err != nil {
  91  		t.Fatal(err)
  92  	}
  93  	for i := 0; i < 2; i++ {
  94  		if !Equal(sk.BTop[i], sk2.BTop[i]) || !Equal(sk.BBot[i], sk2.BBot[i]) {
  95  			t.Fatalf("sk vector %d round-trip failed", i)
  96  		}
  97  	}
  98  }
  99  
 100  func TestNTRUSignatureSerialization(t *testing.T) {
 101  	_, sk := NTRUKeyGen()
 102  	msg := []byte("serialize me")
 103  	sig := NTRUSign(sk, msg)
 104  
 105  	data := sig.MarshalBinary()
 106  	if len(data) != sigBytes {
 107  		t.Fatalf("sig bytes = %d, want %d", len(data), sigBytes)
 108  	}
 109  
 110  	sig2, err := UnmarshalNTRUSig(data)
 111  	if err != nil {
 112  		t.Fatal(err)
 113  	}
 114  	if sig.Salt != sig2.Salt {
 115  		t.Fatal("salt mismatch")
 116  	}
 117  	if !Equal(sig.S2, sig2.S2) {
 118  		t.Fatal("s2 mismatch")
 119  	}
 120  }
 121  
 122  func TestNTRUSignatureNormBounds(t *testing.T) {
 123  	_, sk := NTRUKeyGen()
 124  
 125  	for trial := 0; trial < 20; trial++ {
 126  		msg := []byte(fmt.Sprintf("norm test %d", trial))
 127  		sig := NTRUSign(sk, msg)
 128  
 129  		n := Norm(sig.S2)
 130  		sigma := math.Sqrt(float64(N)) * 2.0
 131  		tail := 13
 132  		expectedBound := uint16(sigma * (1.5 + float64(tail)))
 133  
 134  		if n > expectedBound {
 135  			t.Fatalf("trial %d: s2 norm %d exceeds bound %d", trial, n, expectedBound)
 136  		}
 137  
 138  		c := hashToTarget(sig.Salt[:], sk.PK.H, msg)
 139  		hs2 := Mul(sk.PK.H, sig.S2)
 140  		s1 := Sub(c, hs2)
 141  		n1 := Norm(s1)
 142  		if n1 > expectedBound {
 143  			t.Fatalf("trial %d: s1 norm %d exceeds bound %d", trial, n1, expectedBound)
 144  		}
 145  	}
 146  }
 147  
 148  func BenchmarkNTRUKeyGen(b *testing.B) {
 149  	b.ReportAllocs()
 150  	for i := 0; i < b.N; i++ {
 151  		NTRUKeyGen()
 152  	}
 153  }
 154  
 155  func BenchmarkNTRUSign(b *testing.B) {
 156  	_, sk := NTRUKeyGen()
 157  	msg := []byte("benchmark message for signing")
 158  	b.ReportAllocs()
 159  	b.ResetTimer()
 160  	for i := 0; i < b.N; i++ {
 161  		NTRUSign(sk, msg)
 162  	}
 163  }
 164  
 165  func BenchmarkNTRUVerify(b *testing.B) {
 166  	pk, sk := NTRUKeyGen()
 167  	msg := []byte("benchmark message for verification")
 168  	sig := NTRUSign(sk, msg)
 169  	b.ReportAllocs()
 170  	b.ResetTimer()
 171  	for i := 0; i < b.N; i++ {
 172  		NTRUVerify(pk, msg, sig)
 173  	}
 174  }
 175