From 95e6aab74506c1754993f69b0db0baa26aa49c75 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Tue, 30 Jan 2024 11:32:36 +0100 Subject: [PATCH 1/9] Initial MPC circuit specification based on `garble_lang` --- .gitignore | 1 + mpc-spec/Cargo.lock | 7 ++ mpc-spec/Cargo.toml | 8 ++ mpc-spec/src/circuit.rs | 225 ++++++++++++++++++++++++++++++++++++++++ mpc-spec/src/lib.rs | 6 ++ 5 files changed, 247 insertions(+) create mode 100644 mpc-spec/Cargo.lock create mode 100644 mpc-spec/Cargo.toml create mode 100644 mpc-spec/src/circuit.rs create mode 100644 mpc-spec/src/lib.rs diff --git a/.gitignore b/.gitignore index 14f7811..964ee35 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /mpc-knowledge-base/book/* +/mpc-spec/target diff --git a/mpc-spec/Cargo.lock b/mpc-spec/Cargo.lock new file mode 100644 index 0000000..58d4e70 --- /dev/null +++ b/mpc-spec/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "mpc-spec" +version = "0.1.0" diff --git a/mpc-spec/Cargo.toml b/mpc-spec/Cargo.toml new file mode 100644 index 0000000..a98e9a0 --- /dev/null +++ b/mpc-spec/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "mpc-spec" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs new file mode 100644 index 0000000..ab1dc93 --- /dev/null +++ b/mpc-spec/src/circuit.rs @@ -0,0 +1,225 @@ +//! The [`Circuit`] representation used by the MPC engine. +//! +//! This module is derived from the circuit representation of +//! [`garble_lang`](https://github.com/sine-fdn/garble-lang/tree/main), +//! the license of which is reproduced below. +//! +//! MIT License +//! +//! Copyright (c) 2022 SINE e.V. +//! +//! Permission is hereby granted, free of charge, to any person +//! obtaining a copy of this software and associated documentation +//! files (the "Software"), to deal in the Software without +//! restriction, including without limitation the rights to use, copy, +//! modify, merge, publish, distribute, sublicense, and/or sell copies +//! of the Software, and to permit persons to whom the Software is +//! furnished to do so, subject to the following conditions: +//! +//! The above copyright notice and this permission notice shall be +//! included in all copies or substantial portions of the Software. +//! +//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +//! EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +//! MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +//! NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +//! HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +//! WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +//! DEALINGS IN THE SOFTWARE. +//! + +/// Data type to uniquely identify gates. +pub type GateIndex = usize; + +/// Description of a gate executed under MPC. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Gate { + /// A logical XOR gate attached to the two specified input wires. + Xor(GateIndex, GateIndex), + /// A logical AND gate attached to the two specified input wires. + And(GateIndex, GateIndex), + /// A logical NOT gate attached to the specified input wire. + Not(GateIndex), +} + +/// Representation of a circuit evaluated by an MPC engine. +/// +/// Each circuit consists of 3 parts: +/// +/// 1. `input_gates`, specifying how many input bits each party must provide +/// 2. `gates`, XOR/AND/NOT intermediate gates (with input gates or intermediate gates as inputs) +/// 3. `output_gates`, specifying which gates should be exposed as outputs (and in which order) +/// +/// Conceptually, a circuit is a sequence of input or intermediate (XOR/AND/NOT) gates, with all +/// input gates at the beginning of the sequence, followed by all intermediate gates. The index of a +/// gate in the sequence determines its "wire". For example, in a circuit with two input gates (1 +/// bit for party A, 1 bit for party B), followed by three intermediate gates (an XOR of the two +/// input gates, an AND of the two input gates, and an XOR of these two intermediate XOR/AND gates), +/// the input gates would be the wires 0 and 1, the XOR of these two input gates would be specified +/// as `Gate::Xor(0, 1)` and have the wire 2, the AND of the two input gates would be specified as +/// `Gate::And(0, 1)` and have the wire 3, and the XOR of the two intermediate gates would be +/// specified as `Gate::Xor(2, 3)` and have the wire 4: +/// +/// ```text +/// Input A (Wire 0) ----+----------+ +/// | | +/// Input B (Wire 1) ----|-----+----|-----+ +/// | | | | +/// +-XOR-+ | | +/// (Wire 2) =====> | | | +/// | +-AND-+ +/// (Wire 3) =======|========> | +/// +---XOR----+ +/// (Wire 4) ==========> | +/// ``` +/// +/// The input gates of different parties cannot be interleaved: Each party must supply all of their +/// inputs before the next party's inputs can start. Consequently, a circuit with 16 input bits from +/// party A, 8 input bits from party B and 1 input bit from party C would be specified as an +/// `input_gates` field of `vec![16, 8, 1]`. +/// +/// At least 1 input bit must be specified, not just because a circuit without inputs would not be +/// very useful, but also because the first two intermediate gates of every circuit are constant +/// true and constant false, specified as `Gate::Xor(0, 0)` with wire `n` and `Gate::Not(n)` (and +/// thus depend on the first input bit for their specifications). +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Circuit { + /// The different parties, with `usize` at index `i` as the number of input bits for party `i`. + pub input_gates: Vec, + /// The non-input intermediary gates. + pub gates: Vec, + /// The indices of the gates in [`Circuit::gates`] that produce output bits. + pub output_gates: Vec, +} + +/// An input wire or a gate operating on them. +pub enum Wire { + /// An input wire, with its value coming directly from one of the parties. + Input(GateIndex), + /// A logical XOR gate attached to the two specified input wires. + Xor(GateIndex, GateIndex), + /// A logical AND gate attached to the two specified input wires. + And(GateIndex, GateIndex), + /// A logical NOT gate attached to the specified input wire. + Not(GateIndex), +} + +/// Errors occurring during the validation or the execution of the MPC protocol. +#[derive(Debug, PartialEq, Eq)] +pub enum CircuitError { + /// The gate with the specified wire contains invalid gate connections. + InvalidGate(usize), + /// The specified output gate does not exist in the circuit. + InvalidOutput(usize), + /// The circuit does not specify any output gates. + EmptyOutputs, + /// The provided index does not correspond to any party. + PartyIndexOutOfBounds, +} + +impl Circuit { + /// Returns all the wires (inputs + gates) in the circuit, in ascending order. + pub fn wires(&self) -> Vec { + let mut gates = vec![]; + for (party, inputs) in self.input_gates.iter().enumerate() { + for _ in 0..*inputs { + gates.push(Wire::Input(party)) + } + } + for gate in self.gates.iter() { + let gate = match gate { + Gate::Xor(x, y) => Wire::Xor(*x, *y), + Gate::And(x, y) => Wire::And(*x, *y), + Gate::Not(x) => Wire::Not(*x), + }; + gates.push(gate); + } + gates + } + + /// Checks that the circuit only uses valid wires, includes no cycles, has outputs, etc. + pub fn validate(&self) -> Result<(), CircuitError> { + let wires = self.wires(); + for (i, g) in wires.iter().enumerate() { + match g { + Wire::Input(_) => {} + &Wire::Xor(x, y) => { + if x >= i || y >= i { + return Err(CircuitError::InvalidGate(i)); + } + } + &Wire::And(x, y) => { + if x >= i || y >= i { + return Err(CircuitError::InvalidGate(i)); + } + } + &Wire::Not(x) => { + if x >= i { + return Err(CircuitError::InvalidGate(i)); + } + } + } + } + if self.output_gates.is_empty() { + return Err(CircuitError::EmptyOutputs); + } + for &o in self.output_gates.iter() { + if o >= wires.len() { + return Err(CircuitError::InvalidOutput(o)); + } + } + Ok(()) + } + + /// Evaluates the circuit with the specified inputs (with one `Vec` per party). + /// + /// Assumes that the inputs have been previously type-checked and **panics** if the number of + /// parties or the bits of a particular party do not match the circuit. + pub fn eval(&self, inputs: &[Vec]) -> Vec { + let mut input_len = 0; + for p in self.input_gates.iter() { + input_len += p; + } + let mut output = vec![None; input_len + self.gates.len()]; + let inputs: Vec<_> = inputs.iter().map(|inputs| inputs.iter()).collect(); + let mut i = 0; + if self.input_gates.len() != inputs.len() { + panic!( + "Circuit was built for {} parties, but found {} inputs", + self.input_gates.len(), + inputs.len() + ); + } + for (p, &input_gates) in self.input_gates.iter().enumerate() { + if input_gates != inputs[p].len() { + panic!( + "Expected {} input bits for party {}, but found {}", + input_gates, + p, + inputs[p].len() + ); + } + for bit in inputs[p].as_slice() { + output[i] = Some(*bit); + i += 1; + } + } + for (w, gate) in self.gates.iter().enumerate() { + let w = w + i; + let output_bit = match gate { + Gate::Xor(x, y) => output[*x].unwrap() ^ output[*y].unwrap(), + Gate::And(x, y) => output[*x].unwrap() & output[*y].unwrap(), + Gate::Not(x) => !output[*x].unwrap(), + }; + output[w] = Some(output_bit); + } + + let mut output_packed: Vec = Vec::with_capacity(self.output_gates.len()); + for output_gate in &self.output_gates { + output_packed.push(output[*output_gate].unwrap()); + } + output_packed + } +} diff --git a/mpc-spec/src/lib.rs b/mpc-spec/src/lib.rs new file mode 100644 index 0000000..c73895c --- /dev/null +++ b/mpc-spec/src/lib.rs @@ -0,0 +1,6 @@ +#![deny(missing_docs)] +#![deny(rustdoc::broken_intra_doc_links)] +//! This crate is an executable specification of an MPC engine based on the +//! WRK17 protocol. + +pub mod circuit; From 269300ec9006fb17a8760ddb9bbd7c73c642ebe9 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Thu, 1 Feb 2024 16:37:30 +0100 Subject: [PATCH 2/9] Simplify circuit representation --- mpc-spec/src/circuit.rs | 366 ++++++++++++++++++++++------------------ 1 file changed, 204 insertions(+), 162 deletions(-) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index ab1dc93..3dc6387 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -1,225 +1,267 @@ //! The [`Circuit`] representation used by the MPC engine. //! -//! This module is derived from the circuit representation of -//! [`garble_lang`](https://github.com/sine-fdn/garble-lang/tree/main), -//! the license of which is reproduced below. +//! A circuit is made up of logic gates and value-carrying wires between the +//! gates. Each gate takes one or more wires as input, depending on the type of +//! gate, and has exactly one output wire. +//! +//! Conceptually, a circuit is a sequence of input or logic (XOR/AND/NOT) gates, +//! with all input gates at the beginning of the sequence, followed by all logic +//! gates. The index of a gate in the sequence determines its "wire index", +//! which is available as the input to any gate later in the sequence. For +//! example, in a circuit with two input gates (1 bit for party A, 1 bit for +//! party B), followed by three logic gates (an XOR of the two input gates, an +//! AND of the two input gates, and an XOR of these two XOR/AND gates), the +//! input gates would be the wires 0 and 1, the XOR of these two input gates +//! would be specified as `Gate::Xor(0, 1)` and have wire index 2, the AND of +//! the two input gates would be specified as `Gate::And(0, 1)` and have wire +//! index 3, and the XOR of the two logic gates would be specified as +//! `Gate::Xor(2, 3)` and have wire index 4: //! -//! MIT License +//! ```text +//! Input A (Wire 0) ----+----------+ +//! | | +//! Input B (Wire 1) ----|-----+----|-----+ +//! | | | | +//! +-XOR-+ | | +//! (Wire 2) =====> | | | +//! | +-AND-+ +//! (Wire 3) =======|========> | +//! +---XOR----+ +//! (Wire 4) ==========> | +//! ``` //! -//! Copyright (c) 2022 SINE e.V. +//! The input gates of different parties cannot be interleaved: Each party must +//! supply all of their inputs before the next party's inputs can start. //! -//! Permission is hereby granted, free of charge, to any person -//! obtaining a copy of this software and associated documentation -//! files (the "Software"), to deal in the Software without -//! restriction, including without limitation the rights to use, copy, -//! modify, merge, publish, distribute, sublicense, and/or sell copies -//! of the Software, and to permit persons to whom the Software is -//! furnished to do so, subject to the following conditions: +//! At least one input bit must be specified, and every party contributing +//! inputs to the circuit has to specify at least one input bit. Party input +//! gates may not refer to other input gates' wire indices. //! -//! The above copyright notice and this permission notice shall be -//! included in all copies or substantial portions of the Software. +//! This module is derived from the circuit representation of +//! [`garble_lang`](https://github.com/sine-fdn/garble-lang/tree/main), the +//! license of which is reproduced below. //! -//! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -//! EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -//! MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -//! NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -//! HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -//! WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -//! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -//! DEALINGS IN THE SOFTWARE. +//! > MIT License +//! > +//! > Copyright (c) 2022 SINE e.V. +//! > +//! > Permission is hereby granted, free of charge, to any person obtaining a +//! > copy of this software and associated documentation files (the "Software"), +//! > to deal in the Software without restriction, including without limitation +//! > the rights to use, copy, modify, merge, publish, distribute, sublicense, +//! > and/or sell copies of the Software, and to permit persons to whom the +//! > Software is furnished to do so, subject to the following conditions: +//! > +//! > The above copyright notice and this permission notice shall be included in +//! > all copies or substantial portions of the Software. +//! > +//! > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +//! > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//! > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +//! > THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//! > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +//! > FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +//! > DEALINGS IN THE SOFTWARE. //! +//! + +/// Data type to uniquely identify gate output wires. +pub type WireIndex = usize; -/// Data type to uniquely identify gates. -pub type GateIndex = usize; - -/// Description of a gate executed under MPC. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Gate { - /// A logical XOR gate attached to the two specified input wires. - Xor(GateIndex, GateIndex), - /// A logical AND gate attached to the two specified input wires. - And(GateIndex, GateIndex), - /// A logical NOT gate attached to the specified input wire. - Not(GateIndex), +/// An input gate or a logic gate with its input wire specification. +#[derive(Debug, Clone)] +pub enum WiredGate { + /// An input wire, with its value coming directly from one of the parties. + /// Its [WireIndex] must refer to its own gate index. + Input(WireIndex), + /// A logical XOR gate attached to the two specified input wires. The + /// [WireIndex] of each input wire must refer to a lower index than the + /// gate's own index. + Xor(WireIndex, WireIndex), + /// A logical AND gate attached to the two specified input wires. The + /// [WireIndex] of each input wire must refer to a lower index than the + /// gate's own index. + And(WireIndex, WireIndex), + /// A logical NOT gate attached to the specified input wire. The + /// [WireIndex] of the input wire must refer to a lower index than the + /// gate's own index. + Not(WireIndex), } +/// Specifies how many input bits a party is expected to contribute to the +/// evaluation. +pub type InputWidth = usize; /// Representation of a circuit evaluated by an MPC engine. -/// -/// Each circuit consists of 3 parts: -/// -/// 1. `input_gates`, specifying how many input bits each party must provide -/// 2. `gates`, XOR/AND/NOT intermediate gates (with input gates or intermediate gates as inputs) -/// 3. `output_gates`, specifying which gates should be exposed as outputs (and in which order) -/// -/// Conceptually, a circuit is a sequence of input or intermediate (XOR/AND/NOT) gates, with all -/// input gates at the beginning of the sequence, followed by all intermediate gates. The index of a -/// gate in the sequence determines its "wire". For example, in a circuit with two input gates (1 -/// bit for party A, 1 bit for party B), followed by three intermediate gates (an XOR of the two -/// input gates, an AND of the two input gates, and an XOR of these two intermediate XOR/AND gates), -/// the input gates would be the wires 0 and 1, the XOR of these two input gates would be specified -/// as `Gate::Xor(0, 1)` and have the wire 2, the AND of the two input gates would be specified as -/// `Gate::And(0, 1)` and have the wire 3, and the XOR of the two intermediate gates would be -/// specified as `Gate::Xor(2, 3)` and have the wire 4: -/// -/// ```text -/// Input A (Wire 0) ----+----------+ -/// | | -/// Input B (Wire 1) ----|-----+----|-----+ -/// | | | | -/// +-XOR-+ | | -/// (Wire 2) =====> | | | -/// | +-AND-+ -/// (Wire 3) =======|========> | -/// +---XOR----+ -/// (Wire 4) ==========> | -/// ``` -/// -/// The input gates of different parties cannot be interleaved: Each party must supply all of their -/// inputs before the next party's inputs can start. Consequently, a circuit with 16 input bits from -/// party A, 8 input bits from party B and 1 input bit from party C would be specified as an -/// `input_gates` field of `vec![16, 8, 1]`. -/// -/// At least 1 input bit must be specified, not just because a circuit without inputs would not be -/// very useful, but also because the first two intermediate gates of every circuit are constant -/// true and constant false, specified as `Gate::Xor(0, 0)` with wire `n` and `Gate::Not(n)` (and -/// thus depend on the first input bit for their specifications). #[derive(Debug, Clone)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Circuit { - /// The different parties, with `usize` at index `i` as the number of input bits for party `i`. - pub input_gates: Vec, - /// The non-input intermediary gates. - pub gates: Vec, + /// The bit-width of the inputs expected by the different parties, + /// [InputWidth] at index `i` representing the number of input bits for + /// party `i`. + pub input_widths: Vec, + /// The circuit's gates. + pub gates: Vec, /// The indices of the gates in [`Circuit::gates`] that produce output bits. - pub output_gates: Vec, -} - -/// An input wire or a gate operating on them. -pub enum Wire { - /// An input wire, with its value coming directly from one of the parties. - Input(GateIndex), - /// A logical XOR gate attached to the two specified input wires. - Xor(GateIndex, GateIndex), - /// A logical AND gate attached to the two specified input wires. - And(GateIndex, GateIndex), - /// A logical NOT gate attached to the specified input wire. - Not(GateIndex), + pub output_gates: Vec, } /// Errors occurring during the validation or the execution of the MPC protocol. #[derive(Debug, PartialEq, Eq)] pub enum CircuitError { - /// The gate with the specified wire contains invalid gate connections. + /// The provided party input does not match the number of input bits for + /// that party expected by the circuit. + PartyInputMismatch, + /// The provided set of inputs does not match the number of party inputs + /// expected by the circuit. + PartyCountMismatch, + /// The gate with the specified wire index contains invalid gate connections + /// or is placed out of sequence. InvalidGate(usize), /// The specified output gate does not exist in the circuit. - InvalidOutput(usize), + InvalidOutputWire(usize), /// The circuit does not specify any output gates. - EmptyOutputs, - /// The provided index does not correspond to any party. - PartyIndexOutOfBounds, + EmptyOutputSpecification, + /// The circuit does not specify input wires. + EmptyInputSpecification, + /// The circuit specifies a zero-width input. + InvalidInputSpecification, } impl Circuit { - /// Returns all the wires (inputs + gates) in the circuit, in ascending order. - pub fn wires(&self) -> Vec { - let mut gates = vec![]; - for (party, inputs) in self.input_gates.iter().enumerate() { - for _ in 0..*inputs { - gates.push(Wire::Input(party)) + /// Number of parties expected to contribute inputs to the circuit. + pub fn number_of_parties(&self) -> usize { + self.input_widths.len() + } + + /// Check validity of circuit specification. + /// + /// In particular: + /// * Validate input specification: Input width specification does not allow + /// 0-width inputs and at least one party must provide input bits. + /// * Validate gate sequence: All input gates must be at the beginning of + /// the gate sequence, followed only by logic gates. + /// * Validate gate wiring: A logic gate with index `i` can only take input + /// wires with strictly smaller indices. An input gate with index `i` must + /// refer to its own index as the input wire index. + /// * Validate output specification: The number of specified output wires + /// must be non-zero and all output wire indices must refer to valid wire + /// indices in the circuit, i.e. output wire indices must be smaller or + /// equal to the highest wire index used in the circuit. + pub fn validate_circuit_specification(&self) -> Result<(), CircuitError> { + // Check input validity. + if self.input_widths.is_empty() { + return Err(CircuitError::EmptyInputSpecification); + } + for input_width in self.input_widths.iter() { + if *input_width == 0 { + return Err(CircuitError::InvalidInputSpecification); } } - for gate in self.gates.iter() { - let gate = match gate { - Gate::Xor(x, y) => Wire::Xor(*x, *y), - Gate::And(x, y) => Wire::And(*x, *y), - Gate::Not(x) => Wire::Not(*x), - }; - gates.push(gate); + + // Check gate and gate sequence validity. + let mut total_input_width = 0; + for party_input_width in self.input_widths.iter() { + total_input_width += party_input_width; } - gates - } - /// Checks that the circuit only uses valid wires, includes no cycles, has outputs, etc. - pub fn validate(&self) -> Result<(), CircuitError> { - let wires = self.wires(); - for (i, g) in wires.iter().enumerate() { + for (i, g) in self.gates.iter().enumerate() { match g { - Wire::Input(_) => {} - &Wire::Xor(x, y) => { - if x >= i || y >= i { + &WiredGate::Input(x) => { + if x != i || i >= total_input_width { + return Err(CircuitError::InvalidGate(i)); + } + } + &WiredGate::Xor(x, y) => { + if x >= i || y >= i || i < total_input_width { return Err(CircuitError::InvalidGate(i)); } } - &Wire::And(x, y) => { - if x >= i || y >= i { + &WiredGate::And(x, y) => { + if x >= i || y >= i || i < total_input_width { return Err(CircuitError::InvalidGate(i)); } } - &Wire::Not(x) => { - if x >= i { + &WiredGate::Not(x) => { + if x >= i || i < total_input_width { return Err(CircuitError::InvalidGate(i)); } } } } + + // Validate non-empty output specification. if self.output_gates.is_empty() { - return Err(CircuitError::EmptyOutputs); + return Err(CircuitError::EmptyOutputSpecification); } + + // Validate output wire bounds. for &o in self.output_gates.iter() { - if o >= wires.len() { - return Err(CircuitError::InvalidOutput(o)); + if o >= self.gates.len() { + return Err(CircuitError::InvalidOutputWire(o)); } } + Ok(()) } - /// Evaluates the circuit with the specified inputs (with one `Vec` per party). + /// Validate that a given set of party inputs corresponds to the circuit + /// specification. /// - /// Assumes that the inputs have been previously type-checked and **panics** if the number of - /// parties or the bits of a particular party do not match the circuit. - pub fn eval(&self, inputs: &[Vec]) -> Vec { - let mut input_len = 0; - for p in self.input_gates.iter() { - input_len += p; + /// In particular: + /// * Validate that the number of input vectors corresponds to the number of parties + /// expected to provide inputs. + /// * Validate, for each input vector, that the number of input bits matches the + /// corresponding parties' expected input width. + pub fn validate_input_vectors(&self, inputs: &[Vec]) -> Result<(), CircuitError> { + if self.number_of_parties() != inputs.len() { + return Err(CircuitError::PartyCountMismatch); } - let mut output = vec![None; input_len + self.gates.len()]; - let inputs: Vec<_> = inputs.iter().map(|inputs| inputs.iter()).collect(); - let mut i = 0; - if self.input_gates.len() != inputs.len() { - panic!( - "Circuit was built for {} parties, but found {} inputs", - self.input_gates.len(), - inputs.len() - ); - } - for (p, &input_gates) in self.input_gates.iter().enumerate() { - if input_gates != inputs[p].len() { - panic!( - "Expected {} input bits for party {}, but found {}", - input_gates, - p, - inputs[p].len() - ); - } - for bit in inputs[p].as_slice() { - output[i] = Some(*bit); - i += 1; + + for (party, &expected_input_gates) in self.input_widths.iter().enumerate() { + if expected_input_gates != inputs[party].len() { + return Err(CircuitError::PartyInputMismatch); } } - for (w, gate) in self.gates.iter().enumerate() { - let w = w + i; + + Ok(()) + } + + /// Evaluates a circuit with the specified inputs (with one `Vec` per + /// party). + /// + /// After validation of the circuit specification and validation of the + /// provided input vectors, the circuit is evaluated gate by gate: + /// + /// * Input gates are evaluated as the identity function on the provided + /// input. + /// * Logic gates are evaluated by applying the given logical operation to + /// the wire values of the gates' input wires. + /// + /// Circuit validation ensures that, during sequential evaluation, gate + /// input wires can only refer to previously evaluated gates, or values + /// provided in the circuit inputs in the case of input gate evaulation. + /// + /// The circuit output is packed into a bitstring, with the indicated output + /// wire values appearing in sequential order. + pub fn eval(&self, inputs: &[Vec]) -> Result, CircuitError> { + self.validate_circuit_specification()?; + self.validate_input_vectors(inputs)?; + + let mut wire_evaluations: Vec = inputs.iter().flat_map(|b| b.clone()).collect(); + + for gate in self.gates.iter() { let output_bit = match gate { - Gate::Xor(x, y) => output[*x].unwrap() ^ output[*y].unwrap(), - Gate::And(x, y) => output[*x].unwrap() & output[*y].unwrap(), - Gate::Not(x) => !output[*x].unwrap(), + WiredGate::Input(x) => wire_evaluations[*x], + WiredGate::Xor(x, y) => wire_evaluations[*x] ^ wire_evaluations[*y], + WiredGate::And(x, y) => wire_evaluations[*x] & wire_evaluations[*y], + WiredGate::Not(x) => !wire_evaluations[*x], }; - output[w] = Some(output_bit); + wire_evaluations.push(output_bit); } let mut output_packed: Vec = Vec::with_capacity(self.output_gates.len()); for output_gate in &self.output_gates { - output_packed.push(output[*output_gate].unwrap()); + output_packed.push(wire_evaluations[*output_gate]); } - output_packed + Ok(output_packed) } } From c490e61c55da435500d9086175b7781cb9ed1e54 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Thu, 1 Feb 2024 16:59:09 +0100 Subject: [PATCH 3/9] Clippy warning and some more descriptive names --- mpc-spec/src/circuit.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index 3dc6387..31627f9 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -163,26 +163,26 @@ impl Circuit { total_input_width += party_input_width; } - for (i, g) in self.gates.iter().enumerate() { - match g { - &WiredGate::Input(x) => { - if x != i || i >= total_input_width { - return Err(CircuitError::InvalidGate(i)); + for (gate_index, gate) in self.gates.iter().enumerate() { + match *gate { + WiredGate::Input(x) => { + if x != gate_index || gate_index >= total_input_width { + return Err(CircuitError::InvalidGate(gate_index)); } } - &WiredGate::Xor(x, y) => { - if x >= i || y >= i || i < total_input_width { - return Err(CircuitError::InvalidGate(i)); + WiredGate::Xor(x, y) => { + if x >= gate_index || y >= gate_index || gate_index < total_input_width { + return Err(CircuitError::InvalidGate(gate_index)); } } - &WiredGate::And(x, y) => { - if x >= i || y >= i || i < total_input_width { - return Err(CircuitError::InvalidGate(i)); + WiredGate::And(x, y) => { + if x >= gate_index || y >= gate_index || gate_index < total_input_width { + return Err(CircuitError::InvalidGate(gate_index)); } } - &WiredGate::Not(x) => { - if x >= i || i < total_input_width { - return Err(CircuitError::InvalidGate(i)); + WiredGate::Not(x) => { + if x >= gate_index || gate_index < total_input_width { + return Err(CircuitError::InvalidGate(gate_index)); } } } @@ -194,9 +194,9 @@ impl Circuit { } // Validate output wire bounds. - for &o in self.output_gates.iter() { - if o >= self.gates.len() { - return Err(CircuitError::InvalidOutputWire(o)); + for &output_wire in self.output_gates.iter() { + if output_wire >= self.gates.len() { + return Err(CircuitError::InvalidOutputWire(output_wire)); } } From a9a00afe97f943eaaf6eccf42f486968e37b0723 Mon Sep 17 00:00:00 2001 From: jschneider-bensch <124457079+jschneider-bensch@users.noreply.github.com> Date: Tue, 6 Feb 2024 11:09:31 +0100 Subject: [PATCH 4/9] Apply suggestions for consistent iterations Co-authored-by: Jan Winkelmann <146678+keks@users.noreply.github.com> --- mpc-spec/src/circuit.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index 31627f9..4bbd7d3 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -151,7 +151,7 @@ impl Circuit { if self.input_widths.is_empty() { return Err(CircuitError::EmptyInputSpecification); } - for input_width in self.input_widths.iter() { + for input_width in &self.input_widths { if *input_width == 0 { return Err(CircuitError::InvalidInputSpecification); } @@ -159,7 +159,7 @@ impl Circuit { // Check gate and gate sequence validity. let mut total_input_width = 0; - for party_input_width in self.input_widths.iter() { + for party_input_width in &self.input_widths { total_input_width += party_input_width; } @@ -194,7 +194,7 @@ impl Circuit { } // Validate output wire bounds. - for &output_wire in self.output_gates.iter() { + for &output_wire in &self.output_gates { if output_wire >= self.gates.len() { return Err(CircuitError::InvalidOutputWire(output_wire)); } @@ -248,7 +248,7 @@ impl Circuit { let mut wire_evaluations: Vec = inputs.iter().flat_map(|b| b.clone()).collect(); - for gate in self.gates.iter() { + for gate in &self.gates { let output_bit = match gate { WiredGate::Input(x) => wire_evaluations[*x], WiredGate::Xor(x, y) => wire_evaluations[*x] ^ wire_evaluations[*y], From 0751e929766df00157b1023947e0cea7a3b450ab Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Tue, 6 Feb 2024 11:22:30 +0100 Subject: [PATCH 5/9] Provide more context for Errors --- mpc-spec/src/circuit.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index 4bbd7d3..3b4478f 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -109,10 +109,10 @@ pub struct Circuit { pub enum CircuitError { /// The provided party input does not match the number of input bits for /// that party expected by the circuit. - PartyInputMismatch, + PartyInputMismatch(usize, usize), /// The provided set of inputs does not match the number of party inputs /// expected by the circuit. - PartyCountMismatch, + PartyCountMismatch(usize, usize), /// The gate with the specified wire index contains invalid gate connections /// or is placed out of sequence. InvalidGate(usize), From 07f9aeafff82db6a87f04b57ec868dc5b878d6b0 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Tue, 6 Feb 2024 11:22:41 +0100 Subject: [PATCH 6/9] Implement `Display` for Errors --- mpc-spec/src/circuit.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index 3b4478f..3868f21 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -126,6 +126,40 @@ pub enum CircuitError { InvalidInputSpecification, } +impl std::fmt::Display for CircuitError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + CircuitError::PartyInputMismatch(expected_inputs, actual_inputs) => write!( + f, + "Expected {} input bits for a party, but received {} input bits.", + *expected_inputs, *actual_inputs + ), + CircuitError::PartyCountMismatch(expected_parties, actual_parties) => write!( + f, + "Expected inputs for {} parties, but received inputs for {} parties.", + *expected_parties, *actual_parties + ), + CircuitError::InvalidGate(gate_index) => write!( + f, + "Gate {}: Out of order placement or invalid wiring.", + *gate_index + ), + CircuitError::InvalidOutputWire(oob_index) => { + write!(f, "Output index out of bounds: {}", *oob_index) + } + CircuitError::EmptyOutputSpecification => { + write!(f, "Circuit does not specify output bits.") + } + CircuitError::EmptyInputSpecification => { + write!(f, "Circuit does not specify any party inputs.") + } + CircuitError::InvalidInputSpecification => { + write!(f, "Circuit specifies an empty party input.") + } + } + } +} + impl Circuit { /// Number of parties expected to contribute inputs to the circuit. pub fn number_of_parties(&self) -> usize { From 74985ede52212784d18c471727e738cb5117e164 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Tue, 6 Feb 2024 11:23:03 +0100 Subject: [PATCH 7/9] Provide more context for Errors (addendum) --- mpc-spec/src/circuit.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index 3868f21..ff08f1f 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -247,12 +247,18 @@ impl Circuit { /// corresponding parties' expected input width. pub fn validate_input_vectors(&self, inputs: &[Vec]) -> Result<(), CircuitError> { if self.number_of_parties() != inputs.len() { - return Err(CircuitError::PartyCountMismatch); + return Err(CircuitError::PartyCountMismatch( + self.number_of_parties(), + inputs.len(), + )); } for (party, &expected_input_gates) in self.input_widths.iter().enumerate() { if expected_input_gates != inputs[party].len() { - return Err(CircuitError::PartyInputMismatch); + return Err(CircuitError::PartyInputMismatch( + expected_input_gates, + inputs[party].len(), + )); } } From f3135a39d07ad2a15c6438ea6531482fa156cb48 Mon Sep 17 00:00:00 2001 From: jschneider-bensch <124457079+jschneider-bensch@users.noreply.github.com> Date: Wed, 7 Feb 2024 10:31:26 +0100 Subject: [PATCH 8/9] Apply suggestions: Doc comments Co-authored-by: Jan Winkelmann <146678+keks@users.noreply.github.com> --- mpc-spec/src/circuit.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index ff08f1f..3b48737 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -72,18 +72,18 @@ pub type WireIndex = usize; #[derive(Debug, Clone)] pub enum WiredGate { /// An input wire, with its value coming directly from one of the parties. - /// Its [WireIndex] must refer to its own gate index. + /// Its [`WireIndex`] must refer to its own gate index. Input(WireIndex), /// A logical XOR gate attached to the two specified input wires. The - /// [WireIndex] of each input wire must refer to a lower index than the + /// [`WireIndex`] of each input wire must refer to a lower index than the /// gate's own index. Xor(WireIndex, WireIndex), /// A logical AND gate attached to the two specified input wires. The - /// [WireIndex] of each input wire must refer to a lower index than the + /// [`WireIndex`] of each input wire must refer to a lower index than the /// gate's own index. And(WireIndex, WireIndex), /// A logical NOT gate attached to the specified input wire. The - /// [WireIndex] of the input wire must refer to a lower index than the + /// [`WireIndex`] of the input wire must refer to a lower index than the /// gate's own index. Not(WireIndex), } @@ -95,7 +95,7 @@ pub type InputWidth = usize; #[derive(Debug, Clone)] pub struct Circuit { /// The bit-width of the inputs expected by the different parties, - /// [InputWidth] at index `i` representing the number of input bits for + /// [`InputWidth`] at index `i` representing the number of input bits for /// party `i`. pub input_widths: Vec, /// The circuit's gates. From e79c15a06b78767f0e5888807d4b046c51a418c9 Mon Sep 17 00:00:00 2001 From: Jonas Schneider-Bensch Date: Wed, 7 Feb 2024 10:35:23 +0100 Subject: [PATCH 9/9] Conform to standard library error message conventions --- mpc-spec/src/circuit.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mpc-spec/src/circuit.rs b/mpc-spec/src/circuit.rs index ff08f1f..fef94c4 100644 --- a/mpc-spec/src/circuit.rs +++ b/mpc-spec/src/circuit.rs @@ -131,30 +131,30 @@ impl std::fmt::Display for CircuitError { match self { CircuitError::PartyInputMismatch(expected_inputs, actual_inputs) => write!( f, - "Expected {} input bits for a party, but received {} input bits.", + "expected {} input bits for a party, but received {} input bits", *expected_inputs, *actual_inputs ), CircuitError::PartyCountMismatch(expected_parties, actual_parties) => write!( f, - "Expected inputs for {} parties, but received inputs for {} parties.", + "expected inputs for {} parties, but received inputs for {} parties", *expected_parties, *actual_parties ), CircuitError::InvalidGate(gate_index) => write!( f, - "Gate {}: Out of order placement or invalid wiring.", + "found out of order placement or invalid wiring at gate index {}", *gate_index ), CircuitError::InvalidOutputWire(oob_index) => { - write!(f, "Output index out of bounds: {}", *oob_index) + write!(f, "output index {} is out of bounds", *oob_index) } CircuitError::EmptyOutputSpecification => { - write!(f, "Circuit does not specify output bits.") + write!(f, "circuit does not specify output bits") } CircuitError::EmptyInputSpecification => { - write!(f, "Circuit does not specify any party inputs.") + write!(f, "circuit does not specify any party inputs") } CircuitError::InvalidInputSpecification => { - write!(f, "Circuit specifies an empty party input.") + write!(f, "circuit specifies an empty party input") } } }