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#[derive(Default, PartialEq, Debug, Clone)]
36pub struct Circuit<C: MpcConfig> {
37 pub(super) gates: Vec<GateExt<C>>,
39 pub(super) inputs: Vec<GateIndex>,
41 pub(super) outputs: Vec<GateIndex>,
43}
44
45#[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#[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 pub(super) is_dabit: bool,
66}
67
68#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Default)]
75pub struct GateLevel {
76 comm_level: usize,
77 level: usize, }
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 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 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 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 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 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 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 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 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 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 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 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 check_gate_properties!(self.gate_ext(*x)?, is_field, is_share);
529 }
530 Gate::GetDaBitSharedBit { x, .. } => {
531 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") >= 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") >= 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 (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 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}