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