fancy_garbling/garble/
garbler.rs

1use crate::{
2    AllWire, ArithmeticWire, FancyArithmetic, FancyBinary, HasModulus, WireLabel, WireMod2,
3    check_binary,
4    errors::{FancyError, GarblerError},
5    fancy::{BinaryBundle, CrtBundle, Fancy, FancyReveal},
6    hash_wires,
7    util::{RngExt, output_tweak, tweak, tweak2},
8};
9use rand::{CryptoRng, RngCore};
10#[cfg(feature = "serde")]
11use serde::de::DeserializeOwned;
12use std::collections::HashMap;
13use subtle::ConditionallySelectable;
14use swanky_block::Block;
15use swanky_channel_legacy::AbstractChannel;
16
17use super::security_warning::warn_proj;
18
19/// Streams garbled circuit ciphertexts through a callback.
20pub struct Garbler<C, RNG, Wire> {
21    pub(crate) channel: C,
22    deltas: HashMap<u16, Wire>, // map from modulus to associated delta wire-label.
23    current_output: usize,
24    current_gate: usize,
25    rng: RNG,
26}
27
28#[cfg(feature = "serde")]
29impl<C: AbstractChannel, RNG: CryptoRng + RngCore, Wire: WireLabel + DeserializeOwned>
30    Garbler<C, RNG, Wire>
31{
32    /// Load pre-chosen deltas from a file
33    pub fn load_deltas(&mut self, filename: &str) -> Result<(), Box<dyn std::error::Error>> {
34        let f = std::fs::File::open(filename)?;
35        let reader = std::io::BufReader::new(f);
36        let deltas: HashMap<u16, Wire> = serde_json::from_reader(reader)?;
37        self.deltas.extend(deltas.into_iter());
38        Ok(())
39    }
40}
41
42impl<C: AbstractChannel, RNG: CryptoRng + RngCore, Wire: WireLabel> Garbler<C, RNG, Wire> {
43    /// Create a new garbler.
44    pub fn new(channel: C, rng: RNG) -> Self {
45        Garbler {
46            channel,
47            deltas: HashMap::new(),
48            current_gate: 0,
49            current_output: 0,
50            rng,
51        }
52    }
53
54    /// The current non-free gate index of the garbling computation
55    fn current_gate(&mut self) -> usize {
56        let current = self.current_gate;
57        self.current_gate += 1;
58        current
59    }
60
61    /// Create a delta if it has not been created yet for this modulus, otherwise just
62    /// return the existing one.
63    pub fn delta(&mut self, q: u16) -> Wire {
64        if let Some(delta) = self.deltas.get(&q) {
65            return delta.clone();
66        }
67        let w = Wire::rand_delta(&mut self.rng, q);
68        self.deltas.insert(q, w.clone());
69        w
70    }
71
72    /// The current output index of the garbling computation.
73    fn current_output(&mut self) -> usize {
74        let current = self.current_output;
75        self.current_output += 1;
76        current
77    }
78
79    /// Get the deltas, consuming the Garbler.
80    ///
81    /// This is useful for reusing wires in multiple garbled circuit instances.
82    pub fn get_deltas(self) -> HashMap<u16, Wire> {
83        self.deltas
84    }
85
86    /// Send a wire over the established channel.
87    pub fn send_wire(&mut self, wire: &Wire) -> Result<(), GarblerError> {
88        self.channel.write_block(&wire.as_block())?;
89        Ok(())
90    }
91
92    /// Encode a wire, producing the zero wire as well as the encoded value.
93    pub fn encode_wire(&mut self, val: u16, modulus: u16) -> (Wire, Wire) {
94        let zero = Wire::rand(&mut self.rng, modulus);
95        let delta = self.delta(modulus);
96        let enc = zero.plus(&delta.cmul(val));
97        (zero, enc)
98    }
99
100    /// Encode many wires, producing zero wires as well as encoded values.
101    pub fn encode_many_wires(
102        &mut self,
103        vals: &[u16],
104        moduli: &[u16],
105    ) -> Result<(Vec<Wire>, Vec<Wire>), GarblerError> {
106        if vals.len() != moduli.len() {
107            return Err(GarblerError::EncodingError);
108        }
109        assert!(vals.len() == moduli.len());
110        let mut gbs = Vec::with_capacity(vals.len());
111        let mut evs = Vec::with_capacity(vals.len());
112        for (x, q) in vals.iter().zip(moduli.iter()) {
113            let (gb, ev) = self.encode_wire(*x, *q);
114            gbs.push(gb);
115            evs.push(ev);
116        }
117        Ok((gbs, evs))
118    }
119
120    /// Encode a `CrtBundle`, producing zero wires as well as encoded values.
121    pub fn crt_encode_wire(
122        &mut self,
123        val: u128,
124        modulus: u128,
125    ) -> Result<(CrtBundle<Wire>, CrtBundle<Wire>), GarblerError> {
126        let ms = crate::util::factor(modulus);
127        let xs = crate::util::crt(val, &ms);
128        let (gbs, evs) = self.encode_many_wires(&xs, &ms)?;
129        Ok((CrtBundle::new(gbs), CrtBundle::new(evs)))
130    }
131
132    /// Encode a `BinaryBundle`, producing zero wires as well as encoded values.
133    pub fn bin_encode_wire(
134        &mut self,
135        val: u128,
136        nbits: usize,
137    ) -> Result<(BinaryBundle<Wire>, BinaryBundle<Wire>), GarblerError> {
138        let xs = crate::util::u128_to_bits(val, nbits);
139        let ms = vec![2; nbits];
140        let (gbs, evs) = self.encode_many_wires(&xs, &ms)?;
141        Ok((BinaryBundle::new(gbs), BinaryBundle::new(evs)))
142    }
143
144    /// Garbles an 'and' gate given two input wires and the delta.
145    ///
146    /// Outputs a tuple consisting of the two gates (that should be transfered to the evaluator)
147    /// and the next wire label for the garbler.
148    ///
149    /// Used internally as a subroutine to implement 'and' gates for `FancyBinary`.
150    fn garble_and_gate(
151        &mut self,
152        A: &WireMod2,
153        B: &WireMod2,
154        delta: &WireMod2,
155    ) -> (Block, Block, WireMod2) {
156        let q = A.modulus();
157        let D = delta;
158        let gate_num = self.current_gate();
159
160        let r = B.color(); // secret value known only to the garbler (ev knows r+b)
161
162        let g = tweak2(gate_num as u64, 0);
163
164        // X = H(A+aD) + arD such that a + A.color == 0
165        let alpha = A.color(); // alpha = -A.color
166        let X1 = A.plus(&D.cmul(alpha));
167
168        // Y = H(B + bD) + (b + r)A such that b + B.color == 0
169        let beta = (q - B.color()) % q;
170        let Y1 = B.plus(&D.cmul(beta));
171
172        let AD = A.plus(D);
173        let BD = B.plus(D);
174
175        // idx is always boolean for binary gates, so it can be represented as a `u8`
176        let a_selector = (A.color() as u8).into();
177        let b_selector = (B.color() as u8).into();
178
179        let B = WireMod2::conditional_select(&BD, B, b_selector);
180        let newA = WireMod2::conditional_select(&AD, A, a_selector);
181        let idx = u8::conditional_select(&(r as u8), &0u8, a_selector);
182
183        let [hashA, hashB, hashX, hashY] = hash_wires([&newA, &B, &X1, &Y1], g);
184
185        let X = WireMod2::hash_to_mod(hashX, q).plus_mov(&D.cmul(alpha * r % q));
186        let Y = WireMod2::hash_to_mod(hashY, q);
187
188        let gate0 =
189            hashA ^ Block::conditional_select(&X.as_block(), &X.plus(D).as_block(), idx.into());
190        let gate1 = hashB ^ Y.plus(A).as_block();
191
192        (gate0, gate1, X.plus_mov(&Y))
193    }
194}
195
196impl<C: AbstractChannel, RNG: RngCore + CryptoRng, Wire: WireLabel> FancyReveal
197    for Garbler<C, RNG, Wire>
198{
199    fn reveal(&mut self, x: &Wire) -> Result<u16, GarblerError> {
200        // The evaluator needs our cooperation in order to see the output.
201        // Hence, we call output() ourselves.
202        self.output(x)?;
203        self.channel.flush()?;
204        let val = self.channel.read_u16()?;
205        Ok(val)
206    }
207}
208
209impl<C: AbstractChannel, RNG: RngCore + CryptoRng> FancyBinary for Garbler<C, RNG, WireMod2> {
210    fn and(&mut self, A: &Self::Item, B: &Self::Item) -> Result<Self::Item, Self::Error> {
211        let delta = self.delta(2);
212        let (gate0, gate1, C) = self.garble_and_gate(A, B, &delta);
213        self.channel.write_block(&gate0)?;
214        self.channel.write_block(&gate1)?;
215        Ok(C)
216    }
217
218    fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Result<Self::Item, Self::Error> {
219        Ok(x.plus(y))
220    }
221
222    /// We can negate by having garbler xor wire with Delta
223    ///
224    /// Since we treat all garbler wires as zero,
225    /// xoring with delta conceptually negates the value of the wire
226    fn negate(&mut self, x: &Self::Item) -> Result<Self::Item, Self::Error> {
227        let delta = self.delta(2);
228        self.xor(&delta, x)
229    }
230}
231
232impl<C: AbstractChannel, RNG: RngCore + CryptoRng> FancyBinary for Garbler<C, RNG, AllWire> {
233    /// We can negate by having garbler xor wire with Delta
234    ///
235    /// Since we treat all garbler wires as zero,
236    /// xoring with delta conceptually negates the value of the wire
237    fn negate(&mut self, x: &Self::Item) -> Result<Self::Item, Self::Error> {
238        check_binary!(x);
239
240        let delta = self.delta(2);
241        self.xor(&delta, x)
242    }
243
244    /// Xor is just addition
245    fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Result<Self::Item, Self::Error> {
246        check_binary!(x);
247        check_binary!(y);
248
249        self.add(x, y)
250    }
251
252    /// Use binary and_gate
253    fn and(&mut self, x: &Self::Item, y: &Self::Item) -> Result<Self::Item, Self::Error> {
254        if let (AllWire::Mod2(A), AllWire::Mod2(B), AllWire::Mod2(ref delta)) =
255            (x, y, self.delta(2))
256        {
257            let (gate0, gate1, C) = self.garble_and_gate(A, B, delta);
258            self.channel.write_block(&gate0)?;
259            self.channel.write_block(&gate1)?;
260            return Ok(AllWire::Mod2(C));
261        }
262        // If we got here, one of the wires isn't binary
263        check_binary!(x);
264        check_binary!(y);
265
266        // Shouldn't be reachable, unless the wire has modulus 2 but is not AllWire::Mod2()
267        unreachable!()
268    }
269}
270
271impl<C: AbstractChannel, RNG: RngCore + CryptoRng, Wire: WireLabel + ArithmeticWire> FancyArithmetic
272    for Garbler<C, RNG, Wire>
273{
274    fn add(&mut self, x: &Wire, y: &Wire) -> Result<Wire, GarblerError> {
275        if x.modulus() != y.modulus() {
276            return Err(GarblerError::FancyError(FancyError::UnequalModuli));
277        }
278        Ok(x.plus(y))
279    }
280
281    fn sub(&mut self, x: &Wire, y: &Wire) -> Result<Wire, GarblerError> {
282        if x.modulus() != y.modulus() {
283            return Err(GarblerError::FancyError(FancyError::UnequalModuli));
284        }
285        Ok(x.minus(y))
286    }
287
288    fn cmul(&mut self, x: &Wire, c: u16) -> Result<Wire, GarblerError> {
289        Ok(x.cmul(c))
290    }
291
292    fn mul(&mut self, A: &Wire, B: &Wire) -> Result<Wire, GarblerError> {
293        if A.modulus() < B.modulus() {
294            return self.mul(B, A);
295        }
296
297        let q = A.modulus();
298        let qb = B.modulus();
299        let gate_num = self.current_gate();
300
301        let D = self.delta(q);
302        let Db = self.delta(qb);
303
304        let r;
305        let mut gate = vec![Block::default(); q as usize + qb as usize - 2];
306
307        // hack for unequal moduli
308        if q != qb {
309            // would need to pack minitable into more than one u128 to support qb > 8
310            if qb > 8 {
311                return Err(GarblerError::AsymmetricHalfGateModuliMax8(qb));
312            }
313
314            r = self.rng.gen_u16() % q;
315            let t = tweak2(gate_num as u64, 1);
316
317            let mut minitable = vec![u128::default(); qb as usize];
318            let mut B_ = B.clone();
319            for b in 0..qb {
320                if b > 0 {
321                    B_.plus_eq(&Db);
322                }
323                let new_color = ((r + b) % q) as u128;
324                let ct = (u128::from(B_.hash(t)) & 0xFFFF) ^ new_color;
325                minitable[B_.color() as usize] = ct;
326            }
327
328            let mut packed = 0;
329            for i in 0..qb as usize {
330                packed += minitable[i] << (16 * i);
331            }
332            gate.push(Block::from(packed));
333        } else {
334            r = B.color(); // secret value known only to the garbler (ev knows r+b)
335        }
336
337        let g = tweak2(gate_num as u64, 0);
338
339        // X = H(A+aD) + arD such that a + A.color == 0
340        let alpha = (q - A.color()) % q; // alpha = -A.color
341        let X1 = A.plus(&D.cmul(alpha));
342
343        // Y = H(B + bD) + (b + r)A such that b + B.color == 0
344        let beta = (qb - B.color()) % qb;
345        let Y1 = B.plus(&Db.cmul(beta));
346
347        let [hashX, hashY] = hash_wires([&X1, &Y1], g);
348
349        let X = Wire::hash_to_mod(hashX, q).plus_mov(&D.cmul(alpha * r % q));
350        let Y = Wire::hash_to_mod(hashY, q).plus_mov(&A.cmul((beta + r) % q));
351
352        let mut precomp = Vec::with_capacity(q as usize);
353        // precompute a lookup table of X.minus(&D_cmul[(a * r % q)])
354        //                            = X.plus(&D_cmul[((q - (a * r % q)) % q)])
355        let mut X_ = X.clone();
356        precomp.push(X_.as_block());
357        for _ in 1..q {
358            X_.plus_eq(&D);
359            precomp.push(X_.as_block());
360        }
361
362        // We can vectorize the hashes here too, but then we need to precompute all `q` sums of A
363        // with delta [A, A + D, A + D + D, etc.]
364        // Would probably need another alloc which isn't great
365        let mut A_ = A.clone();
366        for a in 0..q {
367            if a > 0 {
368                A_.plus_eq(&D);
369            }
370            // garbler's half-gate: outputs X-arD
371            // G = H(A+aD) ^ X+a(-r)D = H(A+aD) ^ X-arD
372            if A_.color() != 0 {
373                gate[A_.color() as usize - 1] =
374                    A_.hash(g) ^ precomp[((q - (a * r % q)) % q) as usize];
375            }
376        }
377        precomp.clear();
378
379        // precompute a lookup table of Y.minus(&A_cmul[((b+r) % q)])
380        //                            = Y.plus(&A_cmul[((q - ((b+r) % q)) % q)])
381        let mut Y_ = Y.clone();
382        precomp.push(Y_.as_block());
383        for _ in 1..q {
384            Y_.plus_eq(A);
385            precomp.push(Y_.as_block());
386        }
387
388        // Same note about vectorization as A
389        let mut B_ = B.clone();
390        for b in 0..qb {
391            if b > 0 {
392                B_.plus_eq(&Db);
393            }
394            // evaluator's half-gate: outputs Y-(b+r)D
395            // G = H(B+bD) + Y-(b+r)A
396            if B_.color() != 0 {
397                gate[q as usize - 1 + B_.color() as usize - 1] =
398                    B_.hash(g) ^ precomp[((q - ((b + r) % q)) % q) as usize];
399            }
400        }
401
402        for block in gate.iter() {
403            self.channel.write_block(block)?;
404        }
405        Ok(X.plus_mov(&Y))
406    }
407
408    fn proj(&mut self, A: &Wire, q_out: u16, tt: Option<Vec<u16>>) -> Result<Wire, GarblerError> {
409        warn_proj();
410        let tt = tt.ok_or(GarblerError::TruthTableRequired)?;
411
412        let q_in = A.modulus();
413        let mut gate = vec![Block::default(); q_in as usize - 1];
414
415        let tao = A.color();
416        let g = tweak(self.current_gate());
417
418        let Din = self.delta(q_in);
419        let Dout = self.delta(q_out);
420
421        // output zero-wire
422        // W_g^0 <- -H(g, W_{a_1}^0 - \tao\Delta_m) - \phi(-\tao)\Delta_n
423        let C = A
424            .plus(&Din.cmul((q_in - tao) % q_in))
425            .hashback(g, q_out)
426            .plus_mov(&Dout.cmul((q_out - tt[((q_in - tao) % q_in) as usize]) % q_out));
427
428        // precompute `let C_ = C.plus(&Dout.cmul(tt[x as usize]))`
429        let C_precomputed = {
430            let mut C_ = C.clone();
431            (0..q_out)
432                .map(|x| {
433                    if x > 0 {
434                        C_.plus_eq(&Dout);
435                    }
436                    C_.as_block()
437                })
438                .collect::<Vec<Block>>()
439        };
440
441        let mut A_ = A.clone();
442        for x in 0..q_in {
443            if x > 0 {
444                A_.plus_eq(&Din); // avoiding expensive cmul for `A_ = A.plus(&Din.cmul(x))`
445            }
446
447            let ix = (tao as usize + x as usize) % q_in as usize;
448            if ix == 0 {
449                continue;
450            }
451
452            let ct = A_.hash(g) ^ C_precomputed[tt[x as usize] as usize];
453            gate[ix - 1] = ct;
454        }
455
456        for block in gate.iter() {
457            self.channel.write_block(block)?;
458        }
459        Ok(C)
460    }
461}
462
463impl<C: AbstractChannel, RNG: RngCore + CryptoRng, Wire: WireLabel> Fancy
464    for Garbler<C, RNG, Wire>
465{
466    type Item = Wire;
467    type Error = GarblerError;
468
469    fn constant(&mut self, x: u16, q: u16) -> Result<Wire, GarblerError> {
470        let zero = Wire::rand(&mut self.rng, q);
471        let wire = zero.plus(self.delta(q).cmul_eq(x));
472        self.send_wire(&wire)?;
473        Ok(zero)
474    }
475
476    fn output(&mut self, X: &Wire) -> Result<Option<u16>, GarblerError> {
477        let q = X.modulus();
478        let i = self.current_output();
479        let D = self.delta(q);
480        for k in 0..q {
481            let block = X.plus(&D.cmul(k)).hash(output_tweak(i, k));
482            self.channel.write_block(&block)?;
483        }
484        Ok(None)
485    }
486}