wuhp commited on
Commit
bf87f1f
·
verified ·
1 Parent(s): 5f21945

Update rust-engine/src/lwe.rs

Browse files
Files changed (1) hide show
  1. rust-engine/src/lwe.rs +48 -47
rust-engine/src/lwe.rs CHANGED
@@ -39,13 +39,12 @@ impl RingLwe {
39
  (a - t) as i16
40
  }
41
 
42
- /// Fast CBD sampling using popcount
43
  fn sample_cbd(rng: &mut ChaCha20Rng) -> i16 {
44
- let x: u32 = rng.gen();
45
- // η = 2: 4 bits per sample
46
- // x bits: [a0 b0 a1 b1 ...]
47
- let a = (x & 0x55555555).count_ones() as i16;
48
- let b = ((x >> 1) & 0x55555555).count_ones() as i16;
49
  a - b
50
  }
51
 
@@ -63,17 +62,18 @@ impl RingLwe {
63
  let zeta = ZETAS[k];
64
  k += 1;
65
  for j in start..(start + len) {
 
66
  let t = Self::f_reduce(zeta as i32 * p[j + len] as i32);
67
  p[j + len] = p[j] - t;
68
  p[j] = p[j] + t;
69
- // Note: p[j] might exceed i16 if not reduced, but we'll reduce eventually
70
- // For performance, we can delay reduction, but let's keep it safe
71
- if p[j] > 4096 || p[j] < -4096 { p[j] = Self::f_reduce(p[j] as i32); }
72
- if p[j+len] > 4096 || p[j+len] < -4096 { p[j+len] = Self::f_reduce(p[j+len] as i32); }
73
  }
74
  }
75
  len >>= 1;
76
  }
 
77
  }
78
 
79
  /// Inverse NTT
@@ -85,6 +85,7 @@ impl RingLwe {
85
  let zeta = ZETAS[k];
86
  k -= 1;
87
  for j in start..(start + len) {
 
88
  let t = p[j];
89
  p[j] = Self::f_reduce((t + p[j + len]) as i32);
90
  let val = p[j + len] - t;
@@ -113,12 +114,12 @@ impl RingLwe {
113
  let mut seed = [0u8; 32];
114
  rng.fill(&mut seed);
115
 
116
- // Expand seed to a
117
  let mut a_rng = ChaCha20Rng::from_seed(seed);
118
- let mut a = [0i16; N];
119
  let uniform = Uniform::new(0, self.q);
120
  for i in 0..N {
121
- a[i] = uniform.sample(&mut a_rng);
122
  }
123
 
124
  let mut s = [0i16; N];
@@ -128,32 +129,29 @@ impl RingLwe {
128
  e[i] = Self::sample_cbd(&mut rng);
129
  }
130
 
131
- // Domain: NTT
132
- let mut a_ntt = a;
133
  let mut s_ntt = s;
134
- self.ntt(&mut a_ntt);
135
  self.ntt(&mut s_ntt);
 
136
 
137
- let mut as_prod = Self::pointwise_mul(&a_ntt, &s_ntt);
138
- self.intt(&mut as_prod);
139
-
140
- let mut t = [0i16; N];
141
  for i in 0..N {
142
- t[i] = Self::f_reduce((as_prod[i] + e[i]) as i32);
 
143
  }
144
 
145
- (seed, t, s)
146
  }
147
 
148
- pub fn encrypt(&self, seed: &[u8; 32], t: &Poly, ptxt: i32) -> (Poly, Poly) {
149
  let mut rng = ChaCha20Rng::from_entropy();
150
 
151
- // Expand public a from seed
152
  let mut a_rng = ChaCha20Rng::from_seed(*seed);
153
- let mut a = [0i16; N];
154
  let uniform = Uniform::new(0, self.q);
155
  for i in 0..N {
156
- a[i] = uniform.sample(&mut a_rng);
157
  }
158
 
159
  let mut r = [0i16; N];
@@ -165,48 +163,51 @@ impl RingLwe {
165
  e2[i] = Self::sample_cbd(&mut rng);
166
  }
167
 
168
- let mut a_ntt = a;
169
  let mut r_ntt = r;
170
- self.ntt(&mut a_ntt);
171
  self.ntt(&mut r_ntt);
 
172
 
173
- let mut ar = Self::pointwise_mul(&a_ntt, &r_ntt);
174
- self.intt(&mut ar);
 
 
 
 
175
 
176
- let mut u = [0i16; N];
 
177
  for i in 0..N {
178
- u[i] = Self::f_reduce((ar[i] + e1[i]) as i32);
179
  }
180
-
181
- let mut t_ntt = *t;
182
- self.ntt(&mut t_ntt);
183
- let mut tr = Self::pointwise_mul(&t_ntt, &r_ntt);
184
  self.intt(&mut tr);
185
 
186
  let m_encoded = (ptxt as i16 * (self.q / 2)) % self.q;
187
  let mut v = [0i16; N];
188
  for i in 0..N {
189
- v[i] = Self::f_reduce((tr[i] + e2[i] + m_encoded) as i32);
 
 
190
  }
191
 
192
- (u, v)
193
  }
194
 
195
- pub fn decrypt(&self, s: &Poly, u: &Poly, v: &Poly) -> i32 {
196
- let mut s_ntt = *s;
197
- let mut u_ntt = *u;
198
- self.ntt(&mut s_ntt);
199
- self.ntt(&mut u_ntt);
200
-
201
- let mut su = Self::pointwise_mul(&s_ntt, &u_ntt);
202
  self.intt(&mut su);
203
 
204
  let mut ones = 0;
205
  let mut zeros = 0;
206
- for i in 0..N {
 
207
  let m_approx = Self::f_reduce((v[i] - su[i]) as i32);
208
 
209
- // Constant-ish distance check
210
  let val = if m_approx < 0 { m_approx + self.q } else { m_approx };
211
  let dist_to_0 = if val < self.q / 2 { val } else { self.q - val };
212
  let dist_to_q2 = (val - self.q / 2).abs();
 
39
  (a - t) as i16
40
  }
41
 
42
+ /// Fast CBD sampling for η = 2
43
  fn sample_cbd(rng: &mut ChaCha20Rng) -> i16 {
44
+ let x: u8 = rng.gen();
45
+ // Sample 1 coefficient from 4 bits (a=2bits, b=2bits)
46
+ let a = (x & 0x3).count_ones() as i16;
47
+ let b = ((x >> 2) & 0x3).count_ones() as i16;
 
48
  a - b
49
  }
50
 
 
62
  let zeta = ZETAS[k];
63
  k += 1;
64
  for j in start..(start + len) {
65
+ // Forward butterfly
66
  let t = Self::f_reduce(zeta as i32 * p[j + len] as i32);
67
  p[j + len] = p[j] - t;
68
  p[j] = p[j] + t;
69
+
70
+ // Frequent reduction to prevent overflow
71
+ if p[j].abs() > 4096 { p[j] = Self::f_reduce(p[j] as i32); }
 
72
  }
73
  }
74
  len >>= 1;
75
  }
76
+ for i in 0..N { p[i] = Self::f_reduce(p[i] as i32); }
77
  }
78
 
79
  /// Inverse NTT
 
85
  let zeta = ZETAS[k];
86
  k -= 1;
87
  for j in start..(start + len) {
88
+ // Inverse butterfly
89
  let t = p[j];
90
  p[j] = Self::f_reduce((t + p[j + len]) as i32);
91
  let val = p[j + len] - t;
 
114
  let mut seed = [0u8; 32];
115
  rng.fill(&mut seed);
116
 
117
+ // Expand seed to A in NTT domain
118
  let mut a_rng = ChaCha20Rng::from_seed(seed);
119
+ let mut a_ntt = [0i16; N];
120
  let uniform = Uniform::new(0, self.q);
121
  for i in 0..N {
122
+ a_ntt[i] = uniform.sample(&mut a_rng);
123
  }
124
 
125
  let mut s = [0i16; N];
 
129
  e[i] = Self::sample_cbd(&mut rng);
130
  }
131
 
 
 
132
  let mut s_ntt = s;
133
+ let mut e_ntt = e;
134
  self.ntt(&mut s_ntt);
135
+ self.ntt(&mut e_ntt);
136
 
137
+ let mut t_ntt = [0i16; N];
 
 
 
138
  for i in 0..N {
139
+ let prod = Self::f_reduce(a_ntt[i] as i32 * s_ntt[i] as i32);
140
+ t_ntt[i] = Self::f_reduce((prod + e_ntt[i]) as i32);
141
  }
142
 
143
+ (seed, t_ntt, s_ntt)
144
  }
145
 
146
+ pub fn encrypt(&self, seed: &[u8; 32], t_ntt: &Poly, ptxt: i32) -> (Poly, Poly) {
147
  let mut rng = ChaCha20Rng::from_entropy();
148
 
149
+ // Expand public a from seed in NTT domain
150
  let mut a_rng = ChaCha20Rng::from_seed(*seed);
151
+ let mut a_ntt = [0i16; N];
152
  let uniform = Uniform::new(0, self.q);
153
  for i in 0..N {
154
+ a_ntt[i] = uniform.sample(&mut a_rng);
155
  }
156
 
157
  let mut r = [0i16; N];
 
163
  e2[i] = Self::sample_cbd(&mut rng);
164
  }
165
 
 
166
  let mut r_ntt = r;
167
+ let mut e1_ntt = e1;
168
  self.ntt(&mut r_ntt);
169
+ self.ntt(&mut e1_ntt);
170
 
171
+ // u = a * r + e1 (in NTT domain)
172
+ let mut u_ntt = [0i16; N];
173
+ for i in 0..N {
174
+ let prod = Self::f_reduce(a_ntt[i] as i32 * r_ntt[i] as i32);
175
+ u_ntt[i] = Self::f_reduce((prod + e1_ntt[i]) as i32);
176
+ }
177
 
178
+ // v = t * r + e2 + m * q/2
179
+ let mut tr_ntt = [0i16; N];
180
  for i in 0..N {
181
+ tr_ntt[i] = Self::f_reduce(t_ntt[i] as i32 * r_ntt[i] as i32);
182
  }
183
+ let mut tr = tr_ntt;
 
 
 
184
  self.intt(&mut tr);
185
 
186
  let m_encoded = (ptxt as i16 * (self.q / 2)) % self.q;
187
  let mut v = [0i16; N];
188
  for i in 0..N {
189
+ // Encode message in first 32 coefficients for density to reduce noise sum
190
+ let m = if i < 32 { m_encoded } else { 0 };
191
+ v[i] = Self::f_reduce((tr[i] + e2[i] + m) as i32);
192
  }
193
 
194
+ (u_ntt, v)
195
  }
196
 
197
+ pub fn decrypt(&self, s_ntt: &Poly, u_ntt: &Poly, v: &Poly) -> i32 {
198
+ let mut su_ntt = [0i16; N];
199
+ for i in 0..N {
200
+ su_ntt[i] = Self::f_reduce(s_ntt[i] as i32 * u_ntt[i] as i32);
201
+ }
202
+ let mut su = su_ntt;
 
203
  self.intt(&mut su);
204
 
205
  let mut ones = 0;
206
  let mut zeros = 0;
207
+ // Check only the indices where we encoded the message
208
+ for i in 0..32 {
209
  let m_approx = Self::f_reduce((v[i] - su[i]) as i32);
210
 
 
211
  let val = if m_approx < 0 { m_approx + self.q } else { m_approx };
212
  let dist_to_0 = if val < self.q / 2 { val } else { self.q - val };
213
  let dist_to_q2 = (val - self.q / 2).abs();