Skip to main content

fancy_garbling/circuits/
gcd.rs

1use crate::{
2    BinaryBundle, FancyBinary,
3    circuit::Circuit,
4    circuits::binary::{BinaryEquality, BinaryMultiplex, BinarySubtraction, Mux},
5};
6use core::marker::PhantomData;
7use swanky_channel::Channel;
8use swanky_error::Result;
9
10/// Given [`BinaryBundle`]s `a` and `b`, output `GCD(a, b)`.
11#[derive(Default)]
12pub struct Gcd<'a> {
13    upper_bound: usize,
14    _phantom: PhantomData<&'a ()>,
15}
16
17impl<'a> Gcd<'a> {
18    /// Create a new [`Gcd`] circuit using a fixed upper bound.
19    ///
20    /// Since the circuit needs to be oblivious, we cannot terminate the GCD
21    /// algorithm by conditioning on the values of `a` or `b` as is the case in
22    /// the "standard" version of GCD. Instead, we rely on an upper bound on the
23    /// number of iterations we know the algorithm will terminate by. The
24    /// Euclidean algorithm based on subtractions will take no more than `N` steps
25    /// where `N` is the larger of the two numbers we are computing the GCD for.
26    pub fn new(upper_bound: usize) -> Self {
27        Self {
28            upper_bound,
29            _phantom: PhantomData,
30        }
31    }
32}
33
34impl<'a, F: FancyBinary> Circuit<F> for Gcd<'a>
35where
36    F::Item: 'a,
37{
38    type Input = (&'a BinaryBundle<F::Item>, &'a BinaryBundle<F::Item>);
39    type Output = BinaryBundle<F::Item>;
40
41    fn execute(
42        &self,
43        backend: &mut F,
44        inputs: Self::Input,
45        channel: &mut Channel,
46    ) -> Result<Self::Output> {
47        let (a_ref, b_ref) = inputs;
48        let mut a = (*a_ref).clone();
49        let mut b = (*b_ref).clone();
50        let zero = backend.constant(0, 2, channel)?;
51
52        for _ in 0..self.upper_bound {
53            // Since the circuit is non-branching, we don't know whether `a > b`
54            // and cannot branch computation based on that result of that
55            // conditional. Instead, we need to perform the computation that
56            // occurs for all cases of the predict "is a > b ?", i.e. (1) `a >
57            // b`, and (2) `b > a`. We consider the case where `a == b`
58            // separately since that is the case where we stop updating our
59            // variables and find the result of the computation `gcd(a,b)`.
60
61            // Compute `a := a - b` and check for an underflow that will help
62            // determine if `a > b`.
63            let (r_1, mut underflow_r_1) =
64                BinarySubtraction::new().execute(backend, (&a, &b), channel)?;
65            // Compute `b := b - a` and check for an underflow that will help
66            // determine if `b > a`.
67            let (r_2, mut underflow_r_2) =
68                BinarySubtraction::new().execute(backend, (&b, &a), channel)?;
69
70            let is_equal = BinaryEquality::new().execute(backend, (&a, &b), channel)?;
71
72            // The `underflow` bits act as dual purpose multiplexing bits:
73            // (1) If a > b then underflow_r_1 = 1 and underflow_r_2 = 0
74            // (2) If b > a then underflow_r_1 = 0 and underflow_r_2 = 1
75            // (3) If a == b then underflow_r_1 = underflow_r_2 = 0
76            underflow_r_1 =
77                Mux::new().execute(backend, (&is_equal, &underflow_r_1, &zero), channel)?;
78            underflow_r_2 =
79                Mux::new().execute(backend, (&is_equal, &underflow_r_2, &zero), channel)?;
80
81            // Using the `underflow` bits we multiplex in the following way:
82            // (1) If a > b, a := a - b and b := b
83            // (2) If b > a, a := a  and b := b - a
84            // (3) If a == b, a := a and b := b
85            a = BinaryMultiplex::new().execute(backend, (underflow_r_1, &a, &r_1), channel)?;
86            b = BinaryMultiplex::new().execute(backend, (underflow_r_2, &b, &r_2), channel)?;
87        }
88
89        Ok(a)
90    }
91}