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    + core::default::Default
46    + HasModulus
47    + core::ops::Add<Output = Self>
48    + core::ops::AddAssign
49    + core::ops::Sub<Output = Self>
50    + core::ops::SubAssign
51    + core::ops::Neg<Output = Self>
52    + core::ops::Mul<u16, Output = Self>
53    + core::ops::MulAssign<u16>
54{
55    /// Converts a [`WireLabel`] into its [`U8x16`] representation.
56    fn to_repr(&self) -> U8x16;
57
58    /// The color digit of the wire.
59    fn color(&self) -> u16;
60
61    /// Converts a [`U8x16`] into its [`WireLabel`] representation, based on the
62    /// modulus `q`.
63    ///
64    /// # Panics
65    /// This panics if `q` does not align with the modulus supported by the
66    /// [`WireLabel`].
67    fn from_repr(inp: U8x16, q: u16) -> Self;
68
69    /// A random [`WireLabel`] `mod q`, with the first digit set to `1`.
70    ///
71    /// # Panics
72    /// This panics if `q` does not align with the modulus supported by the
73    /// [`WireLabel`].
74    fn rand_delta<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self;
75
76    /// A random [`WireLabel`] `mod q`.
77    ///
78    /// # Panics
79    /// This panics if `q` does not align with the modulus supported by the
80    /// [`WireLabel`].
81    fn rand<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self;
82
83    /// Converts a hashed block into a valid wire of the given modulus `q`.
84    ///
85    /// This is useful when separately using [`hash_wires`] to hash a set of
86    /// wires in one shot for efficiency reasons.
87    ///
88    /// # Panics
89    /// This panics if `q` does not align with the modulus supported by the
90    /// [`WireLabel`].
91    fn hash_to_mod(hash: U8x16, q: u16) -> Self;
92
93    /// Computes the hash of this [`WireLabel`], converting the result back into
94    /// a [`WireLabel`] based on the modulus `q`.
95    ///
96    /// This is equivalent to `WireLabel::hash_to_mod(self.hash(tweak), q)`, and
97    /// is useful when stringing together a sequence of operations on a
98    /// [`WireLabel`].
99    ///
100    /// # Panics
101    /// This panics if `q` does not align with the modulus supported by the
102    /// [`WireLabel`].
103    fn hashback(&self, tweak: u128, q: u16) -> Self {
104        let hash = self.hash(tweak);
105        Self::hash_to_mod(hash, q)
106    }
107
108    /// Computes the hash of the [`WireLabel`].
109    fn hash(&self, tweak: u128) -> U8x16 {
110        TweakableCircularCorrelationRobustHash::fixed_key().hash(self.to_repr(), tweak)
111    }
112
113    /// Computes a [`WireLabel`] for `x % q`, returning both the zero
114    /// [`WireLabel`] as well as the [`WireLabel`] for `x % q`.
115    fn constant<RNG: CryptoRng + Rng>(x: u16, q: u16, delta: &Self, rng: &mut RNG) -> (Self, Self) {
116        let zero = Self::rand(rng, q);
117        let wire = zero.clone() + delta.clone() * x;
118        (zero, wire)
119    }
120}
121
122#[derive(Debug, Clone, PartialEq)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
124/// A [`WireLabel`] that supports all possible moduli
125pub enum AllWire {
126    /// A `mod 2` [`WireLabel`].
127    Mod2(WireMod2),
128    /// A `mod 3` [`WireLabel`].
129    Mod3(WireMod3),
130    /// A `mod q` [`WireLabel`], where `3 < q < 2^16`.
131    ModN(WireModQ),
132}
133
134impl HasModulus for AllWire {
135    fn modulus(&self) -> u16 {
136        match &self {
137            AllWire::Mod2(x) => x.modulus(),
138            AllWire::Mod3(x) => x.modulus(),
139            AllWire::ModN(x) => x.modulus(),
140        }
141    }
142}
143
144impl core::default::Default for AllWire {
145    fn default() -> Self {
146        Self::Mod2(Default::default())
147    }
148}
149
150impl core::ops::Add for AllWire {
151    type Output = Self;
152
153    fn add(self, rhs: Self) -> Self::Output {
154        let (p, q) = (self.modulus(), rhs.modulus());
155        match (self, rhs) {
156            (Self::Mod2(x), Self::Mod2(y)) => Self::Mod2(x + y),
157            (Self::Mod3(x), Self::Mod3(y)) => Self::Mod3(x + y),
158            (Self::ModN(x), Self::ModN(y)) => Self::ModN(x + y),
159            _ => panic!("unequal moduli: {p} != {q}"),
160        }
161    }
162}
163
164impl core::ops::AddAssign for AllWire {
165    fn add_assign(&mut self, rhs: Self) {
166        let (p, q) = (self.modulus(), rhs.modulus());
167        match (self, rhs) {
168            (Self::Mod2(x), Self::Mod2(y)) => *x += y,
169            (Self::Mod3(x), Self::Mod3(y)) => *x += y,
170            (Self::ModN(x), Self::ModN(y)) => *x += y,
171            _ => panic!("unequal moduli: {p} != {q}"),
172        }
173    }
174}
175
176impl core::ops::Sub for AllWire {
177    type Output = Self;
178
179    fn sub(self, rhs: Self) -> Self::Output {
180        self + -rhs
181    }
182}
183
184impl core::ops::SubAssign for AllWire {
185    fn sub_assign(&mut self, rhs: Self) {
186        *self = self.clone() - rhs;
187    }
188}
189
190impl core::ops::Neg for AllWire {
191    type Output = Self;
192
193    fn neg(self) -> Self::Output {
194        match self {
195            Self::Mod2(x) => Self::Mod2(-x),
196            Self::Mod3(x) => Self::Mod3(-x),
197            Self::ModN(x) => Self::ModN(-x),
198        }
199    }
200}
201
202impl core::ops::Mul<u16> for AllWire {
203    type Output = Self;
204
205    fn mul(self, rhs: u16) -> Self::Output {
206        match self {
207            Self::Mod2(x) => Self::Mod2(x * rhs),
208            Self::Mod3(x) => Self::Mod3(x * rhs),
209            Self::ModN(x) => Self::ModN(x * rhs),
210        }
211    }
212}
213
214impl core::ops::MulAssign<u16> for AllWire {
215    fn mul_assign(&mut self, rhs: u16) {
216        match self {
217            Self::Mod2(x) => {
218                *x *= rhs;
219            }
220            Self::Mod3(x) => {
221                *x *= rhs;
222            }
223            Self::ModN(x) => {
224                *x *= rhs;
225            }
226        };
227    }
228}
229
230impl WireLabel for AllWire {
231    fn rand_delta<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self {
232        match q {
233            2 => AllWire::Mod2(WireMod2::rand_delta(rng, q)),
234            3 => AllWire::Mod3(WireMod3::rand_delta(rng, q)),
235            _ => AllWire::ModN(WireModQ::rand_delta(rng, q)),
236        }
237    }
238
239    fn to_repr(&self) -> U8x16 {
240        match &self {
241            AllWire::Mod2(x) => x.to_repr(),
242            AllWire::Mod3(x) => x.to_repr(),
243            AllWire::ModN(x) => x.to_repr(),
244        }
245    }
246    fn color(&self) -> u16 {
247        match &self {
248            AllWire::Mod2(x) => x.color(),
249            AllWire::Mod3(x) => x.color(),
250            AllWire::ModN(x) => x.color(),
251        }
252    }
253    fn from_repr(inp: U8x16, q: u16) -> Self {
254        match q {
255            2 => AllWire::Mod2(WireMod2::from_repr(inp, q)),
256            3 => AllWire::Mod3(WireMod3::from_repr(inp, q)),
257            _ => AllWire::ModN(WireModQ::from_repr(inp, q)),
258        }
259    }
260
261    fn rand<R: CryptoRng + Rng>(rng: &mut R, q: u16) -> Self {
262        match q {
263            2 => AllWire::Mod2(WireMod2::rand(rng, q)),
264            3 => AllWire::Mod3(WireMod3::rand(rng, q)),
265            _ => AllWire::ModN(WireModQ::rand(rng, q)),
266        }
267    }
268
269    fn hash_to_mod(hash: U8x16, q: u16) -> Self {
270        if q == 3 {
271            AllWire::Mod3(WireMod3::encode_block_mod3(hash))
272        } else {
273            Self::from_repr(hash, q)
274        }
275    }
276}
277fn _unrank(inp: u128, q: u16) -> Vec<u16> {
278    let mut x = inp;
279    let ndigits = util::digits_per_u128(q);
280    let npaths_tab = npaths_tab::lookup(q);
281    x %= npaths_tab[ndigits - 1] * q as u128;
282
283    let mut ds = vec![0; ndigits];
284    for i in (0..ndigits).rev() {
285        let npaths = npaths_tab[i];
286
287        if q <= 23 {
288            // linear search
289            let mut acc = 0;
290            for j in 0..q {
291                acc += npaths;
292                if acc > x {
293                    x -= acc - npaths;
294                    ds[i] = j;
295                    break;
296                }
297            }
298        } else {
299            // naive division
300            let d = x / npaths;
301            ds[i] = d as u16;
302            x -= d * npaths;
303        }
304        // } else {
305        //     // binary search
306        //     let mut low = 0;
307        //     let mut high = q;
308        //     loop {
309        //         let cur = (low + high) / 2;
310        //         let l = npaths * cur as u128;
311        //         let r = npaths * (cur as u128 + 1);
312        //         if x >= l && x < r {
313        //             x -= l;
314        //             ds[i] = cur;
315        //             break;
316        //         }
317        //         if x < l {
318        //             high = cur;
319        //         } else {
320        //             // x >= r
321        //             low = cur;
322        //         }
323        //     }
324        // }
325    }
326    ds
327}
328
329impl ArithmeticWire for AllWire {}
330
331#[cfg(test)]
332mod tests {
333    use super::*;
334    use crate::util::as_base_q_u128;
335    use fancy_circuits::util::RngExt;
336    use itertools::Itertools;
337    use rand::{RngExt as _, rng};
338
339    #[test]
340    fn packing() {
341        let rng = &mut rng();
342        for q in 2..256 {
343            for _ in 0..1000 {
344                let w = AllWire::rand(rng, q);
345                assert_eq!(w, AllWire::from_repr(w.to_repr(), q));
346            }
347        }
348    }
349
350    #[test]
351    fn base_conversion_lookup_method() {
352        let rng = &mut rng();
353        for _ in 0..1000 {
354            let q = 5 + (rng.random::<u16>() % 110);
355            let x = rng.random::<u128>();
356            let w = WireModQ::from_repr(U8x16::from(x), q);
357            let should_be = as_base_q_u128(x, q);
358            assert_eq!(w.ds, should_be, "x={} q={}", x, q);
359        }
360    }
361
362    #[test]
363    fn hash() {
364        let mut rng = rng();
365        for _ in 0..100 {
366            let q = 2 + (rng.random::<u16>() % 110);
367            let x = AllWire::rand(&mut rng, q);
368            let y = x.hashback(1u128, q);
369            assert!(x != y);
370            match y {
371                AllWire::Mod2(WireMod2 { val }) => assert!(u128::from(val) > 0),
372                AllWire::Mod3(WireMod3 { lsb, msb }) => assert!(lsb > 0 && msb > 0),
373                AllWire::ModN(WireModQ { ds, .. }) => assert!(!ds.iter().all(|&y| y == 0)),
374            }
375        }
376    }
377
378    #[test]
379    fn negation() {
380        let rng = &mut rng();
381        for _ in 0..1000 {
382            let q = rng.gen_modulus();
383            let x = AllWire::rand(rng, q);
384            let xneg = -x.clone();
385            if q != 2 {
386                assert!(x != xneg);
387            }
388            let y = -xneg;
389            assert_eq!(x, y);
390        }
391    }
392
393    #[test]
394    #[allow(clippy::erasing_op)]
395    fn arithmetic() {
396        let mut rng = rng();
397        for _ in 0..1024 {
398            let q = rng.gen_modulus();
399            let x = AllWire::rand(&mut rng, q);
400            let y = AllWire::rand(&mut rng, q);
401            assert_eq!(x.clone() * 0, x.clone() - x.clone());
402            assert_eq!(x.clone() * q, x.clone() - x.clone());
403            assert_eq!(x.clone() + x.clone(), x.clone() * 2);
404            assert_eq!(x.clone() + x.clone() + x.clone(), x.clone() * 3);
405            assert_eq!(-(-x.clone()), x);
406            if q == 2 {
407                assert_eq!(x.clone() + y.clone(), x.clone() - y.clone());
408            } else {
409                assert_eq!(x.clone() + -x.clone(), x.clone() - x.clone());
410                assert_eq!(x.clone() + -y.clone(), x.clone() - y.clone());
411            }
412            let mut w = x.clone();
413            let z = w.clone() + y.clone();
414            w += y;
415            assert_eq!(w, z);
416
417            w = x.clone();
418            w *= 2;
419            assert_eq!(x.clone() + x.clone(), w);
420
421            w = x.clone();
422            w = -w;
423            assert_eq!(-x, w);
424        }
425    }
426
427    #[test]
428    fn ndigits_correct() {
429        let mut rng = rng();
430        for _ in 0..1024 {
431            let q = rng.gen_modulus();
432            let x = WireModQ::rand(&mut rng, q);
433            assert_eq!(x.ds.len(), util::digits_per_u128(q));
434        }
435    }
436
437    #[test]
438    fn parallel_hash() {
439        let n = 1000;
440        let mut rng = rng();
441        let q = rng.gen_modulus();
442        let ws = (0..n).map(|_| AllWire::rand(&mut rng, q)).collect_vec();
443
444        let mut handles = Vec::new();
445        for w in ws.iter() {
446            let w_ = w.clone();
447            let h = std::thread::spawn(move || w_.hash(0u128));
448            handles.push(h);
449        }
450        let hashes = handles.into_iter().map(|h| h.join().unwrap()).collect_vec();
451
452        let should_be = ws.iter().map(|w| w.hash(0u128)).collect_vec();
453
454        assert_eq!(hashes, should_be);
455    }
456
457    #[cfg(feature = "serde")]
458    #[test]
459    fn test_serialize_allwire() {
460        let mut rng = rng();
461        for q in 2..16 {
462            let w = AllWire::rand(&mut rng, q);
463            let serialized = serde_json::to_string(&w).unwrap();
464
465            let deserialized: AllWire = serde_json::from_str(&serialized).unwrap();
466
467            assert_eq!(w, deserialized);
468        }
469    }
470}