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