malleable_test.go raw

   1  package ring
   2  
   3  import "testing"
   4  
   5  // TestNoiseFloodPreservesPlaintext verifies that noise flooding
   6  // doesn't change the underlying plaintext.
   7  func TestNoiseFloodPreservesPlaintext(t *testing.T) {
   8  	kp := DefaultHEParams()
   9  	pk, sk, _ := HEKeyGen(kp)
  10  
  11  	for _, bit := range []int{0, 1} {
  12  		ct := HEEncrypt(pk, bit)
  13  		flooded := NoiseFlood(pk, ct, 6) // moderate flood
  14  
  15  		got := HEDecrypt(sk, flooded)
  16  		if got != bit {
  17  			t.Fatalf("bit=%d: after noise flood, decrypted to %d", bit, got)
  18  		}
  19  	}
  20  }
  21  
  22  // TestNoiseFloodChangesNoise verifies that flooding changes the noise pattern.
  23  func TestNoiseFloodChangesNoise(t *testing.T) {
  24  	kp := DefaultHEParams()
  25  	pk, _, _ := HEKeyGen(kp)
  26  
  27  	ct := HEEncrypt(pk, 1)
  28  	flooded := NoiseFlood(pk, ct, 6)
  29  
  30  	// Ciphertext should be different after flooding.
  31  	if Equal(ct.U, flooded.U) && Equal(ct.V, flooded.V) {
  32  		t.Fatal("noise flood didn't change ciphertext")
  33  	}
  34  }
  35  
  36  // TestWrapVerifyResult verifies the full anti-malleability stack.
  37  func TestWrapVerifyResult(t *testing.T) {
  38  	kp := DefaultHEParams()
  39  	pk, sk, _ := HEKeyGen(kp)
  40  	gp := SmallGPVParams()
  41  	gpvPK, gpvSK := GPVKeyGen(gp)
  42  
  43  	ct := HEEncrypt(pk, 1)
  44  	sessionID := []byte("test-session-123")
  45  
  46  	result := WrapResult(pk, ct, sessionID, gpvSK)
  47  
  48  	// Verify passes.
  49  	if !VerifyResult(result, sessionID, gpvPK) {
  50  		t.Fatal("valid result rejected")
  51  	}
  52  
  53  	// Plaintext preserved through all the defenses.
  54  	got := HEDecrypt(sk, result.Ciphertext)
  55  	if got != 1 {
  56  		t.Fatalf("decrypted to %d after wrapping, want 1", got)
  57  	}
  58  }
  59  
  60  // TestWrapVerifyWrongSession verifies session binding.
  61  func TestWrapVerifyWrongSession(t *testing.T) {
  62  	kp := DefaultHEParams()
  63  	pk, _, _ := HEKeyGen(kp)
  64  	gp := SmallGPVParams()
  65  	gpvPK, gpvSK := GPVKeyGen(gp)
  66  
  67  	ct := HEEncrypt(pk, 0)
  68  	result := WrapResult(pk, ct, []byte("correct-session"), gpvSK)
  69  
  70  	// Wrong session ID should fail.
  71  	if VerifyResult(result, []byte("wrong-session"), gpvPK) {
  72  		t.Fatal("wrong session ID accepted")
  73  	}
  74  }
  75  
  76  // TestWrapVerifyTamperedCiphertext verifies tamper detection.
  77  func TestWrapVerifyTamperedCiphertext(t *testing.T) {
  78  	kp := DefaultHEParams()
  79  	pk, _, _ := HEKeyGen(kp)
  80  	gp := SmallGPVParams()
  81  	gpvPK, gpvSK := GPVKeyGen(gp)
  82  
  83  	ct := HEEncrypt(pk, 1)
  84  	sessionID := []byte("tamper-test")
  85  	result := WrapResult(pk, ct, sessionID, gpvSK)
  86  
  87  	// Tamper with ciphertext.
  88  	result.Ciphertext.U.Coeffs[0] = (result.Ciphertext.U.Coeffs[0] + 1) % kp.Ring.Q
  89  
  90  	// Verification should fail (tag won't match).
  91  	if VerifyResult(result, sessionID, gpvPK) {
  92  		t.Fatal("tampered ciphertext accepted")
  93  	}
  94  }
  95  
  96  // TestSessionWrapper verifies the session-based API.
  97  func TestSessionWrapper(t *testing.T) {
  98  	kp := DefaultHEParams()
  99  	pk, sk, _ := HEKeyGen(kp)
 100  	gp := SmallGPVParams()
 101  	gpvPK, gpvSK := GPVKeyGen(gp)
 102  
 103  	session := NewSession(pk, gpvPK, gpvSK)
 104  
 105  	// Encrypt, compute, wrap.
 106  	ct0 := HEEncrypt(pk, 1)
 107  	ct1 := HEEncrypt(pk, 1)
 108  	ctXor := HEAdd(ct0, ct1) // 1 XOR 1 = 0
 109  
 110  	result := session.Wrap(ctXor)
 111  
 112  	// Verify.
 113  	// Need a matching session for verification.
 114  	verifier := &SessionWrapper{
 115  		SessionID: session.SessionID,
 116  		HEPK:      pk,
 117  		GPVPK:     gpvPK,
 118  	}
 119  
 120  	if !verifier.Verify(result) {
 121  		t.Fatal("session verification failed")
 122  	}
 123  
 124  	got := HEDecrypt(sk, result.Ciphertext)
 125  	if got != 0 {
 126  		t.Fatalf("expected 0 (1 XOR 1), got %d", got)
 127  	}
 128  }
 129  
 130  // TestWrapWithoutSignature verifies wrapping without GPV signature.
 131  func TestWrapWithoutSignature(t *testing.T) {
 132  	kp := DefaultHEParams()
 133  	pk, sk, _ := HEKeyGen(kp)
 134  
 135  	ct := HEEncrypt(pk, 1)
 136  	sessionID := []byte("unsigned-session")
 137  
 138  	// Wrap without signing key.
 139  	result := WrapResult(pk, ct, sessionID, nil)
 140  
 141  	// Verify without signing key.
 142  	if !VerifyResult(result, sessionID, nil) {
 143  		t.Fatal("unsigned result rejected")
 144  	}
 145  
 146  	got := HEDecrypt(sk, result.Ciphertext)
 147  	if got != 1 {
 148  		t.Fatalf("decrypted to %d, want 1", got)
 149  	}
 150  }
 151  
 152  func BenchmarkNoiseFlood(b *testing.B) {
 153  	kp := DefaultHEParams()
 154  	pk, _, _ := HEKeyGen(kp)
 155  	ct := HEEncrypt(pk, 1)
 156  	b.ResetTimer()
 157  	for range b.N {
 158  		NoiseFlood(pk, ct, 8)
 159  	}
 160  }
 161  
 162  func BenchmarkWrapResult(b *testing.B) {
 163  	kp := DefaultHEParams()
 164  	pk, _, _ := HEKeyGen(kp)
 165  	gp := SmallGPVParams()
 166  	_, gpvSK := GPVKeyGen(gp)
 167  	ct := HEEncrypt(pk, 1)
 168  	sessionID := []byte("benchmark")
 169  	b.ResetTimer()
 170  	for range b.N {
 171  		WrapResult(pk, ct, sessionID, gpvSK)
 172  	}
 173  }
 174