Skip to main content

kimchi/circuits/polynomials/
complete_add.rs

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