Skip to main content

core_utils/circuit/latest/
gate.rs

1use std::hash::Hash;
2
3use primitives::algebra::{
4    elliptic_curve::{Point, Scalar},
5    BoxedUint,
6};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    circuit::{
11        errors::CircuitError,
12        AlgebraicType,
13        BatchSize,
14        BitPlaintextBinaryOp,
15        BitPlaintextUnaryOp,
16        BitShareBinaryOp,
17        BitShareUnaryOp,
18        Constant,
19        ConstraintClause,
20        FieldPlaintextBinaryOp,
21        FieldPlaintextUnaryOp,
22        FieldShareBinaryOp,
23        FieldShareUnaryOp,
24        FieldType,
25        GateIndex,
26        Input,
27        OnAmbiguity,
28        PointPlaintextBinaryOp,
29        PointPlaintextUnaryOp,
30        PointShareBinaryOp,
31        PointShareUnaryOp,
32        Slice,
33    },
34    config::MpcConfig,
35};
36
37/// Gate operations, where the operation arguments correspond to _wire_ label.
38#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
39#[serde(bound(
40    serialize = "Scalar<C::Curve>: Serialize, Point<C::Curve>: Serialize",
41    deserialize = "Scalar<C::Curve>: Deserialize<'de>, Point<C::Curve>: Deserialize<'de>"
42))]
43#[repr(C)]
44pub enum Gate<C: MpcConfig> {
45    /// Input a wire
46    Input(Input),
47    /// Input a constant value
48    Constant(Constant<C>),
49    /// Generate random shares
50    Random {
51        algebraic_type: AlgebraicType,
52        batch_size: BatchSize,
53    },
54    /// Field share unary operations
55    FieldShareUnaryOp {
56        x: GateIndex,
57        op: FieldShareUnaryOp,
58    },
59    /// Field share binary operations, where the second wire may be a plaintext.
60    FieldShareBinaryOp {
61        x: GateIndex,
62        y: GateIndex,
63        op: FieldShareBinaryOp,
64    },
65    BatchSummation {
66        x: GateIndex,
67    },
68    BitShareUnaryOp {
69        x: GateIndex,
70        op: BitShareUnaryOp,
71    },
72    BitShareBinaryOp {
73        x: GateIndex,
74        y: GateIndex,
75        op: BitShareBinaryOp,
76    },
77    /// Operations with elliptic curve points
78    PointShareUnaryOp {
79        p: GateIndex,
80        op: PointShareUnaryOp,
81    },
82    PointShareBinaryOp {
83        p: GateIndex,
84        y: GateIndex,
85        op: PointShareBinaryOp,
86    },
87    /// Field plaintext unary operations
88    FieldPlaintextUnaryOp {
89        x: GateIndex,
90        op: FieldPlaintextUnaryOp,
91    },
92    /// Field plaintext binary operations
93    FieldPlaintextBinaryOp {
94        x: GateIndex,
95        y: GateIndex,
96        op: FieldPlaintextBinaryOp,
97    },
98    BitPlaintextUnaryOp {
99        x: GateIndex,
100        op: BitPlaintextUnaryOp,
101    },
102    BitPlaintextBinaryOp {
103        x: GateIndex,
104        y: GateIndex,
105        op: BitPlaintextBinaryOp,
106    },
107    PointPlaintextUnaryOp {
108        p: GateIndex,
109        op: PointPlaintextUnaryOp,
110    },
111    PointPlaintextBinaryOp {
112        p: GateIndex,
113        y: GateIndex,
114        op: PointPlaintextBinaryOp,
115    },
116    /// Request a daBit
117    DaBit {
118        field_type: FieldType,
119        batch_size: BatchSize,
120    },
121    GetDaBitFieldShare {
122        x: GateIndex,
123    },
124    GetDaBitSharedBit {
125        x: GateIndex,
126    },
127    /// Base field exponentiation operation
128    BaseFieldPow {
129        x: GateIndex,
130        exp: BoxedUint,
131    },
132    /// Bit plaintext conversion operations
133    BitPlaintextToField {
134        x: GateIndex,
135        field_type: FieldType,
136    },
137    FieldPlaintextToBit {
138        x: GateIndex,
139    },
140    /// Get a slice of elements from a batched wire
141    ExtractFromBatch {
142        x: GateIndex,
143        slice: Slice,
144    },
145    CollectToBatch {
146        wires: Vec<GateIndex>,
147    },
148    PointFromPlaintextCoordinates {
149        wires: Vec<GateIndex>,
150    },
151    PlaintextPointToCoordinates {
152        point: GateIndex,
153    },
154    PlaintextKeccakF1600 {
155        x: GateIndex,
156    },
157    CompressPlaintextPoint {
158        point: GateIndex,
159    },
160    KeyRecoveryPlaintextComputeErrors {
161        d_minus_one: GateIndex,
162        syndromes: GateIndex,
163    },
164    AesGcmKeyStream {
165        round_keys: GateIndex,
166        iv: GateIndex,
167        n_ciphertext_blocks: u32,
168    },
169    GhashPowersOfH {
170        h: GateIndex,
171        n_ciphertext_blocks: u32,
172    },
173    Ghash {
174        x: GateIndex,
175        powers_of_h: GateIndex,
176    },
177    // Variant order is part of the wire format: append new gates here, never insert !!!
178    AesKeySchedule {
179        key: GateIndex,
180    },
181    /// Plaintext bits that each peer supplies locally rather than reading from the agreed public
182    /// inputs — a URL response, say — together with the constraints they must satisfy.
183    ///
184    /// Outputs `x.batch_size + 1` bits: the reconciled data, then a success bit. When no candidate
185    /// satisfies any clause the data bits are all zero and the success bit is false, so the gate
186    /// is total — every input has an output, and that output is the same on every peer.
187    /// Failing this way rather than aborting is also what lets the gate be exercised by
188    /// randomised tests, which would otherwise abort before reaching anything downstream.
189    ///
190    /// Because peers fetch independently, they may start from different bits: a URL can serve
191    /// different data to different peers, or fail to answer some of them. Reconciling that is the
192    /// task's job, not the gate's; the gate only records what the bits must satisfy. Every
193    /// constraint is a deterministic predicate over plaintext, so no secure computation is
194    /// involved and constraints may be arbitrarily expensive.
195    ///
196    /// `clauses` is a disjunctive normal form: the bits are acceptable when some clause holds, and
197    /// a clause holds when all of its constraints do. The slices in a clause index into `x`, and
198    /// each clause must cover `x` on its own — a bit the satisfied clause does not mention has
199    /// nothing to pin it down when peers disagree.
200    ConstrainPlaintextBits {
201        x: GateIndex,
202        clauses: Vec<ConstraintClause>,
203        /// What to do when the satisfying candidates are not all the same batch. Does not affect
204        /// the gate's shape: the output is `x.batch_size + 1` bits either way.
205        on_ambiguity: OnAmbiguity,
206    },
207}
208
209impl<C: MpcConfig> Gate<C> {
210    /// Check whether the gate is an input gate.
211    pub fn is_input(&self) -> bool {
212        matches!(self, Gate::Input { .. })
213    }
214
215    /// Returns the indices of gate inputs.
216    pub fn get_inputs(&self) -> Vec<GateIndex> {
217        match &self {
218            Gate::Input(_) | Gate::Random { .. } | Gate::Constant(_) | Gate::DaBit { .. } => {
219                Vec::new()
220            }
221
222            Gate::FieldShareUnaryOp { x, .. }
223            | Gate::BatchSummation { x, .. }
224            | Gate::BitShareUnaryOp { x, .. }
225            | Gate::PointShareUnaryOp { p: x, .. }
226            | Gate::FieldPlaintextUnaryOp { x, .. }
227            | Gate::BitPlaintextUnaryOp { x, .. }
228            | Gate::PointPlaintextUnaryOp { p: x, .. }
229            | Gate::GetDaBitFieldShare { x, .. }
230            | Gate::GetDaBitSharedBit { x, .. }
231            | Gate::BaseFieldPow { x, .. }
232            | Gate::BitPlaintextToField { x, .. }
233            | Gate::FieldPlaintextToBit { x, .. }
234            | Gate::ExtractFromBatch { x, .. }
235            | Gate::PlaintextPointToCoordinates { point: x, .. }
236            | Gate::CompressPlaintextPoint { point: x, .. }
237            | Gate::PlaintextKeccakF1600 { x }
238            | Gate::GhashPowersOfH { h: x, .. }
239            | Gate::AesKeySchedule { key: x } => {
240                vec![*x]
241            }
242
243            Gate::FieldShareBinaryOp { x, y, .. }
244            | Gate::BitShareBinaryOp { x, y, .. }
245            | Gate::PointShareBinaryOp { p: x, y, .. }
246            | Gate::FieldPlaintextBinaryOp { x, y, .. }
247            | Gate::BitPlaintextBinaryOp { x, y, .. }
248            | Gate::PointPlaintextBinaryOp { p: x, y, .. }
249            | Gate::KeyRecoveryPlaintextComputeErrors {
250                d_minus_one: x,
251                syndromes: y,
252                ..
253            }
254            | Gate::AesGcmKeyStream {
255                round_keys: x,
256                iv: y,
257                ..
258            }
259            | Gate::Ghash { x, powers_of_h: y } => {
260                vec![*x, *y]
261            }
262
263            Gate::CollectToBatch { wires, .. }
264            | Gate::PointFromPlaintextCoordinates { wires, .. } => wires.clone(),
265
266            Gate::ConstrainPlaintextBits { x, clauses, .. } => std::iter::once(*x)
267                .chain(clauses.iter().flat_map(ConstraintClause::wires))
268                .collect(),
269        }
270    }
271
272    /// Maps inplace gate inputs using the given function.
273    pub fn map_inputs<F: FnMut(GateIndex) -> GateIndex>(mut self, mut f: F) -> Self {
274        match &mut self {
275            Gate::Input(_) | Gate::Random { .. } | Gate::Constant(_) | Gate::DaBit { .. } => (),
276
277            Gate::FieldShareUnaryOp { x, .. }
278            | Gate::BatchSummation { x, .. }
279            | Gate::BitShareUnaryOp { x, .. }
280            | Gate::PointShareUnaryOp { p: x, .. }
281            | Gate::FieldPlaintextUnaryOp { x, .. }
282            | Gate::BitPlaintextUnaryOp { x, .. }
283            | Gate::PointPlaintextUnaryOp { p: x, .. }
284            | Gate::GetDaBitFieldShare { x, .. }
285            | Gate::GetDaBitSharedBit { x, .. }
286            | Gate::BaseFieldPow { x, .. }
287            | Gate::BitPlaintextToField { x, .. }
288            | Gate::FieldPlaintextToBit { x, .. }
289            | Gate::ExtractFromBatch { x, .. }
290            | Gate::PlaintextPointToCoordinates { point: x, .. }
291            | Gate::CompressPlaintextPoint { point: x, .. }
292            | Gate::PlaintextKeccakF1600 { x }
293            | Gate::GhashPowersOfH { h: x, .. }
294            | Gate::AesKeySchedule { key: x } => {
295                *x = f(*x);
296            }
297
298            Gate::FieldShareBinaryOp { x, y, .. }
299            | Gate::BitShareBinaryOp { x, y, .. }
300            | Gate::PointShareBinaryOp { p: x, y, .. }
301            | Gate::FieldPlaintextBinaryOp { x, y, .. }
302            | Gate::BitPlaintextBinaryOp { x, y, .. }
303            | Gate::PointPlaintextBinaryOp { p: x, y, .. }
304            | Gate::KeyRecoveryPlaintextComputeErrors {
305                d_minus_one: x,
306                syndromes: y,
307                ..
308            }
309            | Gate::AesGcmKeyStream {
310                round_keys: x,
311                iv: y,
312                ..
313            }
314            | Gate::Ghash { x, powers_of_h: y } => {
315                *x = f(*x);
316                *y = f(*y);
317            }
318
319            Gate::CollectToBatch { wires, .. }
320            | Gate::PointFromPlaintextCoordinates { wires, .. } => {
321                wires.iter_mut().for_each(|x| *x = f(*x))
322            }
323
324            Gate::ConstrainPlaintextBits { x, clauses, .. } => {
325                *x = f(*x);
326                clauses
327                    .iter_mut()
328                    .flat_map(ConstraintClause::wires_mut)
329                    .for_each(|input| *input = f(*input));
330            }
331        };
332
333        self
334    }
335
336    /// Tries to replace the gate inputs with the given ones.
337    ///
338    /// This function returns an error if the number of given inputs does not match the number of
339    /// gate inputs.
340    pub fn try_replace_inputs(mut self, inputs: Vec<GateIndex>) -> Result<Self, CircuitError<C>> {
341        if inputs.len() != self.get_inputs().len() {
342            return Err(CircuitError::InvalidGateInputCount {
343                expected: self.get_inputs().len(),
344                found: inputs.len(),
345            });
346        }
347
348        match &mut self {
349            Gate::Input(_) | Gate::Random { .. } | Gate::Constant(_) | Gate::DaBit { .. } => (),
350
351            Gate::FieldShareUnaryOp { x, .. }
352            | Gate::BatchSummation { x, .. }
353            | Gate::BitShareUnaryOp { x, .. }
354            | Gate::PointShareUnaryOp { p: x, .. }
355            | Gate::FieldPlaintextUnaryOp { x, .. }
356            | Gate::BitPlaintextUnaryOp { x, .. }
357            | Gate::PointPlaintextUnaryOp { p: x, .. }
358            | Gate::GetDaBitFieldShare { x, .. }
359            | Gate::GetDaBitSharedBit { x, .. }
360            | Gate::BaseFieldPow { x, .. }
361            | Gate::BitPlaintextToField { x, .. }
362            | Gate::FieldPlaintextToBit { x, .. }
363            | Gate::ExtractFromBatch { x, .. }
364            | Gate::PlaintextPointToCoordinates { point: x, .. }
365            | Gate::CompressPlaintextPoint { point: x, .. }
366            | Gate::PlaintextKeccakF1600 { x }
367            | Gate::GhashPowersOfH { h: x, .. }
368            | Gate::AesKeySchedule { key: x } => {
369                *x = inputs[0];
370            }
371
372            Gate::FieldShareBinaryOp { x, y, .. }
373            | Gate::BitShareBinaryOp { x, y, .. }
374            | Gate::PointShareBinaryOp { p: x, y, .. }
375            | Gate::FieldPlaintextBinaryOp { x, y, .. }
376            | Gate::BitPlaintextBinaryOp { x, y, .. }
377            | Gate::PointPlaintextBinaryOp { p: x, y, .. }
378            | Gate::KeyRecoveryPlaintextComputeErrors {
379                d_minus_one: x,
380                syndromes: y,
381                ..
382            }
383            | Gate::AesGcmKeyStream {
384                round_keys: x,
385                iv: y,
386                ..
387            }
388            | Gate::Ghash { x, powers_of_h: y } => {
389                *x = inputs[0];
390                *y = inputs[1];
391            }
392
393            Gate::CollectToBatch { wires, .. }
394            | Gate::PointFromPlaintextCoordinates { wires, .. } => *wires = inputs,
395
396            Gate::ConstrainPlaintextBits { x, clauses, .. } => {
397                let mut inputs = inputs.into_iter();
398                // The count was checked above, and `get_inputs` lists `x` before the clauses' own
399                // inputs.
400                *x = inputs.next().expect("input count checked above");
401                clauses
402                    .iter_mut()
403                    .flat_map(ConstraintClause::wires_mut)
404                    .zip(inputs)
405                    .for_each(|(slot, input)| *slot = input);
406            }
407        };
408
409        Ok(self)
410    }
411}
412
413#[cfg(test)]
414mod tests {
415    use std::collections::HashSet;
416
417    use primitives::utils::codec::bincode_io;
418
419    use super::*;
420    use crate::{circuit::FieldShareBinaryOp, config::DefaultConfig as C};
421
422    #[test]
423    fn test_ser_gate() {
424        let scalar_gate: Gate<C> = Gate::FieldShareBinaryOp {
425            x: 1,
426            y: 3,
427            op: FieldShareBinaryOp::Add,
428        };
429        let point_gate: Gate<C> = Gate::PointShareBinaryOp {
430            p: 1,
431            y: 3,
432            op: PointShareBinaryOp::Add,
433        };
434
435        let scalar_gate_ser = bincode_io::serialize(&scalar_gate).unwrap();
436        let point_gate_ser = bincode_io::serialize(&point_gate).unwrap();
437
438        let scalar_gate_de: Gate<C> = bincode_io::deserialize(&scalar_gate_ser).unwrap();
439        let point_gate_de: Gate<C> = bincode_io::deserialize(&point_gate_ser).unwrap();
440
441        assert_eq!(scalar_gate, scalar_gate_de);
442        assert_eq!(point_gate, point_gate_de);
443        let set = HashSet::from([scalar_gate, scalar_gate_de, point_gate, point_gate_de]);
444        assert_eq!(set.len(), 2)
445    }
446}