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