Skip to main content

fancy_garbling/
informer.rs

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