Skip to main content

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};
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 inputs.
83        for i in 0..n1 + n2 {
84            circ.gates.push(BinaryGate::Input { id: i });
85            circ.input_refs.push(i);
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        circ.const_refs.push(n1 + n2);
92        // Process outputs.
93        for i in 0..n3 {
94            circ.output_refs.push(nwires - n3 + i);
95        }
96        for line in reader.lines() {
97            let line = line.wrap_err(ErrorKind::OtherError, "Failed to read line")?;
98            match line.chars().next() {
99                Some('1') => {
100                    let cap = regex2captures(&re1, &line)?;
101                    let yref = cap2int(&cap, 1)?;
102                    let out = cap2int(&cap, 2)?;
103                    circ.gates.push(BinaryGate::Inv {
104                        xref: yref,
105                        out: Some(out),
106                    })
107                }
108                Some('2') => {
109                    let cap = regex2captures(&re2, &line)?;
110                    let xref = cap2int(&cap, 1)?;
111                    let yref = cap2int(&cap, 2)?;
112                    let out = cap2int(&cap, 3)?;
113                    let typ = cap2typ(&cap, 4)?;
114                    let gate = match typ {
115                        GateType::AndGate => {
116                            let gate = BinaryGate::And {
117                                xref,
118                                yref,
119                                id,
120                                out: Some(out),
121                            };
122                            id += 1;
123                            gate
124                        }
125                        GateType::XorGate => BinaryGate::Xor {
126                            xref,
127                            yref,
128                            out: Some(out),
129                        },
130                    };
131                    circ.gates.push(gate);
132                }
133                None => break,
134                _ => {
135                    swanky_error::bail!(ErrorKind::OtherError, "Invalid wire value");
136                }
137            }
138        }
139        Ok(circ)
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use crate::{
146        WireMod2, circuit::BinaryCircuit as Circuit, classic::GarbledCircuit, dummy::Dummy,
147    };
148    use swanky_rng::SwankyRng;
149
150    #[test]
151    fn test_parser() {
152        let circ = Circuit::parse(std::io::Cursor::<&'static [u8]>::new(include_bytes!(
153            "../circuits/AES-non-expanded.txt"
154        )))
155        .unwrap();
156        let input = [0u16; 256];
157        let output = Dummy::eval(&circ, &input).unwrap();
158        assert_eq!(
159            output.iter().map(|i| i.to_string()).collect::<String>(),
160            "01100110111010010100101111010100111011111000101000101100001110111000100001001100111110100101100111001010001101000010101100101110"
161        );
162        let mut input = vec![0u16; 128];
163        input.extend([1u16; 128]);
164        let output = Dummy::eval(&circ, &input).unwrap();
165        assert_eq!(
166            output.iter().map(|i| i.to_string()).collect::<String>(),
167            "10100001111101100010010110001100100001110111110101011111110011011000100101100100010010000100010100111000101111111100100100101100"
168        );
169        let mut input = [0u16; 256];
170        for key_part in input[128..].iter_mut().take(8) {
171            *key_part = 1;
172        }
173        let output = Dummy::eval(&circ, &input).unwrap();
174        assert_eq!(
175            output.iter().map(|i| i.to_string()).collect::<String>(),
176            "10110001110101110101100000100101011010110010100011111101100001010000101011010100100101000100001000001000110011110001000101010101"
177        );
178        let mut input = vec![0u16; 256];
179        input[128 + 7] = 1;
180        let output = Dummy::eval(&circ, &input).unwrap();
181        assert_eq!(
182            output.iter().map(|i| i.to_string()).collect::<String>(),
183            "11011100000011101101100001011101111110010110000100011010101110110111001001001001110011011101000101101000110001010100011001111110"
184        );
185    }
186
187    #[test]
188    fn test_gc_eval() {
189        let circ = Circuit::parse(std::io::Cursor::<&'static [u8]>::new(include_bytes!(
190            "../circuits/AES-non-expanded.txt"
191        )))
192        .unwrap();
193        let (encoder, gc, _) =
194            GarbledCircuit::garble::<WireMod2, _, _>(&circ, SwankyRng::new()).unwrap();
195        let inputs = encoder.encode_inputs(&vec![0u16; 256]);
196        gc.eval_to_wirelabels(&circ, &inputs).unwrap();
197    }
198}