Skip to main content

fancy_garbling/garble/
evaluator.rs

1use super::security_warning::warn_proj;
2use crate::{
3    AllWire, ArithmeticWire, WireMod2,
4    garble::binary_and::BinaryWireLabel,
5    hash_wires,
6    util::{output_tweak, tweak, tweak2},
7    wire::WireLabel,
8};
9use fancy_traits::{
10    Fancy, FancyArithmetic, FancyBinary, FancyEncode, FancyOutput, FancyProj, HasModulus, is_binary,
11};
12use swanky_channel::Channel;
13use swanky_error::ErrorKind;
14use vectoreyes::U8x16;
15
16/// Streaming evaluator using a callback to receive ciphertexts as needed.
17///
18/// Evaluates a garbled circuit on the fly, using messages containing ciphertexts and
19/// wires. Parallelizable.
20pub struct Evaluator<Wire> {
21    one: Wire,
22    current_gate: usize,
23    current_output: usize,
24}
25
26impl<Wire: WireLabel> Evaluator<Wire> {
27    /// Create a new [`Evaluator`].
28    pub fn new(channel: &mut Channel) -> swanky_error::Result<Self> {
29        // Receive the constant one wirelabel from the garbler. This is used to
30        // make negation free.
31        let one = channel.read::<U8x16>()?;
32        Ok(Evaluator {
33            one: Wire::from_repr(one, 2),
34            current_gate: 0,
35            current_output: 0,
36        })
37    }
38
39    /// The current non-free gate index of the garbling computation.
40    fn current_gate(&mut self) -> usize {
41        let current = self.current_gate;
42        self.current_gate += 1;
43        current
44    }
45
46    /// The current output index of the garbling computation.
47    fn current_output(&mut self) -> usize {
48        let current = self.current_output;
49        self.current_output += 1;
50        current
51    }
52}
53
54impl<W: BinaryWireLabel> FancyBinary for Evaluator<W> {
55    /// Negate is a noop for the evaluator
56    fn negate(&mut self, x: &Self::Item) -> Self::Item {
57        *x + self.one
58    }
59
60    fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
61        *x + *y
62    }
63
64    fn and(
65        &mut self,
66        A: &Self::Item,
67        B: &Self::Item,
68        channel: &mut Channel,
69    ) -> swanky_error::Result<Self::Item> {
70        let gate_num = self.current_gate();
71        let gate0 = channel.read()?;
72        let gate1 = channel.read()?;
73        Ok(W::evaluate_and_gate(gate_num, A, B, &gate0, &gate1))
74    }
75}
76
77impl FancyBinary for Evaluator<AllWire> {
78    /// Overriding `negate` to be a noop: entirely handled on garbler's end
79    fn negate(&mut self, x: &Self::Item) -> Self::Item {
80        is_binary!(x);
81
82        x.clone() + self.one.clone()
83    }
84
85    fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
86        is_binary!(x);
87        is_binary!(y);
88
89        self.add(x, y)
90    }
91
92    fn and(
93        &mut self,
94        x: &Self::Item,
95        y: &Self::Item,
96        channel: &mut Channel,
97    ) -> swanky_error::Result<Self::Item> {
98        if let (AllWire::Mod2(A), AllWire::Mod2(B)) = (x, y) {
99            let gate_num = self.current_gate();
100            let gate0 = channel.read()?;
101            let gate1 = channel.read()?;
102            return Ok(AllWire::Mod2(WireMod2::evaluate_and_gate(
103                gate_num, A, B, &gate0, &gate1,
104            )));
105        }
106
107        // If we got here, one of the wires isn't binary
108        is_binary!(x);
109        is_binary!(y);
110
111        // Shouldn't be reachable, unless the wire has modulus 2 but is not AllWire::Mod2()
112        unreachable!()
113    }
114}
115
116impl<Wire: WireLabel + ArithmeticWire> FancyArithmetic for Evaluator<Wire> {
117    fn add(&mut self, x: &Wire, y: &Wire) -> Wire {
118        assert_eq!(x.modulus(), y.modulus());
119        x.clone() + y.clone()
120    }
121
122    fn sub(&mut self, x: &Wire, y: &Wire) -> Wire {
123        assert_eq!(x.modulus(), y.modulus());
124        x.clone() - y.clone()
125    }
126
127    fn cmul(&mut self, x: &Wire, c: u16) -> Wire {
128        x.clone() * c
129    }
130
131    fn mul(&mut self, A: &Wire, B: &Wire, channel: &mut Channel) -> swanky_error::Result<Wire> {
132        if A.modulus() < B.modulus() {
133            return self.mul(B, A, channel);
134        }
135        let q = A.modulus();
136        let qb = B.modulus();
137        let unequal = q != qb;
138        let ngates = q as usize + qb as usize - 2 + unequal as usize;
139        let mut gate = Vec::with_capacity(ngates);
140        {
141            for _ in 0..ngates {
142                let block = channel.read::<U8x16>()?;
143                gate.push(block);
144            }
145        }
146        let gate_num = self.current_gate();
147        let g = tweak2(gate_num as u64, 0);
148
149        let [hashA, hashB] = hash_wires([A, B], g);
150
151        // garbler's half gate
152        let L = if A.color() == 0 {
153            Wire::hash_to_mod(hashA, q)
154        } else {
155            let ct_left = gate[A.color() as usize - 1];
156            Wire::from_repr(ct_left ^ hashA, q)
157        };
158
159        // evaluator's half gate
160        let R = if B.color() == 0 {
161            Wire::hash_to_mod(hashB, q)
162        } else {
163            let ct_right = gate[(q + B.color()) as usize - 2];
164            Wire::from_repr(ct_right ^ hashB, q)
165        };
166
167        // hack for unequal mods
168        // TODO: Batch this with original hash if unequal.
169        let new_b_color = if unequal {
170            let minitable = *gate.last().unwrap();
171            let ct = u128::from(minitable) >> (B.color() * 16);
172            let pt = u128::from(B.hash(tweak2(gate_num as u64, 1))) ^ ct;
173            pt as u16
174        } else {
175            B.color()
176        };
177
178        let res = L + R + A.clone() * new_b_color;
179        Ok(res)
180    }
181}
182
183impl<Wire: WireLabel + ArithmeticWire> FancyProj for Evaluator<Wire> {
184    fn proj(
185        &mut self,
186        x: &Wire,
187        q: u16,
188        _: Option<Vec<u16>>,
189        channel: &mut Channel,
190    ) -> swanky_error::Result<Wire> {
191        warn_proj();
192        let ngates = (x.modulus() - 1) as usize;
193        let mut gate = Vec::with_capacity(ngates);
194        for _ in 0..ngates {
195            let block = channel.read::<U8x16>()?;
196            gate.push(block);
197        }
198        let t = tweak(self.current_gate());
199        if x.color() == 0 {
200            Ok(x.hashback(t, q))
201        } else {
202            let ct = gate[x.color() as usize - 1];
203            Ok(Wire::from_repr(ct ^ x.hash(t), q))
204        }
205    }
206}
207
208impl<Wire: WireLabel> Fancy for Evaluator<Wire> {
209    type Item = Wire;
210
211    fn constant(&mut self, _: u16, q: u16, channel: &mut Channel) -> swanky_error::Result<Wire> {
212        Ok(Wire::from_repr(channel.read()?, q))
213    }
214}
215
216impl<Wire: WireLabel> FancyEncode for Evaluator<Wire> {
217    fn encode_many(
218        &mut self,
219        _values: &[u16],
220        _moduli: &[u16],
221        _: &mut Channel,
222    ) -> swanky_error::Result<Vec<Self::Item>> {
223        unimplemented!("Evaluator cannot encode values")
224    }
225
226    fn receive_many(
227        &mut self,
228        moduli: &[u16],
229        channel: &mut Channel,
230    ) -> swanky_error::Result<Vec<Self::Item>> {
231        moduli
232            .iter()
233            .map(|q| {
234                let block = channel.read()?;
235                Ok(Wire::from_repr(block, *q))
236            })
237            .collect()
238    }
239}
240
241impl<Wire: WireLabel> FancyOutput for Evaluator<Wire> {
242    fn output(&mut self, x: &Wire, channel: &mut Channel) -> swanky_error::Result<Option<u16>> {
243        let q = x.modulus();
244        let i = self.current_output();
245
246        // Receive the output ciphertext from the garbler
247        let mut ct = Vec::with_capacity(q as usize);
248        for _ in 0..q {
249            let block = channel.read()?;
250            ct.push(block);
251        }
252
253        // Attempt to brute force x using the output ciphertext
254        let mut decoded = None;
255        for k in 0..q {
256            let hashed_wire = x.hash(output_tweak(i, k));
257            if hashed_wire == ct[k as usize] {
258                decoded = Some(k);
259                break;
260            }
261        }
262
263        if let Some(output) = decoded {
264            Ok(Some(output))
265        } else {
266            swanky_error::bail!(ErrorKind::OtherError, "Decoding failed");
267        }
268    }
269}