fancy_garbling/
parser.rs

1//! Functions for parsing and running a circuit file based on the format given
2//! here: <https://homes.esat.kuleuven.be/~nsmart/MPC/>.
3
4use crate::{
5    circuit::{BinaryCircuit, BinaryGate, CircuitRef, CircuitType},
6    errors::CircuitParserError as Error,
7};
8use regex::{Captures, Regex};
9use std::str::FromStr;
10
11enum GateType {
12    AndGate,
13    XorGate,
14}
15
16fn cap2int(cap: &Captures, idx: usize) -> Result<usize, Error> {
17    let s = cap.get(idx).ok_or(Error::ParseIntError)?;
18    FromStr::from_str(s.as_str()).map_err(Error::from)
19}
20
21fn cap2typ(cap: &Captures, idx: usize) -> Result<GateType, Error> {
22    let s = cap.get(idx).ok_or(Error::ParseIntError)?;
23    let s = s.as_str();
24    match s {
25        "AND" => Ok(GateType::AndGate),
26        "XOR" => Ok(GateType::XorGate),
27        s => Err(Error::ParseGateError(s.to_string())),
28    }
29}
30
31fn regex2captures<'t>(re: &Regex, line: &'t str) -> Result<Captures<'t>, Error> {
32    re.captures(line)
33        .ok_or_else(|| Error::ParseLineError(line.to_string()))
34}
35
36impl BinaryCircuit {
37    /// Generates a new `Circuit` from file `filename`. The file must follow the
38    /// format given here: <https://homes.esat.kuleuven.be/~nsmart/MPC/old-circuits.html>,
39    /// (Bristol Format---the OLD format---not Bristol Fashion---the NEW format) otherwise
40    /// a `CircuitParserError` is returned.
41    pub fn parse(mut reader: impl std::io::BufRead) -> Result<Self, Error> {
42        // Parse first line: ngates nwires\n
43        let mut line = String::new();
44        reader.read_line(&mut line)?;
45        let re = Regex::new(r"(\d+)\s+(\d+)")?;
46        let cap = regex2captures(&re, &line)?;
47        let ngates = cap2int(&cap, 1)?;
48        let nwires = cap2int(&cap, 2)?;
49
50        // Parse second line: n1 n2 n3\n
51        let mut line = String::new();
52        reader.read_line(&mut line)?;
53        let re = Regex::new(r"(\d+)\s+(\d+)\s+(\d+)")?;
54        let cap = regex2captures(&re, &line)?;
55        let n1 = cap2int(&cap, 1)?; // Number of garbler inputs
56        let n2 = cap2int(&cap, 2)?; // Number of evaluator inputs
57        let n3 = cap2int(&cap, 3)?; // Number of outputs
58
59        // Parse third line: \n
60        let mut line = String::new();
61        reader.read_line(&mut line)?;
62        #[allow(clippy::trivial_regex)]
63        let re = Regex::new(r"\n")?;
64        let _ = regex2captures(&re, &line)?;
65
66        let mut circ = Self::new(Some(ngates));
67
68        let re1 = Regex::new(r"1 1 (\d+) (\d+) INV")?;
69        let re2 = Regex::new(r"2 1 (\d+) (\d+) (\d+) ((AND|XOR))")?;
70
71        let mut id = 0;
72
73        // Process garbler inputs.
74        for i in 0..n1 {
75            circ.gates.push(BinaryGate::GarblerInput { id: i });
76            circ.garbler_input_refs
77                .push(CircuitRef { ix: i, modulus: 2 });
78        }
79        // Process evaluator inputs.
80        for i in 0..n2 {
81            circ.gates.push(BinaryGate::EvaluatorInput { id: i });
82            circ.evaluator_input_refs.push(CircuitRef {
83                ix: n1 + i,
84                modulus: 2,
85            });
86        }
87        // Create a constant wire for negations.
88        // This is no longer required for the implementation
89        // of our garbler/evaluator pair. Consider removing
90        circ.gates.push(BinaryGate::Constant { val: 1 });
91        let oneref = CircuitRef {
92            ix: n1 + n2,
93            modulus: 2,
94        };
95        circ.const_refs.push(oneref);
96        // Process outputs.
97        for i in 0..n3 {
98            circ.output_refs.push(CircuitRef {
99                ix: nwires - n3 + i,
100                modulus: 2,
101            });
102        }
103        for line in reader.lines() {
104            let line = line?;
105            match line.chars().next() {
106                Some('1') => {
107                    let cap = regex2captures(&re1, &line)?;
108                    let yref = cap2int(&cap, 1)?;
109                    let out = cap2int(&cap, 2)?;
110                    let yref = CircuitRef {
111                        ix: yref,
112                        modulus: 2,
113                    };
114                    circ.gates.push(BinaryGate::Inv {
115                        xref: yref,
116                        out: Some(out),
117                    })
118                }
119                Some('2') => {
120                    let cap = regex2captures(&re2, &line)?;
121                    let xref = cap2int(&cap, 1)?;
122                    let yref = cap2int(&cap, 2)?;
123                    let out = cap2int(&cap, 3)?;
124                    let typ = cap2typ(&cap, 4)?;
125                    let xref = CircuitRef {
126                        ix: xref,
127                        modulus: 2,
128                    };
129                    let yref = CircuitRef {
130                        ix: yref,
131                        modulus: 2,
132                    };
133                    let gate = match typ {
134                        GateType::AndGate => {
135                            let gate = BinaryGate::And {
136                                xref,
137                                yref,
138                                id,
139                                out: Some(out),
140                            };
141                            id += 1;
142                            gate
143                        }
144                        GateType::XorGate => BinaryGate::Xor {
145                            xref,
146                            yref,
147                            out: Some(out),
148                        },
149                    };
150                    circ.gates.push(gate);
151                }
152                None => break,
153                _ => {
154                    return Err(Error::ParseLineError(line.to_string()));
155                }
156            }
157        }
158        Ok(circ)
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use crate::{
165        WireMod2,
166        circuit::{BinaryCircuit as Circuit, eval_plain},
167        classic::garble,
168    };
169
170    #[test]
171    fn test_parser() {
172        let circ = Circuit::parse(std::io::Cursor::<&'static [u8]>::new(include_bytes!(
173            "../circuits/AES-non-expanded.txt"
174        )))
175        .unwrap();
176        let key = vec![0u16; 128];
177        let pt = vec![0u16; 128];
178        let output = eval_plain(&circ, &pt, &key).unwrap();
179        assert_eq!(
180            output.iter().map(|i| i.to_string()).collect::<String>(),
181            "01100110111010010100101111010100111011111000101000101100001110111000100001001100111110100101100111001010001101000010101100101110"
182        );
183        let key = vec![1u16; 128];
184        let pt = vec![0u16; 128];
185        let output = eval_plain(&circ, &pt, &key).unwrap();
186        assert_eq!(
187            output.iter().map(|i| i.to_string()).collect::<String>(),
188            "10100001111101100010010110001100100001110111110101011111110011011000100101100100010010000100010100111000101111111100100100101100"
189        );
190        let mut key = vec![0u16; 128];
191        for i in 0..8 {
192            key[i] = 1;
193        }
194        let pt = vec![0u16; 128];
195        let output = eval_plain(&circ, &pt, &key).unwrap();
196        assert_eq!(
197            output.iter().map(|i| i.to_string()).collect::<String>(),
198            "10110001110101110101100000100101011010110010100011111101100001010000101011010100100101000100001000001000110011110001000101010101"
199        );
200        let mut key = vec![0u16; 128];
201        key[7] = 1;
202        let pt = vec![0u16; 128];
203        let output = eval_plain(&circ, &pt, &key).unwrap();
204        assert_eq!(
205            output.iter().map(|i| i.to_string()).collect::<String>(),
206            "11011100000011101101100001011101111110010110000100011010101110110111001001001001110011011101000101101000110001010100011001111110"
207        );
208    }
209
210    #[test]
211    fn test_gc_eval() {
212        let circ = Circuit::parse(std::io::Cursor::<&'static [u8]>::new(include_bytes!(
213            "../circuits/AES-non-expanded.txt"
214        )))
215        .unwrap();
216        let (en, gc) = garble::<WireMod2, _>(&circ).unwrap();
217        let gb = en.encode_garbler_inputs(&vec![0u16; 128]);
218        let ev = en.encode_evaluator_inputs(&vec![0u16; 128]);
219        gc.eval(&circ, &gb, &ev).unwrap();
220    }
221}