kimchi/circuits/polynomials/
complete_add.rs

1//! This module implements a complete EC addition gate.
2
3//~ The layout is
4//~
5//~ |  0 |  1 |  2 |  3 |  4 |  5 |  6  |    7   | 8 |   9   |    10   |
6//~ |:--:|:--:|:--:|:--:|:--:|:--:|:---:|:------:|:-:|:-----:|:-------:|
7//~ | x1 | y1 | x2 | y2 | x3 | y3 | inf | same_x | s | inf_z | x21_inv |
8//~
9//~ where
10//~
11//~ * `(x1, y1), (x2, y2)` are the inputs and `(x3, y3)` the output.
12//~ * `inf` is a boolean that is true iff the result (x3, y3) is the point at infinity.
13//~
14//~ The rest of the values are inaccessible from the permutation argument, but
15//~ `same_x` is a boolean that is true iff `x1 == x2`.
16//~
17use crate::circuits::{
18    argument::{Argument, ArgumentEnv, ArgumentType},
19    berkeley_columns::BerkeleyChallengeTerm,
20    expr::{constraints::ExprOps, Cache},
21    gate::{CircuitGate, GateType},
22    wires::COLUMNS,
23};
24use ark_ff::{Field, PrimeField};
25use core::marker::PhantomData;
26
27/// This enforces that
28///
29/// r = (z == 0) ? 1 : 0
30///
31/// Additionally, if r == 0, then `z_inv` = 1 / z.
32///
33/// If r == 1 however (i.e., if z == 0), then z_inv is unconstrained.
34fn zero_check<F: Field, T: ExprOps<F, BerkeleyChallengeTerm>>(z: T, z_inv: T, r: T) -> Vec<T> {
35    vec![z_inv * z.clone() - (T::one() - r.clone()), r * z]
36}
37
38//~ The following constraints are generated:
39//~
40//~ constraint 1:
41//~
42//~ * $x_{0} = w_{2} - w_{0}$
43//~ * $(w_{10} \cdot x_{0} - \mathbb{F}(1) - w_{7})$
44//~
45//~ constraint 2:
46//~
47//~ * $x_{0} = w_{2} - w_{0}$
48//~ * $w_{7} \cdot x_{0}$
49//~
50//~ constraint 3:
51//~
52//~ * $x_{0} = w_{2} - w_{0}$
53//~ * $x_{1} = w_{3} - w_{1}$
54//~ * $x_{2} = w_{0} \cdot w_{0}$
55//~ * $w_{7} \cdot (2 \cdot w_{8} \cdot w_{1} - 2 \cdot x_{2} - x_{2}) + (\mathbb{F}(1) - w_{7}) \cdot (x_{0} \cdot w_{8} - x_{1})$
56//~
57//~ constraint 4:
58//~
59//~ * $w_{0} + w_{2} + w_{4} - w_{8} \cdot w_{8}$
60//~
61//~ constraint 5:
62//~
63//~ * $w_{8} \cdot (w_{0} - w_{4}) - w_{1} - w_{5}$
64//~
65//~ constraint 6:
66//~
67//~ * $x_{1} = w_{3} - w_{1}$
68//~ * $x_{1} \cdot (w_{7} - w_{6})$
69//~
70//~ constraint 7:
71//~
72//~ * $x_{1} = w_{3} - w_{1}$
73//~ * $x_{1} \cdot w_{9} - w_{6}$
74//~
75
76/// Implementation of the `CompleteAdd` gate
77/// It uses the constraints
78///
79///   (x2 - x1) * s = y2 - y1
80///   s^2 = x1 + x2 + x3
81///   y3 = s (x1 - x3) - y1
82///
83/// for addition and
84///
85///   2 * s * y1 = 3 * x1^2
86///   s^2 = 2 x1 + x3
87///   y3 = s (x1 - x3) - y1
88///
89/// for doubling.
90///
91/// See [here](https://en.wikipedia.org/wiki/Elliptic_curve#The_group_law) for the formulas used.
92#[derive(Default)]
93pub struct CompleteAdd<F>(PhantomData<F>);
94
95impl<F> Argument<F> for CompleteAdd<F>
96where
97    F: PrimeField,
98{
99    const ARGUMENT_TYPE: ArgumentType = ArgumentType::Gate(GateType::CompleteAdd);
100    const CONSTRAINTS: u32 = 7;
101
102    fn constraint_checks<T: ExprOps<F, BerkeleyChallengeTerm>>(
103        env: &ArgumentEnv<F, T>,
104        cache: &mut Cache,
105    ) -> Vec<T> {
106        // This function makes 2 + 1 + 1 + 1 + 2 = 7 constraints
107        let x1 = env.witness_curr(0);
108        let y1 = env.witness_curr(1);
109        let x2 = env.witness_curr(2);
110        let y2 = env.witness_curr(3);
111        let x3 = env.witness_curr(4);
112        let y3 = env.witness_curr(5);
113
114        let inf = env.witness_curr(6);
115        // same_x is 1 if x1 == x2, 0 otherwise
116        let same_x = env.witness_curr(7);
117
118        let s = env.witness_curr(8);
119
120        // This variable is used to constrain inf
121        let inf_z = env.witness_curr(9);
122
123        // This variable is used to constrain same_x
124        let x21_inv = env.witness_curr(10);
125
126        let x21 = cache.cache(x2.clone() - x1.clone());
127        let y21 = cache.cache(y2 - y1.clone());
128
129        // same_x is now constrained
130        let mut res = zero_check(x21.clone(), x21_inv, same_x.clone());
131
132        // this constrains s so that
133        // if same_x:
134        //   2 * s * y1 = 3 * x1^2
135        // else:
136        //   (x2 - x1) * s = y2 - y1
137        {
138            let x1_squared = cache.cache(x1.clone() * x1.clone());
139            let dbl_case = s.double() * y1.clone() - x1_squared.double() - x1_squared;
140            let add_case = x21 * s.clone() - y21.clone();
141
142            res.push(same_x.clone() * dbl_case + (T::one() - same_x.clone()) * add_case);
143        }
144
145        // Unconditionally constrain
146        //
147        // s^2 = x1 + x2 + x3
148        //
149        // This constrains x3.
150        res.push(x1.clone() + x2 + x3.clone() - s.clone() * s.clone());
151
152        // Unconditionally constrain
153        // y3 = -y1 + s(x1 - x3)
154        //
155        // This constrains y3.
156        res.push(s * (x1 - x3) - y1 - y3);
157
158        // It only remains to constrain inf
159        //
160        // The result is the point at infinity only if x1 == x2 but y1 != y2. I.e.,
161        //
162        // inf = same_x && !(y1 == y2)
163        //
164        // We can do this using a modified version of the zero_check constraints
165        //
166        // Let Y = (y1 == y2).
167        //
168        // The zero_check constraints for Y (introducing a new z_inv variable) would be
169        //
170        // (y2 - y1) Y = 0
171        // (y2 - y1) z_inv = 1 - Y
172        //
173        // By definition,
174        //
175        // inf = same_x * (1 - Y) = same_x - Y same_x
176        //
177        // rearranging gives
178        //
179        // Y same_x = same_x - inf
180        //
181        // so multiplying the above constraints through by same_x yields constraints on inf.
182        //
183        // (y2 - y1) same_x Y = 0
184        // (y2 - y1) same_x z_inv = inf
185        //
186        // i.e.,
187        //
188        // (y2 - y1) (same_x - inf) = 0
189        // (y2 - y1) same_x z_inv = inf
190        //
191        // Now, since z_inv was an arbitrary variable, unused elsewhere, we'll set
192        // inf_z to take on the value of same_x * z_inv, and thus we have equations
193        //
194        // (y2 - y1) (same_x - inf) = 0
195        // (y2 - y1) inf_z = inf
196        //
197        // Let's check that these equations are correct.
198        //
199        // Case 1: [y1 == y2]
200        //   In this case the expected result is inf = 0, since for the result to be the point at
201        //   infinity we need y1 = -y2 (note here we assume y1 != 0, which is the case for prime order
202        //   curves).
203        //
204        //   y2 - y1 = 0, so the second equation becomes inf = 0, which is correct.
205        //
206        //   We can set inf_z = 0 in this case.
207        //
208        // Case 2: [y1 != y2]
209        //   In this case, the expected result is 1 if x1 == x2, and 0 if x1 != x2.
210        //   I.e., inf = same_x.
211        //
212        //   y2 - y1 != 0, so the first equation implies same_x - inf = 0.
213        //   I.e., inf = same_x, as desired.
214        //
215        //   In this case, we set
216        //   inf_z = if same_x then 1 / (y2 - y1) else 0
217
218        res.push(y21.clone() * (same_x - inf.clone()));
219        res.push(y21 * inf_z - inf);
220
221        res
222    }
223}
224
225impl<F: PrimeField> CircuitGate<F> {
226    /// Check the correctness of witness values for a complete-add gate.
227    ///
228    /// # Errors
229    ///
230    /// Will give error if the gate value validations are not met.
231    ///
232    /// # Panics
233    ///
234    /// Will panic if `multiplicative inverse` operation between gate values fails.
235    pub fn verify_complete_add(
236        &self,
237        row: usize,
238        witness: &[Vec<F>; COLUMNS],
239    ) -> Result<(), String> {
240        let x1 = witness[0][row];
241        let y1 = witness[1][row];
242        let x2 = witness[2][row];
243        let y2 = witness[3][row];
244        let x3 = witness[4][row];
245        let y3 = witness[5][row];
246        let inf = witness[6][row];
247        let same_x = witness[7][row];
248        let s = witness[8][row];
249        let inf_z = witness[9][row];
250        let x21_inv = witness[10][row];
251
252        if x1 == x2 {
253            ensure_eq!(same_x, F::one(), "Expected same_x = true");
254        } else {
255            ensure_eq!(same_x, F::zero(), "Expected same_x = false");
256        }
257
258        if same_x == F::one() {
259            let x1_squared = x1.square();
260            ensure_eq!(
261                (s + s) * y1,
262                (x1_squared.double() + x1_squared),
263                "double s wrong"
264            );
265        } else {
266            ensure_eq!((x2 - x1) * s, y2 - y1, "add s wrong");
267        }
268
269        ensure_eq!(s.square(), x1 + x2 + x3, "x3 wrong");
270        let expected_y3 = s * (x1 - x3) - y1;
271        ensure_eq!(
272            y3,
273            expected_y3,
274            format!("y3 wrong {row}: (expected {expected_y3}, got {y3})")
275        );
276
277        let not_same_y = F::from(u64::from(y1 != y2));
278        ensure_eq!(inf, same_x * not_same_y, "inf wrong");
279
280        if y1 == y2 {
281            ensure_eq!(inf_z, F::zero(), "wrong inf z (y1 == y2)");
282        } else {
283            let a = if same_x == F::one() {
284                (y2 - y1).inverse().unwrap()
285            } else {
286                F::zero()
287            };
288            ensure_eq!(inf_z, a, "wrong inf z (y1 != y2)");
289        }
290
291        if x1 == x2 {
292            ensure_eq!(x21_inv, F::zero(), "wrong x21_inv (x1 == x2)");
293        } else {
294            ensure_eq!(
295                x21_inv,
296                (x2 - x1).inverse().unwrap(),
297                "wrong x21_inv (x1 != x2)"
298            );
299        }
300
301        Ok(())
302    }
303}