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}