Skip to main content

core_utils/circuit/v2/
circuit.rs

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