1use fancy_traits::HasModulus;
2use rand::CryptoRng;
3use vectoreyes::U8x16;
4
5use crate::{ArithmeticWireLabel, WireLabel, WireMod2, WireMod3, WireModQ};
6
7#[derive(Debug, Clone, PartialEq)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9pub enum AllWire {
11 Mod2(WireMod2),
13 Mod3(WireMod3),
15 ModN(WireModQ),
17}
18
19impl HasModulus for AllWire {
20 fn modulus(&self) -> u16 {
21 match &self {
22 AllWire::Mod2(x) => x.modulus(),
23 AllWire::Mod3(x) => x.modulus(),
24 AllWire::ModN(x) => x.modulus(),
25 }
26 }
27}
28
29impl core::default::Default for AllWire {
30 fn default() -> Self {
31 Self::Mod2(Default::default())
32 }
33}
34
35impl core::ops::Add for AllWire {
36 type Output = Self;
37
38 fn add(self, rhs: Self) -> Self::Output {
39 let (p, q) = (self.modulus(), rhs.modulus());
40 match (self, rhs) {
41 (Self::Mod2(x), Self::Mod2(y)) => Self::Mod2(x + y),
42 (Self::Mod3(x), Self::Mod3(y)) => Self::Mod3(x + y),
43 (Self::ModN(x), Self::ModN(y)) => Self::ModN(x + y),
44 _ => panic!("unequal moduli: {p} != {q}"),
45 }
46 }
47}
48
49impl core::ops::AddAssign for AllWire {
50 fn add_assign(&mut self, rhs: Self) {
51 let (p, q) = (self.modulus(), rhs.modulus());
52 match (self, rhs) {
53 (Self::Mod2(x), Self::Mod2(y)) => *x += y,
54 (Self::Mod3(x), Self::Mod3(y)) => *x += y,
55 (Self::ModN(x), Self::ModN(y)) => *x += y,
56 _ => panic!("unequal moduli: {p} != {q}"),
57 }
58 }
59}
60
61impl core::ops::Sub for AllWire {
62 type Output = Self;
63
64 fn sub(self, rhs: Self) -> Self::Output {
65 self + -rhs
66 }
67}
68
69impl core::ops::SubAssign for AllWire {
70 fn sub_assign(&mut self, rhs: Self) {
71 *self = self.clone() - rhs;
72 }
73}
74
75impl core::ops::Neg for AllWire {
76 type Output = Self;
77
78 fn neg(self) -> Self::Output {
79 match self {
80 Self::Mod2(x) => Self::Mod2(-x),
81 Self::Mod3(x) => Self::Mod3(-x),
82 Self::ModN(x) => Self::ModN(-x),
83 }
84 }
85}
86
87impl core::ops::Mul<u16> for AllWire {
88 type Output = Self;
89
90 fn mul(self, rhs: u16) -> Self::Output {
91 match self {
92 Self::Mod2(x) => Self::Mod2(x * rhs),
93 Self::Mod3(x) => Self::Mod3(x * rhs),
94 Self::ModN(x) => Self::ModN(x * rhs),
95 }
96 }
97}
98
99impl core::ops::MulAssign<u16> for AllWire {
100 fn mul_assign(&mut self, rhs: u16) {
101 match self {
102 Self::Mod2(x) => {
103 *x *= rhs;
104 }
105 Self::Mod3(x) => {
106 *x *= rhs;
107 }
108 Self::ModN(x) => {
109 *x *= rhs;
110 }
111 };
112 }
113}
114
115impl WireLabel for AllWire {
116 fn rand_delta<R: CryptoRng>(rng: &mut R, q: u16) -> Self {
117 match q {
118 2 => AllWire::Mod2(WireMod2::rand_delta(rng, q)),
119 3 => AllWire::Mod3(WireMod3::rand_delta(rng, q)),
120 _ => AllWire::ModN(WireModQ::rand_delta(rng, q)),
121 }
122 }
123
124 fn to_repr(&self) -> U8x16 {
125 match &self {
126 AllWire::Mod2(x) => x.to_repr(),
127 AllWire::Mod3(x) => x.to_repr(),
128 AllWire::ModN(x) => x.to_repr(),
129 }
130 }
131 fn color(&self) -> u16 {
132 match &self {
133 AllWire::Mod2(x) => x.color(),
134 AllWire::Mod3(x) => x.color(),
135 AllWire::ModN(x) => x.color(),
136 }
137 }
138 fn from_repr(inp: U8x16, q: u16) -> Self {
139 match q {
140 2 => AllWire::Mod2(WireMod2::from_repr(inp, q)),
141 3 => AllWire::Mod3(WireMod3::from_repr(inp, q)),
142 _ => AllWire::ModN(WireModQ::from_repr(inp, q)),
143 }
144 }
145
146 fn rand<R: CryptoRng>(rng: &mut R, q: u16) -> Self {
147 match q {
148 2 => AllWire::Mod2(WireMod2::rand(rng, q)),
149 3 => AllWire::Mod3(WireMod3::rand(rng, q)),
150 _ => AllWire::ModN(WireModQ::rand(rng, q)),
151 }
152 }
153
154 fn hash_to_mod(hash: U8x16, q: u16) -> Self {
155 if q == 3 {
156 AllWire::Mod3(WireMod3::encode_block_mod3(hash))
157 } else {
158 Self::from_repr(hash, q)
159 }
160 }
161}
162
163impl ArithmeticWireLabel for AllWire {}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168 use crate::util::as_base_q_u128;
169 use fancy_circuits::util::RngExt;
170 use itertools::Itertools;
171 use rand::{RngExt as _, rng};
172
173 #[test]
174 fn packing() {
175 let rng = &mut rng();
176 for q in 2..256 {
177 for _ in 0..1000 {
178 let w = AllWire::rand(rng, q);
179 assert_eq!(w, AllWire::from_repr(w.to_repr(), q));
180 }
181 }
182 }
183
184 #[test]
185 fn base_conversion_lookup_method() {
186 let rng = &mut rng();
187 for _ in 0..1000 {
188 let q = 5 + (rng.random::<u16>() % 110);
189 let x = rng.random::<u128>();
190 let w = WireModQ::from_repr(U8x16::from(x), q);
191 let should_be = as_base_q_u128(x, q);
192 assert_eq!(w.ds, should_be, "x={} q={}", x, q);
193 }
194 }
195
196 #[test]
197 fn hash() {
198 let mut rng = rng();
199 for _ in 0..100 {
200 let q = 2 + (rng.random::<u16>() % 110);
201 let x = AllWire::rand(&mut rng, q);
202 let y = AllWire::hash_to_mod(x.hash(1u128), q);
203 assert!(x != y);
204 match y {
205 AllWire::Mod2(WireMod2 { val }) => assert!(u128::from(val) > 0),
206 AllWire::Mod3(WireMod3 { lsb, msb }) => assert!(lsb > 0 && msb > 0),
207 AllWire::ModN(WireModQ { ds, .. }) => assert!(!ds.iter().all(|&y| y == 0)),
208 }
209 }
210 }
211
212 #[test]
213 fn negation() {
214 let rng = &mut rng();
215 for _ in 0..1000 {
216 let q = rng.gen_modulus();
217 let x = AllWire::rand(rng, q);
218 let xneg = -x.clone();
219 if q != 2 {
220 assert!(x != xneg);
221 }
222 let y = -xneg;
223 assert_eq!(x, y);
224 }
225 }
226
227 #[test]
228 #[allow(clippy::erasing_op)]
229 fn arithmetic() {
230 let mut rng = rng();
231 for _ in 0..1024 {
232 let q = rng.gen_modulus();
233 let x = AllWire::rand(&mut rng, q);
234 let y = AllWire::rand(&mut rng, q);
235 assert_eq!(x.clone() * 0, x.clone() - x.clone());
236 assert_eq!(x.clone() * q, x.clone() - x.clone());
237 assert_eq!(x.clone() + x.clone(), x.clone() * 2);
238 assert_eq!(x.clone() + x.clone() + x.clone(), x.clone() * 3);
239 assert_eq!(-(-x.clone()), x);
240 if q == 2 {
241 assert_eq!(x.clone() + y.clone(), x.clone() - y.clone());
242 } else {
243 assert_eq!(x.clone() + -x.clone(), x.clone() - x.clone());
244 assert_eq!(x.clone() + -y.clone(), x.clone() - y.clone());
245 }
246 let mut w = x.clone();
247 let z = w.clone() + y.clone();
248 w += y;
249 assert_eq!(w, z);
250
251 w = x.clone();
252 w *= 2;
253 assert_eq!(x.clone() + x.clone(), w);
254
255 w = x.clone();
256 w = -w;
257 assert_eq!(-x, w);
258 }
259 }
260
261 #[test]
262 fn ndigits_correct() {
263 let mut rng = rng();
264 for _ in 0..1024 {
265 let q = rng.gen_modulus();
266 let x = WireModQ::rand(&mut rng, q);
267 assert_eq!(x.ds.len(), crate::util::digits_per_u128(q));
268 }
269 }
270
271 #[test]
272 fn parallel_hash() {
273 let n = 1000;
274 let mut rng = rng();
275 let q = rng.gen_modulus();
276 let ws = (0..n).map(|_| AllWire::rand(&mut rng, q)).collect_vec();
277
278 let mut handles = Vec::new();
279 for w in ws.iter() {
280 let w_ = w.clone();
281 let h = std::thread::spawn(move || w_.hash(0u128));
282 handles.push(h);
283 }
284 let hashes = handles.into_iter().map(|h| h.join().unwrap()).collect_vec();
285
286 let should_be = ws.iter().map(|w| w.hash(0u128)).collect_vec();
287
288 assert_eq!(hashes, should_be);
289 }
290
291 #[cfg(feature = "serde")]
292 #[test]
293 fn test_serialize_allwire() {
294 let mut rng = rng();
295 for q in 2..16 {
296 let w = AllWire::rand(&mut rng, q);
297 let serialized = serde_json::to_string(&w).unwrap();
298
299 let deserialized: AllWire = serde_json::from_str(&serialized).unwrap();
300
301 assert_eq!(w, deserialized);
302 }
303 }
304}