1use crate::util;
9use fancy_traits::HasModulus;
10use rand::{CryptoRng, Rng};
11use swanky_cr_hash::TweakableCircularCorrelationRobustHash;
12use vectoreyes::{
13 U8x16,
14 array_utils::{ArrayUnrolledExt, ArrayUnrolledOps, UnrollableArraySize},
15};
16
17mod mod2;
18pub use mod2::WireMod2;
19mod mod3;
20pub use mod3::WireMod3;
21mod modq;
22pub use modq::WireModQ;
23mod npaths_tab;
24
25pub fn hash_wires<const Q: usize, W: WireLabel>(wires: [&W; Q], tweak: u128) -> [U8x16; Q]
27where
28 ArrayUnrolledOps: UnrollableArraySize<Q>,
29{
30 let batch = wires.array_map(|x| x.to_repr());
31 TweakableCircularCorrelationRobustHash::fixed_key().hash_many(batch, tweak)
32}
33
34pub trait ArithmeticWire: Clone {}
37
38pub trait WireLabel:
43 Clone
44 + core::fmt::Debug
45 + HasModulus
46 + core::ops::Add<Output = Self>
47 + core::ops::AddAssign
48 + core::ops::Sub<Output = Self>
49 + core::ops::SubAssign
50 + core::ops::Neg<Output = Self>
51 + core::ops::Mul<u16, Output = Self>
52 + core::ops::MulAssign<u16>
53{
54 fn to_repr(&self) -> U8x16;
56
57 fn color(&self) -> u16;
59
60 fn from_repr(inp: U8x16, q: u16) -> Self;
67
68 fn rand_delta<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self;
74
75 fn rand<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self;
81
82 fn hash_to_mod(hash: U8x16, q: u16) -> Self;
91
92 fn hashback(&self, tweak: u128, q: u16) -> Self {
103 let hash = self.hash(tweak);
104 Self::hash_to_mod(hash, q)
105 }
106
107 fn hash(&self, tweak: u128) -> U8x16 {
109 TweakableCircularCorrelationRobustHash::fixed_key().hash(self.to_repr(), tweak)
110 }
111
112 fn constant<RNG: CryptoRng + Rng>(x: u16, q: u16, delta: &Self, rng: &mut RNG) -> (Self, Self) {
115 let zero = Self::rand(rng, q);
116 let wire = zero.clone() + delta.clone() * x;
117 (zero, wire)
118 }
119}
120
121#[derive(Debug, Clone, PartialEq)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
123pub enum AllWire {
125 Mod2(WireMod2),
127 Mod3(WireMod3),
129 ModN(WireModQ),
131}
132
133impl HasModulus for AllWire {
134 fn modulus(&self) -> u16 {
135 match &self {
136 AllWire::Mod2(x) => x.modulus(),
137 AllWire::Mod3(x) => x.modulus(),
138 AllWire::ModN(x) => x.modulus(),
139 }
140 }
141}
142
143impl core::ops::Add for AllWire {
144 type Output = Self;
145
146 fn add(self, rhs: Self) -> Self::Output {
147 let (p, q) = (self.modulus(), rhs.modulus());
148 match (self, rhs) {
149 (Self::Mod2(x), Self::Mod2(y)) => Self::Mod2(x + y),
150 (Self::Mod3(x), Self::Mod3(y)) => Self::Mod3(x + y),
151 (Self::ModN(x), Self::ModN(y)) => Self::ModN(x + y),
152 _ => panic!("unequal moduli: {p} != {q}"),
153 }
154 }
155}
156
157impl core::ops::AddAssign for AllWire {
158 fn add_assign(&mut self, rhs: Self) {
159 let (p, q) = (self.modulus(), rhs.modulus());
160 match (self, rhs) {
161 (Self::Mod2(x), Self::Mod2(y)) => *x += y,
162 (Self::Mod3(x), Self::Mod3(y)) => *x += y,
163 (Self::ModN(x), Self::ModN(y)) => *x += y,
164 _ => panic!("unequal moduli: {p} != {q}"),
165 }
166 }
167}
168
169impl core::ops::Sub for AllWire {
170 type Output = Self;
171
172 fn sub(self, rhs: Self) -> Self::Output {
173 self + -rhs
174 }
175}
176
177impl core::ops::SubAssign for AllWire {
178 fn sub_assign(&mut self, rhs: Self) {
179 *self = self.clone() - rhs;
180 }
181}
182
183impl core::ops::Neg for AllWire {
184 type Output = Self;
185
186 fn neg(self) -> Self::Output {
187 match self {
188 Self::Mod2(x) => Self::Mod2(-x),
189 Self::Mod3(x) => Self::Mod3(-x),
190 Self::ModN(x) => Self::ModN(-x),
191 }
192 }
193}
194
195impl core::ops::Mul<u16> for AllWire {
196 type Output = Self;
197
198 fn mul(self, rhs: u16) -> Self::Output {
199 match self {
200 Self::Mod2(x) => Self::Mod2(x * rhs),
201 Self::Mod3(x) => Self::Mod3(x * rhs),
202 Self::ModN(x) => Self::ModN(x * rhs),
203 }
204 }
205}
206
207impl core::ops::MulAssign<u16> for AllWire {
208 fn mul_assign(&mut self, rhs: u16) {
209 match self {
210 Self::Mod2(x) => {
211 *x *= rhs;
212 }
213 Self::Mod3(x) => {
214 *x *= rhs;
215 }
216 Self::ModN(x) => {
217 *x *= rhs;
218 }
219 };
220 }
221}
222
223impl WireLabel for AllWire {
224 fn rand_delta<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self {
225 match q {
226 2 => AllWire::Mod2(WireMod2::rand_delta(rng, q)),
227 3 => AllWire::Mod3(WireMod3::rand_delta(rng, q)),
228 _ => AllWire::ModN(WireModQ::rand_delta(rng, q)),
229 }
230 }
231
232 fn to_repr(&self) -> U8x16 {
233 match &self {
234 AllWire::Mod2(x) => x.to_repr(),
235 AllWire::Mod3(x) => x.to_repr(),
236 AllWire::ModN(x) => x.to_repr(),
237 }
238 }
239 fn color(&self) -> u16 {
240 match &self {
241 AllWire::Mod2(x) => x.color(),
242 AllWire::Mod3(x) => x.color(),
243 AllWire::ModN(x) => x.color(),
244 }
245 }
246 fn from_repr(inp: U8x16, q: u16) -> Self {
247 match q {
248 2 => AllWire::Mod2(WireMod2::from_repr(inp, q)),
249 3 => AllWire::Mod3(WireMod3::from_repr(inp, q)),
250 _ => AllWire::ModN(WireModQ::from_repr(inp, q)),
251 }
252 }
253
254 fn rand<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self {
255 match q {
256 2 => AllWire::Mod2(WireMod2::rand(rng, q)),
257 3 => AllWire::Mod3(WireMod3::rand(rng, q)),
258 _ => AllWire::ModN(WireModQ::rand(rng, q)),
259 }
260 }
261
262 fn hash_to_mod(hash: U8x16, q: u16) -> Self {
263 if q == 3 {
264 AllWire::Mod3(WireMod3::encode_block_mod3(hash))
265 } else {
266 Self::from_repr(hash, q)
267 }
268 }
269}
270fn _unrank(inp: u128, q: u16) -> Vec<u16> {
271 let mut x = inp;
272 let ndigits = util::digits_per_u128(q);
273 let npaths_tab = npaths_tab::lookup(q);
274 x %= npaths_tab[ndigits - 1] * q as u128;
275
276 let mut ds = vec![0; ndigits];
277 for i in (0..ndigits).rev() {
278 let npaths = npaths_tab[i];
279
280 if q <= 23 {
281 let mut acc = 0;
283 for j in 0..q {
284 acc += npaths;
285 if acc > x {
286 x -= acc - npaths;
287 ds[i] = j;
288 break;
289 }
290 }
291 } else {
292 let d = x / npaths;
294 ds[i] = d as u16;
295 x -= d * npaths;
296 }
297 }
319 ds
320}
321
322impl ArithmeticWire for AllWire {}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use crate::util::as_base_q_u128;
328 use fancy_circuits::util::RngExt;
329 use itertools::Itertools;
330 use rand::{RngExt as _, rng};
331
332 #[test]
333 fn packing() {
334 let rng = &mut rng();
335 for q in 2..256 {
336 for _ in 0..1000 {
337 let w = AllWire::rand(rng, q);
338 assert_eq!(w, AllWire::from_repr(w.to_repr(), q));
339 }
340 }
341 }
342
343 #[test]
344 fn base_conversion_lookup_method() {
345 let rng = &mut rng();
346 for _ in 0..1000 {
347 let q = 5 + (rng.random::<u16>() % 110);
348 let x = rng.random::<u128>();
349 let w = WireModQ::from_repr(U8x16::from(x), q);
350 let should_be = as_base_q_u128(x, q);
351 assert_eq!(w.ds, should_be, "x={} q={}", x, q);
352 }
353 }
354
355 #[test]
356 fn hash() {
357 let mut rng = rng();
358 for _ in 0..100 {
359 let q = 2 + (rng.random::<u16>() % 110);
360 let x = AllWire::rand(&mut rng, q);
361 let y = x.hashback(1u128, q);
362 assert!(x != y);
363 match y {
364 AllWire::Mod2(WireMod2 { val }) => assert!(u128::from(val) > 0),
365 AllWire::Mod3(WireMod3 { lsb, msb }) => assert!(lsb > 0 && msb > 0),
366 AllWire::ModN(WireModQ { ds, .. }) => assert!(!ds.iter().all(|&y| y == 0)),
367 }
368 }
369 }
370
371 #[test]
372 fn negation() {
373 let rng = &mut rng();
374 for _ in 0..1000 {
375 let q = rng.gen_modulus();
376 let x = AllWire::rand(rng, q);
377 let xneg = -x.clone();
378 if q != 2 {
379 assert!(x != xneg);
380 }
381 let y = -xneg;
382 assert_eq!(x, y);
383 }
384 }
385
386 #[test]
387 #[allow(clippy::erasing_op)]
388 fn arithmetic() {
389 let mut rng = rng();
390 for _ in 0..1024 {
391 let q = rng.gen_modulus();
392 let x = AllWire::rand(&mut rng, q);
393 let y = AllWire::rand(&mut rng, q);
394 assert_eq!(x.clone() * 0, x.clone() - x.clone());
395 assert_eq!(x.clone() * q, x.clone() - x.clone());
396 assert_eq!(x.clone() + x.clone(), x.clone() * 2);
397 assert_eq!(x.clone() + x.clone() + x.clone(), x.clone() * 3);
398 assert_eq!(-(-x.clone()), x);
399 if q == 2 {
400 assert_eq!(x.clone() + y.clone(), x.clone() - y.clone());
401 } else {
402 assert_eq!(x.clone() + -x.clone(), x.clone() - x.clone());
403 assert_eq!(x.clone() + -y.clone(), x.clone() - y.clone());
404 }
405 let mut w = x.clone();
406 let z = w.clone() + y.clone();
407 w += y;
408 assert_eq!(w, z);
409
410 w = x.clone();
411 w *= 2;
412 assert_eq!(x.clone() + x.clone(), w);
413
414 w = x.clone();
415 w = -w;
416 assert_eq!(-x, w);
417 }
418 }
419
420 #[test]
421 fn ndigits_correct() {
422 let mut rng = rng();
423 for _ in 0..1024 {
424 let q = rng.gen_modulus();
425 let x = WireModQ::rand(&mut rng, q);
426 assert_eq!(x.ds.len(), util::digits_per_u128(q));
427 }
428 }
429
430 #[test]
431 fn parallel_hash() {
432 let n = 1000;
433 let mut rng = rng();
434 let q = rng.gen_modulus();
435 let ws = (0..n).map(|_| AllWire::rand(&mut rng, q)).collect_vec();
436
437 let mut handles = Vec::new();
438 for w in ws.iter() {
439 let w_ = w.clone();
440 let h = std::thread::spawn(move || w_.hash(0u128));
441 handles.push(h);
442 }
443 let hashes = handles.into_iter().map(|h| h.join().unwrap()).collect_vec();
444
445 let should_be = ws.iter().map(|w| w.hash(0u128)).collect_vec();
446
447 assert_eq!(hashes, should_be);
448 }
449
450 #[cfg(feature = "serde")]
451 #[test]
452 fn test_serialize_allwire() {
453 let mut rng = rng();
454 for q in 2..16 {
455 let w = AllWire::rand(&mut rng, q);
456 let serialized = serde_json::to_string(&w).unwrap();
457
458 let deserialized: AllWire = serde_json::from_str(&serialized).unwrap();
459
460 assert_eq!(w, deserialized);
461 }
462 }
463}