fancy_garbling/
informer.rs

1//! `Informer` runs a fancy computation and learns information from it.
2
3use crate::{
4    FancyArithmetic, FancyBinary,
5    fancy::{Fancy, FancyInput, FancyReveal, HasModulus},
6};
7use std::collections::{HashMap, HashSet};
8use swanky_channel::Channel;
9
10/// Implements `Fancy`. Used to learn information about a `Fancy` computation in
11/// a lightweight way.
12pub struct Informer<F: Fancy> {
13    /// The underlying fancy object.
14    pub underlying: F,
15    stats: InformerStats,
16}
17
18/// The statistics revealed by the informer.
19#[derive(Clone, Debug)]
20pub struct InformerStats {
21    garbler_input_moduli: Vec<u16>,
22    evaluator_input_moduli: Vec<u16>,
23    constants: HashSet<(u16, u16)>,
24    outputs: Vec<u16>,
25    nadds: usize,
26    nsubs: usize,
27    ncmuls: usize,
28    nmuls: usize,
29    nprojs: usize,
30    nciphertexts: usize,
31    moduli: HashMap<u16, usize>,
32}
33
34impl InformerStats {
35    /// Number of garbler inputs in the fancy computation.
36    pub fn num_garbler_inputs(&self) -> usize {
37        self.garbler_input_moduli.len()
38    }
39
40    /// Moduli of garbler inputs in the fancy computation.
41    pub fn garbler_input_moduli(&self) -> Vec<u16> {
42        self.garbler_input_moduli.clone()
43    }
44
45    /// Number of evaluator inputs in the fancy computation.
46    pub fn num_evaluator_inputs(&self) -> usize {
47        self.evaluator_input_moduli.len()
48    }
49
50    /// Moduli of evaluator inputs in the fancy computation.
51    pub fn evaluator_input_moduli(&self) -> Vec<u16> {
52        self.evaluator_input_moduli.clone()
53    }
54
55    /// Number of constants in the fancy computation.
56    pub fn num_consts(&self) -> usize {
57        self.constants.len()
58    }
59
60    /// Number of outputs in the fancy computation.
61    pub fn num_outputs(&self) -> usize {
62        self.outputs.len()
63    }
64
65    /// Number of output ciphertexts.
66    pub fn num_output_ciphertexts(&self) -> usize {
67        self.outputs.iter().map(|&m| m as usize).sum()
68    }
69
70    /// Number of additions in the fancy computation.
71    pub fn num_adds(&self) -> usize {
72        self.nadds
73    }
74
75    /// Number of subtractions in the fancy computation.
76    pub fn num_subs(&self) -> usize {
77        self.nsubs
78    }
79
80    /// Number of scalar multiplications in the fancy computation.
81    pub fn num_cmuls(&self) -> usize {
82        self.ncmuls
83    }
84
85    /// Number of multiplications in the fancy computation.
86    pub fn num_muls(&self) -> usize {
87        self.nmuls
88    }
89
90    /// Number of projections in the fancy computation.
91    pub fn num_projs(&self) -> usize {
92        self.nprojs
93    }
94
95    /// Number of ciphertexts in the fancy computation.
96    pub fn num_ciphertexts(&self) -> usize {
97        self.nciphertexts
98    }
99}
100
101impl std::fmt::Display for InformerStats {
102    /// Print information about the fancy computation.
103    ///
104    /// For example, below is the output when run on `circuits/AES-non-expanded.txt`:
105    /// ```text
106    /// computation info:
107    ///   garbler inputs:                  128 // comms cost: 16 Kb
108    ///   evaluator inputs:                128 // comms cost: 48 Kb
109    ///   outputs:                         128
110    ///   output ciphertexts:              256 // comms cost: 32 Kb
111    ///   constants:                         1 // comms cost: 0.125 Kb
112    ///   additions:                     25124
113    ///   subtractions:                   1692
114    ///   cmuls:                             0
115    ///   projections:                       0
116    ///   multiplications:                6800
117    ///   ciphertexts:                   13600 // comms cost: 1.66 Mb (1700.00 Kb)
118    ///   total comms cost:            1.75 Mb // 1700.00 Kb
119    /// ```
120    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
121        let mut total = 0.0;
122        writeln!(f, "computation info:")?;
123        let comm = self.num_garbler_inputs() as f64 * 128.0 / 1000.0;
124
125        writeln!(
126            f,
127            "  garbler inputs:     {:16} // communication: {:.2} Kb",
128            self.num_garbler_inputs(),
129            comm
130        )?;
131        total += comm;
132        // The cost of IKNP is 256 bits for one random and one 128 bit string
133        // dependent on the random one. This is for each input bit, so for
134        // modulus `q` we need to do `log2(q)` OTs.
135        let comm = self.evaluator_input_moduli.iter().fold(0.0, |acc, q| {
136            acc + (*q as f64).log2().ceil() * 384.0 / 1000.0
137        });
138
139        writeln!(
140            f,
141            "  evaluator inputs:   {:16} // communication: {:.2} Kb",
142            self.num_evaluator_inputs(),
143            comm
144        )?;
145        total += comm;
146        let comm = self.num_output_ciphertexts() as f64 * 128.0 / 1000.0;
147
148        writeln!(f, "  outputs:            {:16}", self.num_outputs())?;
149        writeln!(
150            f,
151            "  output ciphertexts: {:16} // communication: {:.2} Kb",
152            self.num_output_ciphertexts(),
153            comm
154        )?;
155        total += comm;
156        let comm = self.num_consts() as f64 * 128.0 / 1000.0;
157
158        writeln!(
159            f,
160            "  constants:          {:16} // communication: {:.2} Kb",
161            self.num_consts(),
162            comm
163        )?;
164        total += comm;
165
166        writeln!(f, "  additions:          {:16}", self.num_adds())?;
167        writeln!(f, "  subtractions:       {:16}", self.num_subs())?;
168        writeln!(f, "  cmuls:              {:16}", self.num_cmuls())?;
169        writeln!(f, "  projections:        {:16}", self.num_projs())?;
170        writeln!(f, "  multiplications:    {:16}", self.num_muls())?;
171        let cs = self.num_ciphertexts();
172        let kb = cs as f64 * 128.0 / 1000.0;
173        let mb = kb / 1000.0;
174        writeln!(
175            f,
176            "  ciphertexts:        {:16} // communication: {:.2} Mb ({:.2} Kb)",
177            cs, mb, kb
178        )?;
179        total += kb;
180
181        let mb = total / 1000.0;
182        writeln!(f, "  total communication:  {:11.2} Mb", mb)?;
183        writeln!(f, "  wire moduli: {:#?}", self.moduli)?;
184        Ok(())
185    }
186}
187
188impl<F: Fancy> Informer<F> {
189    /// Make a new `Informer`.
190    pub fn new(underlying: F) -> Informer<F> {
191        Informer {
192            underlying,
193            stats: InformerStats {
194                garbler_input_moduli: Vec::new(),
195                evaluator_input_moduli: Vec::new(),
196                constants: HashSet::new(),
197                outputs: Vec::new(),
198                nadds: 0,
199                nsubs: 0,
200                ncmuls: 0,
201                nmuls: 0,
202                nprojs: 0,
203                nciphertexts: 0,
204                moduli: HashMap::new(),
205            },
206        }
207    }
208
209    /// Get the statistics collected by the `Informer`
210    pub fn stats(&self) -> InformerStats {
211        self.stats.clone()
212    }
213
214    fn update_moduli(&mut self, q: u16) {
215        let entry = self.stats.moduli.entry(q).or_insert(0);
216        *entry += 1;
217    }
218}
219
220impl<F: Fancy + FancyInput<Item = <F as Fancy>::Item>> FancyInput for Informer<F> {
221    type Item = <F as Fancy>::Item;
222
223    fn receive_many(
224        &mut self,
225        moduli: &[u16],
226        channel: &mut Channel,
227    ) -> swanky_error::Result<Vec<Self::Item>> {
228        self.stats
229            .garbler_input_moduli
230            .extend(moduli.iter().cloned());
231        self.underlying.receive_many(moduli, channel)
232    }
233
234    fn encode_many(
235        &mut self,
236        values: &[u16],
237        moduli: &[u16],
238        channel: &mut Channel,
239    ) -> swanky_error::Result<Vec<Self::Item>> {
240        self.stats
241            .garbler_input_moduli
242            .extend(moduli.iter().cloned());
243        self.underlying.encode_many(values, moduli, channel)
244    }
245}
246
247impl<F: FancyBinary> FancyBinary for Informer<F> {
248    fn xor(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
249        let result = self.underlying.xor(x, y);
250        self.stats.nadds += 1;
251        self.update_moduli(x.modulus());
252        result
253    }
254
255    fn and(
256        &mut self,
257        x: &Self::Item,
258        y: &Self::Item,
259        channel: &mut Channel,
260    ) -> swanky_error::Result<Self::Item> {
261        let result = self.underlying.and(x, y, channel)?;
262        self.stats.nmuls += 1;
263        self.stats.nciphertexts += 2;
264        self.update_moduli(x.modulus());
265        Ok(result)
266    }
267
268    fn negate(&mut self, x: &Self::Item) -> Self::Item {
269        let result = self.underlying.negate(x);
270
271        // Technically only the garbler adds: noop for the evaluator
272        self.stats.nadds += 1;
273        self.update_moduli(x.modulus());
274        result
275    }
276}
277
278impl<F: FancyArithmetic> FancyArithmetic for Informer<F> {
279    // In general, for the below, we first check to see if the result succeeds before
280    // updating the stats. That way we can avoid checking multiple times that, e.g.
281    // the moduli are equal.
282
283    fn add(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
284        let result = self.underlying.add(x, y);
285        self.stats.nadds += 1;
286        self.update_moduli(x.modulus());
287        result
288    }
289
290    fn sub(&mut self, x: &Self::Item, y: &Self::Item) -> Self::Item {
291        let result = self.underlying.sub(x, y);
292        self.stats.nsubs += 1;
293        self.update_moduli(x.modulus());
294        result
295    }
296
297    fn cmul(&mut self, x: &Self::Item, y: u16) -> Self::Item {
298        let result = self.underlying.cmul(x, y);
299        self.stats.ncmuls += 1;
300        self.update_moduli(x.modulus());
301        result
302    }
303
304    fn mul(
305        &mut self,
306        x: &Self::Item,
307        y: &Self::Item,
308        channel: &mut Channel,
309    ) -> swanky_error::Result<Self::Item> {
310        if x.modulus() < y.modulus() {
311            return self.mul(y, x, channel);
312        }
313        let result = self.underlying.mul(x, y, channel)?;
314        self.stats.nmuls += 1;
315        self.stats.nciphertexts += x.modulus() as usize + y.modulus() as usize - 2;
316        if x.modulus() != y.modulus() {
317            // there is an extra ciphertext to support nonequal inputs
318            self.stats.nciphertexts += 1;
319        }
320        self.update_moduli(x.modulus());
321        Ok(result)
322    }
323
324    fn proj(
325        &mut self,
326        x: &Self::Item,
327        q: u16,
328        tt: Option<Vec<u16>>,
329        channel: &mut Channel,
330    ) -> swanky_error::Result<Self::Item> {
331        let result = self.underlying.proj(x, q, tt, channel)?;
332        self.stats.nprojs += 1;
333        self.stats.nciphertexts += x.modulus() as usize - 1;
334        self.update_moduli(q);
335        Ok(result)
336    }
337}
338
339impl<F: Fancy> Fancy for Informer<F> {
340    type Item = F::Item;
341
342    fn constant(
343        &mut self,
344        val: u16,
345        q: u16,
346        channel: &mut Channel,
347    ) -> swanky_error::Result<Self::Item> {
348        self.stats.constants.insert((val, q));
349        self.update_moduli(q);
350        self.underlying.constant(val, q, channel)
351    }
352
353    fn output(
354        &mut self,
355        x: &Self::Item,
356        channel: &mut Channel,
357    ) -> swanky_error::Result<Option<u16>> {
358        let result = self.underlying.output(x, channel)?;
359        self.stats.outputs.push(x.modulus());
360        Ok(result)
361    }
362}
363
364impl<F: Fancy + FancyReveal> FancyReveal for Informer<F> {
365    fn reveal(&mut self, x: &Self::Item, channel: &mut Channel) -> swanky_error::Result<u16> {
366        self.underlying.reveal(x, channel)
367    }
368}