Skip to main content

core_utils/circuit/latest/
circuit.rs

1use primitives::algebra::elliptic_curve::NumCoordinates;
2use typenum::Unsigned;
3
4use crate::{
5    circuit::{
6        errors::{CircuitError, ConversionError},
7        gate::Gate,
8        preprocessing::AES_S_BOX_N_NETWORK_ROUNDS,
9        AlgebraicType,
10        BitShareBinaryOp,
11        BitShareUnaryOp,
12        FieldShareBinaryOp,
13        FieldShareUnaryOp,
14        FieldType,
15        GateIndex,
16        Input,
17        PlaintextBitConstraint,
18        PointPlaintextBinaryOp,
19        PointShareBinaryOp,
20        PointShareUnaryOp,
21        ShareOrPlaintext,
22        MAX_EXPR_DEPTH,
23        MAX_EXPR_NODES,
24    },
25    config::MpcConfig,
26    key_recovery::{MXE_KEY_RECOVERY_D, MXE_KEY_RECOVERY_N},
27};
28
29/// A circuit composed of a sequence of gates, input and output identifiers.
30///
31/// Each circuit gate contains additional information about its output characteristics, refer to
32///  the ` GateExt ` struct for more details.
33///
34/// The circuit is always valid because the gate addition is validated.
35#[derive(Default, PartialEq, Debug, Clone)]
36pub struct Circuit<C: MpcConfig> {
37    /// The circuit gates.
38    pub(super) gates: Vec<GateExt<C>>,
39    /// The input gates in order of definition
40    pub(super) inputs: Vec<GateIndex>,
41    /// The output gates in order of definition
42    pub(super) outputs: Vec<GateIndex>,
43}
44
45/// A circuit gate together with additional information about its output.
46/// The additional information is automatically deduced when the gate is added to the circuit.
47#[derive(Clone, Debug, PartialEq)]
48pub struct GateExt<C: MpcConfig> {
49    pub gate: Gate<C>,
50    pub output: GateOutput,
51    pub level: GateLevel,
52}
53
54/// Gate output characteristics like algebraic type, visibility, and batch size
55#[derive(PartialEq, Copy, Clone, Debug)]
56pub struct GateOutput {
57    pub(super) algebraic_type: AlgebraicType,
58    pub(super) form: ShareOrPlaintext,
59    pub(super) batch_size: u32,
60    /// Whether the wire carries daBits rather than plain elements of `algebraic_type`.
61    ///
62    /// A daBit is a bit authenticated in both backends at once, so it is a different Rust type
63    /// from a share of the same field and needs a different task. The two are indistinguishable
64    /// by `algebraic_type` and `form` alone, which is why this is tracked separately.
65    pub(super) is_dabit: bool,
66}
67
68/// The level of a gate in a circuit. All gates with the same level can be executed in parallel as
69/// they do not depend on each other.
70///
71/// The gate level is a pair of integers: the first one is the communication round (level), and the
72/// second one is a relative level within the same communication round. The gate communication round
73/// is the number of communications rounds which have passed after the gate execution.
74#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Default)]
75pub struct GateLevel {
76    comm_level: usize,
77    level: usize, // relative counter for ordering gates within a multiplicative level
78}
79
80impl<C: MpcConfig> GateExt<C> {
81    pub fn new(gate: Gate<C>, output: GateOutput, level: GateLevel) -> Self {
82        Self {
83            gate,
84            output,
85            level,
86        }
87    }
88}
89
90impl GateOutput {
91    pub fn get_type(&self) -> AlgebraicType {
92        self.algebraic_type
93    }
94
95    pub fn get_field_type(&self) -> Result<FieldType, ConversionError> {
96        FieldType::try_from(self.algebraic_type)
97    }
98
99    pub fn get_field_type_unchecked(&self) -> FieldType {
100        self.get_field_type().unwrap()
101    }
102
103    pub fn get_form(&self) -> ShareOrPlaintext {
104        self.form
105    }
106
107    /// Whether this wire carries daBits. See [`GateOutput::is_dabit`].
108    pub fn is_dabit(&self) -> bool {
109        self.is_dabit
110    }
111
112    pub fn get_batch_size(&self) -> u32 {
113        self.batch_size
114    }
115
116    fn is_field(&self) -> bool {
117        FieldType::try_from(self.algebraic_type).is_ok()
118    }
119
120    fn is_bit(&self) -> bool {
121        self.algebraic_type == AlgebraicType::Bit
122    }
123
124    fn is_point(&self) -> bool {
125        self.algebraic_type == AlgebraicType::Point
126    }
127
128    fn is_base_field(&self) -> bool {
129        self.algebraic_type == AlgebraicType::BaseField
130    }
131
132    fn is_scalar_field(&self) -> bool {
133        self.algebraic_type == AlgebraicType::ScalarField
134    }
135
136    fn is_share(&self) -> bool {
137        self.form == ShareOrPlaintext::Share
138    }
139
140    pub fn is_plaintext(&self) -> bool {
141        self.form == ShareOrPlaintext::Plaintext
142    }
143
144    fn with_new_form(self, form: ShareOrPlaintext) -> Self {
145        let mut res = self;
146        res.form = form;
147        res
148    }
149
150    fn with_new_type(self, algebraic_type: AlgebraicType) -> Self {
151        let mut res = self;
152        res.algebraic_type = algebraic_type;
153        res
154    }
155
156    fn with_new_batch_size(self, batch_size: u32) -> Self {
157        let mut res = self;
158        res.batch_size = batch_size;
159        res
160    }
161
162    /// For the gates that read one component out of a daBit: the result is a plain share.
163    fn without_dabit(self) -> Self {
164        let mut res = self;
165        res.is_dabit = false;
166        res
167    }
168}
169
170impl GateLevel {
171    fn next(&self, comm_rounds: usize) -> GateLevel {
172        if comm_rounds > 0 {
173            GateLevel {
174                comm_level: self.comm_level + comm_rounds,
175                level: 0,
176            }
177        } else {
178            GateLevel {
179                comm_level: self.comm_level,
180                level: self.level + 1,
181            }
182        }
183    }
184
185    pub fn comm_level(&self) -> usize {
186        self.comm_level
187    }
188}
189
190impl<C: MpcConfig> Circuit<C> {
191    pub fn new() -> Self {
192        Self::default()
193    }
194
195    /// Tries to add a gate to the circuit.
196    ///
197    /// This function validates the gate before adding the gate or fails otherwise.
198    pub fn add_gate(&mut self, gate: Gate<C>) -> Result<GateIndex, CircuitError<C>> {
199        self.validate_gate(&gate)?;
200
201        let index = self.nb_gates();
202        if index == GateIndex::MAX {
203            return Err(CircuitError::CircuitTooBig);
204        }
205
206        let gate_output = self.comp_gate_output(&gate);
207        let level = self.comp_gate_level(&gate);
208
209        if gate.is_input() {
210            self.inputs.push(index);
211        }
212        self.gates.push(GateExt::new(gate, gate_output?, level));
213
214        Ok(index)
215    }
216
217    /// Tries to set a gate as circuit output.
218    ///
219    /// This function fails if there is no gate with the given index.
220    pub fn add_output(&mut self, index: GateIndex) -> Result<(), CircuitError<C>> {
221        if index < self.nb_gates() {
222            self.outputs.push(index);
223            Ok(())
224        } else {
225            Err(CircuitError::GateIndexOutOfBounds(index, self.nb_gates()))
226        }
227    }
228
229    pub fn nb_gates(&self) -> GateIndex {
230        self.gates.len() as GateIndex
231    }
232
233    pub fn nb_inputs(&self) -> GateIndex {
234        self.inputs.len() as GateIndex
235    }
236
237    pub fn nb_outputs(&self) -> GateIndex {
238        self.outputs.len() as GateIndex
239    }
240
241    /// Consumes the circuit and returns the list of gates.
242    pub fn into_gates(self) -> Vec<GateExt<C>> {
243        self.gates
244    }
245
246    pub fn iter_gates_ext(
247        &self,
248    ) -> impl ExactSizeIterator<Item = &GateExt<C>> + DoubleEndedIterator {
249        self.gates.iter()
250    }
251
252    pub fn iter_gates(&self) -> impl ExactSizeIterator<Item = &Gate<C>> + DoubleEndedIterator {
253        self.gates.iter().map(|g| &g.gate)
254    }
255
256    pub fn iter_output_indices(&self) -> impl ExactSizeIterator<Item = &GateIndex> {
257        self.outputs.iter()
258    }
259
260    pub fn iter_input_indices(&self) -> impl ExactSizeIterator<Item = &GateIndex> {
261        self.inputs.iter()
262    }
263
264    pub fn gate_ext(&self, index: GateIndex) -> Result<&GateExt<C>, CircuitError<C>> {
265        if index < self.nb_gates() {
266            Ok(&self.gates[index as usize])
267        } else {
268            Err(CircuitError::GateIndexOutOfBounds(index, self.nb_gates()))
269        }
270    }
271
272    pub fn gate_ext_unchecked(&self, index: GateIndex) -> &GateExt<C> {
273        &self.gates[index as usize]
274    }
275
276    pub fn gate(&self, index: GateIndex) -> Result<&Gate<C>, CircuitError<C>> {
277        self.gate_ext(index).map(|g| &g.gate)
278    }
279
280    pub fn gate_unchecked(&self, index: GateIndex) -> &Gate<C> {
281        &self.gate_ext_unchecked(index).gate
282    }
283
284    pub fn gate_output(&self, index: GateIndex) -> Result<GateOutput, CircuitError<C>> {
285        self.gate_ext(index).map(|g| g.output)
286    }
287
288    pub fn gate_output_unchecked(&self, index: GateIndex) -> GateOutput {
289        self.gate_ext_unchecked(index).output
290    }
291
292    pub fn gate_level(&self, index: GateIndex) -> Result<GateLevel, CircuitError<C>> {
293        self.gate_ext(index).map(|g| g.level)
294    }
295
296    pub fn gate_level_unchecked(&self, index: GateIndex) -> GateLevel {
297        self.gate_ext_unchecked(index).level
298    }
299}
300
301macro_rules! check_algebraic_type {
302    ($exp_type:expr, $found_type:expr) => {
303        if $exp_type != $found_type {
304            return Err(CircuitError::InvalidGateAlgebraicType {
305                expected: $exp_type,
306                found: $found_type,
307            });
308        }
309    };
310}
311
312impl<C: MpcConfig> Circuit<C> {
313    /// Opens and outputs as scalar field a given gate
314    pub fn open_and_output_scalar(&mut self, x: GateIndex) -> Result<(), CircuitError<C>> {
315        check_algebraic_type!(AlgebraicType::ScalarField, self.gate_output(x)?.get_type());
316        let opening_index = self.add_gate(Gate::FieldShareUnaryOp {
317            x,
318            op: FieldShareUnaryOp::Open,
319        })?;
320        self.add_output(opening_index)
321    }
322
323    /// Opens and outputs as base field a given gate
324    pub fn open_and_output_base_field(&mut self, x: GateIndex) -> Result<(), CircuitError<C>> {
325        check_algebraic_type!(AlgebraicType::BaseField, self.gate_output(x)?.get_type());
326        let opening_index = self.add_gate(Gate::FieldShareUnaryOp {
327            x,
328            op: FieldShareUnaryOp::Open,
329        })?;
330        self.add_output(opening_index)
331    }
332
333    /// Opens and outputs a given gate as the MPC field
334    pub fn open_and_output_mpc_field(&mut self, x: GateIndex) -> Result<(), CircuitError<C>> {
335        check_algebraic_type!(AlgebraicType::MpcField, self.gate_output(x)?.get_type());
336        let opening_index = self.add_gate(Gate::FieldShareUnaryOp {
337            x,
338            op: FieldShareUnaryOp::Open,
339        })?;
340        self.add_output(opening_index)
341    }
342
343    /// Opens and outputs as point a given gate
344    pub fn open_and_output_point(&mut self, p: GateIndex) -> Result<(), CircuitError<C>> {
345        check_algebraic_type!(AlgebraicType::Point, self.gate_output(p)?.get_type());
346        let opening_index = self.add_gate(Gate::PointShareUnaryOp {
347            p,
348            op: PointShareUnaryOp::Open,
349        })?;
350        self.add_output(opening_index)
351    }
352
353    /// Opens and outputs as bit a given gate
354    pub fn open_and_output_bit(&mut self, x: GateIndex) -> Result<(), CircuitError<C>> {
355        check_algebraic_type!(AlgebraicType::Bit, self.gate_output(x)?.get_type());
356        let opening_index = self.add_gate(Gate::BitShareUnaryOp {
357            x,
358            op: BitShareUnaryOp::Open,
359        })?;
360        self.add_output(opening_index)
361    }
362}
363
364impl<C: MpcConfig> Circuit<C> {
365    /// Validates a gate.
366    ///
367    /// Checks that:
368    ///     - gate inputs are present in the circuit
369    ///     - gate input types correspond to the specification
370    ///     - gate input batch sizes are compatible
371    ///     - gate parameters are valid
372    fn validate_gate(&self, gate: &Gate<C>) -> Result<(), CircuitError<C>> {
373        macro_rules! check_op {
374            ($msg:expr, $gate:expr => $($val1:expr, $op:tt, $val2:expr);+$(;)?) => {
375                $(if !($val1 $op $val2) {
376                    return Err(CircuitError::InvalidGate(
377                        $gate.clone(),
378                        format!("{}: {:?} {:?} {:?}", $msg, $val1, stringify!($op), $val2),
379                    ));
380                })+
381            };
382        }
383
384        macro_rules! check_gate_properties {
385            ($gate:expr, $($func:ident),* $(,)?) => {
386                $(if !($gate.output.$func()) {
387                    return Err(CircuitError::InvalidGate(
388                        $gate.gate.clone(),
389                        format!("{:?} fails - {}", $gate.output, stringify!($func)))); }
390                )*
391            };
392        }
393
394        match gate {
395            Gate::Input(input) => {
396                check_op!(
397                    "input batch size must be non-zero",
398                    gate =>
399                    0, <, input.batch_size();
400                );
401            }
402            Gate::Constant(constant) => {
403                check_op!(
404                    "constant batch size must be non-zero",
405                    gate =>
406                     0, <, constant.batch_size()?;
407                );
408            }
409            Gate::Random { batch_size, .. } => {
410                check_op!(
411                    "random batch size must be non-zero",
412                    gate =>
413                     0, <, *batch_size;
414                );
415            }
416            Gate::FieldShareUnaryOp { x, .. } => {
417                check_gate_properties!(self.gate_ext(*x)?, is_field, is_share);
418            }
419            Gate::FieldShareBinaryOp { x, y, .. } => {
420                let (gx, gy) = (self.gate_ext(*x)?, self.gate_ext(*y)?);
421                check_gate_properties!(gx, is_field, is_share);
422                check_gate_properties!(gy, is_field);
423                check_op!(
424                    "inputs must have same batch-size and field type",
425                    gate =>
426                    gx.output.batch_size, ==, gy.output.batch_size;
427                    gx.output.algebraic_type, ==, gy.output.algebraic_type
428                );
429            }
430            Gate::BatchSummation { x } => {
431                self.gate_ext(*x)?;
432            }
433            Gate::BitShareUnaryOp { x, .. } => {
434                check_gate_properties!(self.gate_ext(*x)?, is_bit, is_share);
435            }
436            Gate::BitShareBinaryOp { x, y, .. } => {
437                let (gx, gy) = (self.gate_ext(*x)?, self.gate_ext(*y)?);
438                check_gate_properties!(gx, is_bit, is_share);
439                check_gate_properties!(gy, is_bit);
440                check_op!(
441                    "inputs must have same batch-size",
442                    gate =>
443                    gx.output.batch_size, ==, gy.output.batch_size
444                );
445            }
446            Gate::PointShareUnaryOp { p: x, .. } => {
447                check_gate_properties!(self.gate_ext(*x)?, is_point, is_share);
448            }
449            Gate::PointShareBinaryOp { p: x, y, op } => {
450                let (gx, gy) = (self.gate_ext(*x)?, self.gate_ext(*y)?);
451                if gx.output.is_plaintext() && gy.output.is_plaintext() {
452                    return Err(CircuitError::InvalidGate(
453                        gate.clone(),
454                        "at least one input must be share".to_string(),
455                    ));
456                }
457                check_gate_properties!(gx, is_point);
458                match op {
459                    PointShareBinaryOp::Add => {
460                        check_gate_properties!(gy, is_point);
461                    }
462                    PointShareBinaryOp::ScalarMul => {
463                        check_gate_properties!(gy, is_scalar_field);
464                    }
465                };
466                check_op!(
467                    "inputs must have same batch-size",
468                    gate =>
469                    gx.output.batch_size, ==, gy.output.batch_size
470                );
471            }
472            Gate::FieldPlaintextUnaryOp { x, .. } => {
473                check_gate_properties!(self.gate_ext(*x)?, is_field, is_plaintext);
474            }
475            Gate::FieldPlaintextBinaryOp { x, y, .. } => {
476                let (gx, gy) = (self.gate_ext(*x)?, self.gate_ext(*y)?);
477                check_gate_properties!(gx, is_field, is_plaintext);
478                check_gate_properties!(gy, is_field, is_plaintext);
479                check_op!(
480                    "inputs must have same field type",
481                    gate =>
482                    gx.output.algebraic_type, ==, gy.output.algebraic_type;
483                    gx.output.batch_size, ==, gy.output.batch_size
484                );
485            }
486            Gate::BitPlaintextUnaryOp { x, .. } => {
487                check_gate_properties!(self.gate_ext(*x)?, is_bit, is_plaintext);
488            }
489            Gate::BitPlaintextBinaryOp { x, y, .. } => {
490                let (gx, gy) = (self.gate_ext(*x)?, self.gate_ext(*y)?);
491                check_gate_properties!(gx, is_bit, is_plaintext);
492                check_gate_properties!(gy, is_bit, is_plaintext);
493                check_op!(
494                    "inputs must have same batch-size",
495                    gate =>
496                    gx.output.batch_size, ==, gy.output.batch_size
497                );
498            }
499            Gate::PointPlaintextUnaryOp { p: x, .. } => {
500                check_gate_properties!(self.gate_ext(*x)?, is_point, is_plaintext);
501            }
502            Gate::PointPlaintextBinaryOp { p: x, y, op } => {
503                let (gx, gy) = (self.gate_ext(*x)?, self.gate_ext(*y)?);
504                check_gate_properties!(gx, is_point, is_plaintext);
505                match op {
506                    PointPlaintextBinaryOp::Add => {
507                        check_gate_properties!(gy, is_point, is_plaintext);
508                    }
509                    PointPlaintextBinaryOp::ScalarMul => {
510                        check_gate_properties!(gy, is_scalar_field, is_plaintext);
511                    }
512                }
513                check_op!(
514                    "inputs must have same batch-size",
515                    gate =>
516                    gx.output.batch_size, ==, gy.output.batch_size
517                );
518            }
519            Gate::DaBit { batch_size, .. } => {
520                check_op!(
521                    "input batch size must be non-zero",
522                    gate =>
523                    0, <, *batch_size
524                );
525            }
526            Gate::GetDaBitFieldShare { x, .. } => {
527                // By convention, we suppose that the `DaBit` output is a field element
528                check_gate_properties!(self.gate_ext(*x)?, is_field, is_share);
529            }
530            Gate::GetDaBitSharedBit { x, .. } => {
531                // By convention, we suppose that the `DaBit` output is a field element
532                check_gate_properties!(self.gate_ext(*x)?, is_field, is_share);
533            }
534            Gate::BaseFieldPow { x, .. } => {
535                check_gate_properties!(self.gate_ext(*x)?, is_base_field, is_share);
536            }
537            Gate::BitPlaintextToField { x, .. } => {
538                check_gate_properties!(self.gate_ext(*x)?, is_bit, is_plaintext);
539            }
540            Gate::FieldPlaintextToBit { x, .. } => {
541                check_gate_properties!(self.gate_ext(*x)?, is_field, is_plaintext);
542            }
543            Gate::ExtractFromBatch { x, slice, .. } => {
544                let gx = self.gate_ext(*x)?;
545                if slice.is_empty() {
546                    return Err(CircuitError::InvalidGate(
547                        gate.clone(),
548                        format!("slice must be non-empty: {slice:?}"),
549                    ));
550                }
551                if slice.get_indices()
552                    .into_iter()
553                    .max()
554                    .expect("non-empty slice expected") // never fails as we check that the slice is non-empty
555                    >= gx.output.batch_size
556                {
557                    return Err(CircuitError::InvalidGate(
558                        gate.clone(),
559                        format!("slice indices out-of-range: {slice:?}"),
560                    ));
561                }
562            }
563            Gate::CollectToBatch { wires } => {
564                check_op!("expected at least one input", gate => 0, <, wires.len());
565                let first = self.gate_ext(wires[0])?.output;
566                for x in wires.iter().skip(1) {
567                    let gx = self.gate_ext(*x)?.output;
568                    check_op!(
569                        "all inputs must have the same type",
570                        gate =>
571                        first.algebraic_type, ==, gx.algebraic_type;
572                        first.form, ==, gx.form;
573                        first.is_dabit, ==, gx.is_dabit
574                    );
575                }
576            }
577            Gate::GatherFromBatches { parts } => {
578                check_op!("expected at least one part", gate => 0, <, parts.len());
579                let first = self.gate_output(parts[0].0)?;
580                for (x, slice) in parts {
581                    let gx = self.gate_output(*x)?;
582                    check_op!(
583                        "all parts must have the same type",
584                        gate =>
585                        first.algebraic_type, ==, gx.algebraic_type;
586                        first.form, ==, gx.form;
587                        first.is_dabit, ==, gx.is_dabit
588                    );
589                    if slice.is_empty() {
590                        return Err(CircuitError::InvalidGate(
591                            gate.clone(),
592                            format!("slice must be non-empty: {slice:?}"),
593                        ));
594                    }
595                    if slice
596                        .get_indices()
597                        .into_iter()
598                        .max()
599                        .expect("non-empty slice expected") // never fails, the slice is non-empty
600                        >= gx.batch_size
601                    {
602                        return Err(CircuitError::InvalidGate(
603                            gate.clone(),
604                            format!("slice indices out-of-range: {slice:?}"),
605                        ));
606                    }
607                }
608            }
609            Gate::PointFromPlaintextCoordinates { wires } => {
610                check_op!(
611                    "expected one input per point coordinate",
612                    gate => wires.len(), ==, NumCoordinates::<C::Curve>::USIZE
613                );
614                for x in wires {
615                    let gx = self.gate_ext(*x)?;
616                    check_gate_properties!(gx, is_base_field, is_plaintext);
617                    check_op!("expected batch-size 1", gate => gx.output.batch_size, ==, 1);
618                }
619            }
620            Gate::PlaintextPointToCoordinates { point: x, .. } => {
621                let gx = self.gate_ext(*x)?;
622                check_gate_properties!(gx, is_point, is_plaintext);
623                check_op!("expected batch-size 1", gate => gx.output.batch_size, ==, 1);
624            }
625            Gate::PlaintextKeccakF1600 { x } => {
626                let gx = self.gate_ext(*x)?;
627                check_gate_properties!(gx, is_bit, is_plaintext);
628                check_op!("expected batch-size 1600", gate => gx.output.batch_size, ==, 1600);
629            }
630            Gate::CompressPlaintextPoint { point: x, .. } => {
631                let gx = self.gate_ext(*x)?;
632                check_gate_properties!(gx, is_point, is_plaintext);
633                check_op!("expected batch-size 1", gate => gx.output.batch_size, ==, 1);
634            }
635            Gate::KeyRecoveryPlaintextComputeErrors {
636                d_minus_one,
637                syndromes,
638            } => {
639                let g1 = self.gate_ext(*d_minus_one)?;
640                let g2 = self.gate_ext(*syndromes)?;
641                check_gate_properties!(g1, is_base_field, is_plaintext);
642                check_gate_properties!(g2, is_base_field, is_plaintext);
643
644                check_op!("expected batch-size 1", gate => g1.output.batch_size, ==, 1);
645                // TODO: Check that the batch size of `g2` is correct.
646                check_op!(format!("expected batch-size {}", MXE_KEY_RECOVERY_D - 1),
647                    gate => g2.output.batch_size, ==, MXE_KEY_RECOVERY_D as u32 - 1);
648            }
649            Gate::AesKeySchedule { key } => {
650                let g = self.gate_ext(*key)?;
651                check_gate_properties!(g, is_bit, is_share);
652                let key_length = g.output.batch_size;
653                check_op!("key_length: expected 128, 192 or 256", gate => matches!(key_length, 128 | 192 | 256), ==, true);
654            }
655            Gate::AesGcmKeyStream {
656                round_keys,
657                iv,
658                n_ciphertext_blocks,
659            } => {
660                let g1 = self.gate_ext(*round_keys)?;
661                let g2 = self.gate_ext(*iv)?;
662                check_gate_properties!(g1, is_bit, is_share);
663                check_gate_properties!(g2, is_bit, is_plaintext);
664                let is_multiple = g1.output.batch_size.is_multiple_of(128);
665                check_op!("round_keys: expected batch-size multiple of 128", gate => is_multiple, ==, true);
666                let n_round_key_blocks = g1.output.batch_size / 128;
667                // 11, 13 and 15 correspond to AES-128, AES-192 and AES-256 respectively
668                check_op!("round_keys: expected 11, 13 or 15 128-bit round_keys", gate => matches!(n_round_key_blocks, 11 | 13 | 15), ==, true);
669                check_op!("IV: expected batch-size 96", gate => g2.output.batch_size, ==, 96);
670                // The batch size of a gate input/ouput is a u32, and since this task outputs
671                // (1+n_ciphertext_blocks) * 128 bit shares we require n_ciphertext_blocks to be
672                // less than 2^24.
673                check_op!("n_ciphertext_blocks: expected at most 2^24 - 1", gate => *n_ciphertext_blocks, <, 1u32 << 24);
674            }
675            Gate::GhashPowersOfH {
676                h,
677                n_ciphertext_blocks,
678            } => {
679                let g = self.gate_ext(*h)?;
680                check_gate_properties!(g, is_bit, is_share);
681                let len = g.output.batch_size;
682                check_op!("batch size of h must be 128", gate => len, ==, 128);
683                check_op!("n_ciphertext_blocks must be positive", gate => *n_ciphertext_blocks, >, 0);
684            }
685            Gate::Ghash { x, powers_of_h } => {
686                let g1 = self.gate_ext(*x)?;
687                let g2 = self.gate_ext(*powers_of_h)?;
688                check_gate_properties!(g1, is_bit, is_plaintext);
689                check_gate_properties!(g2, is_bit, is_share);
690                let x_len = g1.output.batch_size;
691                let powers_of_h_len = g2.output.batch_size;
692                check_op!("batch size of x must be equal to batch size of powers_of_h", gate => x_len, ==, powers_of_h_len);
693                check_op!("x must be non-empty", gate => x_len, !=, 0);
694                check_op!("batch size of x must be a multiple of 128", gate => x_len.is_multiple_of(128), ==, true);
695            }
696            Gate::ConstrainPlaintextBits { x, clauses, .. } => {
697                let gx = self.gate_ext(*x)?;
698                check_gate_properties!(gx, is_bit, is_plaintext);
699                let batch_size = gx.output.batch_size;
700                check_op!("expected at least one clause", gate => clauses.is_empty(), ==, false);
701
702                // Resolves how wide a wire is, for the operand length checks below. `None` for a
703                // wire that is out of range or not a plaintext bit batch; the per-wire checks
704                // report those properly.
705                let wire_bits = |wire: GateIndex| -> Option<u32> {
706                    let g = self.gate_ext(wire).ok()?;
707                    (g.output.is_bit() && g.output.is_plaintext()).then_some(g.output.batch_size)
708                };
709
710                for (i, clause) in clauses.iter().enumerate() {
711                    check_op!(format!("clause {i} is empty"), gate => clause.is_empty(), ==, false);
712
713                    // Whichever clause ends up satisfied is the only one pinning the bits down, so
714                    // coverage has to hold clause by clause rather than across the disjunction.
715                    let mut covered = vec![false; batch_size as usize];
716                    for constraint in clause.constraints() {
717                        for operand in constraint.operands() {
718                            check_op!(
719                                format!("clause {i}: expression nests deeper than {MAX_EXPR_DEPTH}"),
720                                gate => operand.depth(), <=, MAX_EXPR_DEPTH
721                            );
722                            check_op!(
723                                format!("clause {i}: expression has more than {MAX_EXPR_NODES} nodes"),
724                                gate => operand.node_count(), <=, MAX_EXPR_NODES
725                            );
726                            for slice in operand.slices() {
727                                check_op!(format!("clause {i}: slices must be non-empty"), gate => slice.is_empty(), ==, false);
728                                for index in slice.get_indices() {
729                                    check_op!(
730                                        format!("clause {i}: slice index out-of-range: {slice:?}"),
731                                        gate => index, <, batch_size
732                                    );
733                                }
734                            }
735                            for wire in operand.wires() {
736                                let g = self.gate_ext(wire)?;
737                                check_gate_properties!(g, is_bit, is_plaintext);
738                            }
739                        }
740
741                        match constraint {
742                            PlaintextBitConstraint::Signature {
743                                scheme,
744                                signature,
745                                message,
746                                public_key,
747                            } => {
748                                check_op!(
749                                    format!("clause {i}: expected a {}-bit {scheme:?} signature", scheme.signature_bits()),
750                                    gate => signature.static_len(&wire_bits), ==, Some(scheme.signature_bits())
751                                );
752                                check_op!(
753                                    format!("clause {i}: expected a {}-bit {scheme:?} public key", scheme.public_key_bits()),
754                                    gate => public_key.static_len(&wire_bits), ==, Some(scheme.public_key_bits())
755                                );
756                                // The message is signed as bytes, so a partial trailing byte could
757                                // never verify.
758                                let message_bits = message.static_len(&wire_bits);
759                                check_op!(
760                                    format!("clause {i}: signed message must be a whole number of bytes"),
761                                    gate => message_bits.is_none_or(|bits| bits.is_multiple_of(8)), ==, true
762                                );
763                            }
764                            PlaintextBitConstraint::Equality { bits, expected } => {
765                                let (lhs, rhs) =
766                                    (bits.static_len(&wire_bits), expected.static_len(&wire_bits));
767                                // Only enforced when both widths are known; an operand of
768                                // as-yet-unknown width is the case `static_len` leaves room for.
769                                check_op!(
770                                    format!("clause {i}: the two sides of an equality must be the same length"),
771                                    gate => lhs.zip(rhs).is_none_or(|(lhs, rhs)| lhs == rhs), ==, true
772                                );
773                            }
774                            PlaintextBitConstraint::Comparison { lhs, rhs, .. } => {
775                                let (lhs, rhs) =
776                                    (lhs.static_len(&wire_bits), rhs.static_len(&wire_bits));
777                                check_op!(
778                                    format!("clause {i}: the two sides of a comparison must be the same length"),
779                                    gate => lhs.zip(rhs).is_none_or(|(lhs, rhs)| lhs == rhs), ==, true
780                                );
781                                // The ordering is over bytes, so a partial trailing byte has no
782                                // place in it.
783                                check_op!(
784                                    format!("clause {i}: a comparison's operands must be a whole number of bytes"),
785                                    gate => lhs.into_iter().chain(rhs).all(|bits| bits.is_multiple_of(8)), ==, true
786                                );
787                            }
788                        }
789
790                        // Every operand was range-checked above; only the ones this constraint
791                        // *pins* count towards coverage. See `covering_operands`.
792                        for operand in constraint.covering_operands() {
793                            for slice in operand.slices() {
794                                for index in slice.get_indices() {
795                                    covered[index as usize] = true;
796                                }
797                            }
798                        }
799                    }
800                    let uncovered = covered.iter().filter(|c| !**c).count();
801                    check_op!(
802                        format!("clause {i}: every input bit must be covered by a constraint, {uncovered} are not"),
803                        gate => uncovered, ==, 0
804                    );
805                }
806            }
807        }
808
809        Ok(())
810    }
811
812    /// Computes the output type of gate.
813    ///
814    /// **Note: ** This function can panic if the gate is not valid.
815    fn comp_gate_output(&self, gate: &Gate<C>) -> Result<GateOutput, CircuitError<C>> {
816        let r = match gate {
817            Gate::Input(input_type) => GateOutput {
818                batch_size: input_type.batch_size(),
819                algebraic_type: input_type.algebraic_type(),
820                form: input_type.share_or_plaintext(),
821                is_dabit: false,
822            },
823
824            Gate::Constant(const_type) => GateOutput {
825                batch_size: const_type.batch_size()?,
826                algebraic_type: const_type.algebraic_type(),
827                form: ShareOrPlaintext::Plaintext,
828                is_dabit: false,
829            },
830
831            Gate::Random {
832                algebraic_type,
833                batch_size,
834            } => GateOutput {
835                batch_size: *batch_size,
836                algebraic_type: *algebraic_type,
837                form: ShareOrPlaintext::Share,
838                is_dabit: false,
839            },
840
841            Gate::FieldShareUnaryOp { x, op } => match op {
842                FieldShareUnaryOp::Neg | FieldShareUnaryOp::MulInverse => {
843                    self.gate_output_unchecked(*x)
844                }
845                FieldShareUnaryOp::Open | FieldShareUnaryOp::IsZero => self
846                    .gate_output_unchecked(*x)
847                    .with_new_form(ShareOrPlaintext::Plaintext),
848            },
849
850            Gate::FieldShareBinaryOp { x, .. }
851            | Gate::BitShareBinaryOp { x, .. }
852            | Gate::FieldPlaintextUnaryOp { x, .. }
853            | Gate::FieldPlaintextBinaryOp { x, .. }
854            | Gate::BitPlaintextUnaryOp { x, .. }
855            | Gate::BitPlaintextBinaryOp { x, .. }
856            | Gate::PointPlaintextUnaryOp { p: x, .. }
857            | Gate::PointPlaintextBinaryOp { p: x, .. }
858            | Gate::BaseFieldPow { x, .. } => self.gate_output_unchecked(*x),
859
860            Gate::BatchSummation { x, .. } => self.gate_output_unchecked(*x).with_new_batch_size(1),
861
862            Gate::PointShareBinaryOp { p: x, .. } => self
863                .gate_output_unchecked(*x)
864                .with_new_form(ShareOrPlaintext::Share),
865
866            Gate::BitShareUnaryOp { x, op } => match op {
867                BitShareUnaryOp::Not => self.gate_output_unchecked(*x),
868                BitShareUnaryOp::Open => self
869                    .gate_output_unchecked(*x)
870                    .with_new_form(ShareOrPlaintext::Plaintext),
871            },
872
873            Gate::PointShareUnaryOp { p: x, op } => match op {
874                PointShareUnaryOp::Neg => self.gate_output_unchecked(*x),
875                PointShareUnaryOp::Open => self
876                    .gate_output_unchecked(*x)
877                    .with_new_form(ShareOrPlaintext::Plaintext),
878                PointShareUnaryOp::IsZero => self
879                    .gate_output_unchecked(*x)
880                    .with_new_form(ShareOrPlaintext::Plaintext)
881                    .with_new_type(AlgebraicType::ScalarField),
882            },
883
884            Gate::DaBit {
885                field_type,
886                batch_size,
887            } => GateOutput {
888                batch_size: *batch_size,
889                algebraic_type: AlgebraicType::from(*field_type),
890                form: ShareOrPlaintext::Share,
891                is_dabit: true,
892            },
893
894            Gate::GetDaBitFieldShare { x, .. } => self.gate_output_unchecked(*x).without_dabit(),
895
896            Gate::GetDaBitSharedBit { x, .. } => self
897                .gate_output_unchecked(*x)
898                .with_new_type(AlgebraicType::Bit)
899                .without_dabit(),
900
901            Gate::BitPlaintextToField { x, field_type } => self
902                .gate_output_unchecked(*x)
903                .with_new_type(AlgebraicType::from(*field_type)),
904
905            Gate::FieldPlaintextToBit { x } => self
906                .gate_output_unchecked(*x)
907                .with_new_type(AlgebraicType::Bit),
908
909            Gate::ExtractFromBatch { x, slice } => self
910                .gate_output_unchecked(*x)
911                .with_new_batch_size(slice.len()),
912
913            Gate::CollectToBatch { wires, .. } => {
914                let batch_size = wires
915                    .iter()
916                    .map(|x| self.gate_output_unchecked(*x).batch_size)
917                    .sum();
918                self.gate_output_unchecked(wires[0])
919                    .with_new_batch_size(batch_size)
920            }
921
922            Gate::GatherFromBatches { parts } => {
923                let batch_size = parts.iter().map(|(_, slice)| slice.len()).sum();
924                self.gate_output_unchecked(parts[0].0)
925                    .with_new_batch_size(batch_size)
926            }
927
928            Gate::PointFromPlaintextCoordinates { .. } => GateOutput {
929                algebraic_type: AlgebraicType::Point,
930                form: ShareOrPlaintext::Plaintext,
931                batch_size: 1,
932                is_dabit: false,
933            },
934            Gate::PlaintextPointToCoordinates { .. } => GateOutput {
935                algebraic_type: AlgebraicType::BaseField,
936                form: ShareOrPlaintext::Plaintext,
937                batch_size: NumCoordinates::<C::Curve>::U32,
938                is_dabit: false,
939            },
940            Gate::PlaintextKeccakF1600 { .. } => GateOutput {
941                algebraic_type: AlgebraicType::Bit,
942                form: ShareOrPlaintext::Plaintext,
943                batch_size: 1600,
944                is_dabit: false,
945            },
946            Gate::CompressPlaintextPoint { .. } => GateOutput {
947                algebraic_type: AlgebraicType::Bit,
948                form: ShareOrPlaintext::Plaintext,
949                batch_size: 256,
950                is_dabit: false,
951            },
952            Gate::KeyRecoveryPlaintextComputeErrors { .. } => GateOutput {
953                algebraic_type: AlgebraicType::BaseField,
954                form: ShareOrPlaintext::Plaintext,
955                batch_size: MXE_KEY_RECOVERY_N as u32,
956                is_dabit: false,
957            },
958            Gate::AesKeySchedule { key } => {
959                let key_length = self.gate_output_unchecked(*key).batch_size;
960                let n_round_key_bits = match key_length {
961                    128 => 11 * 128,
962                    192 => 13 * 128,
963                    256 => 15 * 128,
964                    _ => {
965                        // the length of key was validated in Circuit::validate_gate
966                        panic!("key length {key_length} does not match any of 128, 192 or 256");
967                    }
968                };
969                GateOutput {
970                    algebraic_type: AlgebraicType::Bit,
971                    form: ShareOrPlaintext::Share,
972                    batch_size: n_round_key_bits,
973                    is_dabit: false,
974                }
975            }
976            Gate::AesGcmKeyStream {
977                n_ciphertext_blocks,
978                ..
979            } => GateOutput {
980                algebraic_type: AlgebraicType::Bit,
981                form: ShareOrPlaintext::Share,
982                batch_size: (1 + n_ciphertext_blocks) * 128,
983                is_dabit: false,
984            },
985            Gate::GhashPowersOfH {
986                n_ciphertext_blocks,
987                ..
988            } => GateOutput {
989                algebraic_type: AlgebraicType::Bit,
990                form: ShareOrPlaintext::Share,
991                batch_size: n_ciphertext_blocks * 128,
992                is_dabit: false,
993            },
994            Gate::Ghash { .. } => GateOutput {
995                algebraic_type: AlgebraicType::Bit,
996                form: ShareOrPlaintext::Share,
997                batch_size: 128,
998                is_dabit: false,
999            },
1000            // The agreed bits, plus a trailing success bit.
1001            Gate::ConstrainPlaintextBits { x, .. } => {
1002                let output = self.gate_output_unchecked(*x);
1003                let batch_size = output.batch_size;
1004                output.with_new_batch_size(batch_size + 1)
1005            }
1006        };
1007
1008        Ok(r)
1009    }
1010
1011    /// Computes the number of rounds required to evaluate the gate.
1012    ///
1013    /// **Note: ** This function can panic if the gate is not valid.
1014    fn comp_gate_comm_rounds(&self, gate: &Gate<C>) -> usize {
1015        match gate {
1016            Gate::Input(input_type) => match input_type {
1017                Input::SecretPlaintext { .. } => 1,
1018                _ => 0,
1019            },
1020
1021            Gate::Constant(_) | Gate::Random { .. } => 0,
1022
1023            Gate::FieldShareUnaryOp { op, .. } => match op {
1024                FieldShareUnaryOp::Neg => 0,
1025                FieldShareUnaryOp::MulInverse => 2,
1026                FieldShareUnaryOp::Open => 1,
1027                FieldShareUnaryOp::IsZero => 2,
1028            },
1029            Gate::FieldShareBinaryOp { op, y, .. } => {
1030                match (op, self.gate_output_unchecked(*y).form) {
1031                    (FieldShareBinaryOp::Mul, ShareOrPlaintext::Share) => 1,
1032                    (FieldShareBinaryOp::Mul, ShareOrPlaintext::Plaintext)
1033                    | (FieldShareBinaryOp::Add, _) => 0,
1034                }
1035            }
1036            Gate::BatchSummation { .. } => 0,
1037            Gate::BitShareUnaryOp { op, .. } => match op {
1038                BitShareUnaryOp::Not => 0,
1039                BitShareUnaryOp::Open => 1,
1040            },
1041            Gate::BitShareBinaryOp { op, y, .. } => {
1042                match (op, self.gate_output_unchecked(*y).form) {
1043                    (BitShareBinaryOp::Xor, _) => 0,
1044                    (_, ShareOrPlaintext::Share) => 1,
1045                    (_, ShareOrPlaintext::Plaintext) => 0,
1046                }
1047            }
1048            Gate::PointShareUnaryOp { op, .. } => match op {
1049                PointShareUnaryOp::Neg => 0,
1050                PointShareUnaryOp::Open => 1,
1051                PointShareUnaryOp::IsZero => 2,
1052            },
1053            Gate::PointShareBinaryOp { y, op, .. } => {
1054                match (op, self.gate_output_unchecked(*y).form) {
1055                    (PointShareBinaryOp::Add, _) => 0,
1056                    (PointShareBinaryOp::ScalarMul, ShareOrPlaintext::Share) => 1,
1057                    (PointShareBinaryOp::ScalarMul, ShareOrPlaintext::Plaintext) => 0,
1058                }
1059            }
1060
1061            Gate::BaseFieldPow { .. } => 2,
1062
1063            Gate::FieldPlaintextUnaryOp { .. }
1064            | Gate::FieldPlaintextBinaryOp { .. }
1065            | Gate::BitPlaintextUnaryOp { .. }
1066            | Gate::BitPlaintextBinaryOp { .. }
1067            | Gate::PointPlaintextUnaryOp { .. }
1068            | Gate::PointPlaintextBinaryOp { .. }
1069            | Gate::DaBit { .. }
1070            | Gate::GetDaBitFieldShare { .. }
1071            | Gate::GetDaBitSharedBit { .. }
1072            | Gate::BitPlaintextToField { .. }
1073            | Gate::FieldPlaintextToBit { .. }
1074            | Gate::ExtractFromBatch { .. }
1075            | Gate::CollectToBatch { .. }
1076            | Gate::GatherFromBatches { .. }
1077            | Gate::PointFromPlaintextCoordinates { .. }
1078            | Gate::PlaintextPointToCoordinates { .. }
1079            | Gate::PlaintextKeccakF1600 { .. }
1080            | Gate::CompressPlaintextPoint { .. }
1081            | Gate::KeyRecoveryPlaintextComputeErrors { .. }
1082            | Gate::Ghash { .. } => 0,
1083
1084            // Two: the broadcast in which peers exchange what they fetched, and the one in which
1085            // they exchange a digest of what they reconciled it to. Constraint checking and
1086            // selection in between are local, and so is comparing the digests.
1087            Gate::ConstrainPlaintextBits { .. } => 2,
1088
1089            Gate::AesKeySchedule { key, .. } => {
1090                let key_length = self.gate_ext(*key).map(|g| g.output.batch_size);
1091                let n_sub_bytes_calls = match key_length {
1092                    Ok(128) => 10,
1093                    Ok(192) => 8,
1094                    Ok(256) => 13,
1095                    _ => panic!("Invalid key length"),
1096                };
1097                n_sub_bytes_calls * AES_S_BOX_N_NETWORK_ROUNDS
1098            }
1099
1100            Gate::AesGcmKeyStream { round_keys, .. } => {
1101                let round_keys_length = self.gate_ext(*round_keys).map(|g| g.output.batch_size);
1102                // round keys length must be 11 * 128, 13 * 128 or 15 * 128
1103                // for AES-128, AES-192 and AES-256 respectively
1104                let n_rounds = match round_keys_length {
1105                    Ok(1408) => 10,
1106                    Ok(1664) => 12,
1107                    Ok(1920) => 14,
1108                    _ => panic!("Invalid round keys length"),
1109                };
1110                // in the first round we do a small optimization though this incurs two calls
1111                // to the S-box
1112                (1 + n_rounds) * AES_S_BOX_N_NETWORK_ROUNDS
1113            }
1114            Gate::GhashPowersOfH {
1115                n_ciphertext_blocks,
1116                ..
1117            } => {
1118                if *n_ciphertext_blocks <= 1 {
1119                    0
1120                } else {
1121                    (n_ciphertext_blocks - 1).ilog2() as usize + 1
1122                }
1123            }
1124        }
1125    }
1126
1127    /// Computes the communication level of the gate.
1128    ///
1129    /// **Note: ** This function can panic if the gate is not valid.
1130    fn comp_gate_level(&self, gate: &Gate<C>) -> GateLevel {
1131        let comm_rounds = self.comp_gate_comm_rounds(gate);
1132        match gate
1133            .get_inputs()
1134            .iter()
1135            .map(|pred| self.gate_level_unchecked(*pred))
1136            .max()
1137        {
1138            None => GateLevel::default(),
1139            Some(preds_level) => preds_level.next(comm_rounds),
1140        }
1141    }
1142
1143    #[cfg(feature = "dev")]
1144    pub fn remove_secret_shared_inputs(&mut self) {
1145        let mut inputs_to_remove = std::collections::HashSet::new();
1146        for (idx, gate) in self.gates.iter_mut().enumerate() {
1147            if let &Gate::Input(Input::Share {
1148                algebraic_type,
1149                batch_size,
1150            }) = &gate.gate
1151            {
1152                gate.gate = Gate::Random {
1153                    algebraic_type,
1154                    batch_size,
1155                };
1156                inputs_to_remove.insert(idx as GateIndex);
1157            }
1158        }
1159        self.inputs.retain(|gate| !inputs_to_remove.contains(gate));
1160    }
1161}
1162
1163#[cfg(test)]
1164mod tests {
1165    use crate::{
1166        circuit::{AlgebraicType, Circuit, FieldShareBinaryOp, Gate, Input},
1167        config::DefaultConfig as C,
1168    };
1169
1170    #[test]
1171    fn test_circuit_new() {
1172        let mut circuit = Circuit::<C>::new();
1173
1174        let x = circuit
1175            .add_gate(Gate::Input(Input::SecretPlaintext {
1176                inputer: 0,
1177                algebraic_type: AlgebraicType::MpcField,
1178                batch_size: 3,
1179            }))
1180            .unwrap();
1181
1182        let y = circuit
1183            .add_gate(Gate::Input(Input::SecretPlaintext {
1184                inputer: 0,
1185                algebraic_type: AlgebraicType::MpcField,
1186                batch_size: 3,
1187            }))
1188            .unwrap();
1189
1190        let z = circuit
1191            .add_gate(Gate::FieldShareBinaryOp {
1192                x,
1193                y,
1194                op: FieldShareBinaryOp::Mul,
1195            })
1196            .unwrap();
1197
1198        circuit.add_output(z).unwrap();
1199
1200        assert_eq!(circuit.nb_inputs(), 2);
1201        assert_eq!(circuit.nb_gates(), 2 + 1);
1202        assert_eq!(circuit.nb_outputs(), 1);
1203    }
1204}