Skip to main content

fancy_garbling/
wire.rs

1//! Wirelabels for use in garbled circuits.
2//!
3//! This module contains a [`WireLabel`] trait, alongside various instantiations
4//! of this trait. The [`WireLabel`] trait is the core underlying primitive used
5//! in garbled circuits, and represents an encoding of the value on any given
6//! wire of the circuit.
7
8use 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
25/// Hash a batch of wires, using the same tweak for each wire.
26pub 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
34/// A marker trait indicating that the given [`WireLabel`] instantiation
35/// supports arithmetic operations.
36pub trait ArithmeticWire: Clone {}
37
38/// A trait that defines a wirelabel as used in garbled circuits.
39///
40/// At its core, a [`WireLabel`] is a way of encoding values, and operating on
41/// those encoded values.
42pub 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    /// Converts a [`WireLabel`] into its [`U8x16`] representation.
55    fn to_repr(&self) -> U8x16;
56
57    /// The color digit of the wire.
58    fn color(&self) -> u16;
59
60    /// Converts a [`U8x16`] into its [`WireLabel`] representation, based on the
61    /// modulus `q`.
62    ///
63    /// # Panics
64    /// This panics if `q` does not align with the modulus supported by the
65    /// [`WireLabel`].
66    fn from_repr(inp: U8x16, q: u16) -> Self;
67
68    /// A random [`WireLabel`] `mod q`, with the first digit set to `1`.
69    ///
70    /// # Panics
71    /// This panics if `q` does not align with the modulus supported by the
72    /// [`WireLabel`].
73    fn rand_delta<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self;
74
75    /// A random [`WireLabel`] `mod q`.
76    ///
77    /// # Panics
78    /// This panics if `q` does not align with the modulus supported by the
79    /// [`WireLabel`].
80    fn rand<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self;
81
82    /// Converts a hashed block into a valid wire of the given modulus `q`.
83    ///
84    /// This is useful when separately using [`hash_wires`] to hash a set of
85    /// wires in one shot for efficiency reasons.
86    ///
87    /// # Panics
88    /// This panics if `q` does not align with the modulus supported by the
89    /// [`WireLabel`].
90    fn hash_to_mod(hash: U8x16, q: u16) -> Self;
91
92    /// Computes the hash of this [`WireLabel`], converting the result back into
93    /// a [`WireLabel`] based on the modulus `q`.
94    ///
95    /// This is equivalent to `WireLabel::hash_to_mod(self.hash(tweak), q)`, and
96    /// is useful when stringing together a sequence of operations on a
97    /// [`WireLabel`].
98    ///
99    /// # Panics
100    /// This panics if `q` does not align with the modulus supported by the
101    /// [`WireLabel`].
102    fn hashback(&self, tweak: u128, q: u16) -> Self {
103        let hash = self.hash(tweak);
104        Self::hash_to_mod(hash, q)
105    }
106
107    /// Computes the hash of the [`WireLabel`].
108    fn hash(&self, tweak: u128) -> U8x16 {
109        TweakableCircularCorrelationRobustHash::fixed_key().hash(self.to_repr(), tweak)
110    }
111
112    /// Computes a [`WireLabel`] for `x % q`, returning both the zero
113    /// [`WireLabel`] as well as the [`WireLabel`] for `x % q`.
114    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))]
123/// A [`WireLabel`] that supports all possible moduli
124pub enum AllWire {
125    /// A `mod 2` [`WireLabel`].
126    Mod2(WireMod2),
127    /// A `mod 3` [`WireLabel`].
128    Mod3(WireMod3),
129    /// A `mod q` [`WireLabel`], where `3 < q < 2^16`.
130    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            // linear search
282            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            // naive division
293            let d = x / npaths;
294            ds[i] = d as u16;
295            x -= d * npaths;
296        }
297        // } else {
298        //     // binary search
299        //     let mut low = 0;
300        //     let mut high = q;
301        //     loop {
302        //         let cur = (low + high) / 2;
303        //         let l = npaths * cur as u128;
304        //         let r = npaths * (cur as u128 + 1);
305        //         if x >= l && x < r {
306        //             x -= l;
307        //             ds[i] = cur;
308        //             break;
309        //         }
310        //         if x < l {
311        //             high = cur;
312        //         } else {
313        //             // x >= r
314        //             low = cur;
315        //         }
316        //     }
317        // }
318    }
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}