|
1 |
| -use std::{cell::RefCell, iter, rc::Rc}; |
| 1 | +use std::{cell::RefCell, rc::Rc}; |
2 | 2 |
|
3 |
| -use itertools::{zip_eq, Itertools}; |
4 | 3 | use num_bigint::BigUint;
|
5 |
| -use num_traits::{One, Zero}; |
6 |
| -use openvm_circuit::arch::{ |
7 |
| - AdapterAirContext, AdapterRuntimeContext, DynAdapterInterface, DynArray, MinimalInstruction, |
8 |
| - Result, VmAdapterInterface, VmCoreAir, VmCoreChip, |
9 |
| -}; |
10 |
| -use openvm_circuit_primitives::{ |
11 |
| - bigint::utils::big_uint_to_num_limbs, |
12 |
| - var_range::{SharedVariableRangeCheckerChip, VariableRangeCheckerBus}, |
13 |
| - SubAir, TraceSubRowGenerator, |
14 |
| -}; |
15 |
| -use openvm_ecc_transpiler::Rv32WeierstrassOpcode; |
16 |
| -use openvm_instructions::instruction::Instruction; |
17 |
| -use openvm_mod_circuit_builder::{ |
18 |
| - utils::{biguint_to_limbs_vec, limbs_to_biguint}, |
19 |
| - ExprBuilder, ExprBuilderConfig, FieldExpr, FieldExprCols, FieldVariable, |
20 |
| -}; |
21 |
| -use openvm_stark_backend::{ |
22 |
| - interaction::InteractionBuilder, |
23 |
| - p3_air::{AirBuilder, BaseAir}, |
24 |
| - p3_field::{Field, FieldAlgebra, PrimeField32}, |
25 |
| - p3_matrix::{dense::RowMajorMatrix, Matrix}, |
26 |
| - rap::BaseAirWithPublicValues, |
27 |
| -}; |
28 |
| -use serde::{Deserialize, Serialize}; |
29 |
| -use serde_with::{serde_as, DisplayFromStr}; |
30 |
| - |
31 |
| -// We do not use FieldExpressionCoreAir because EcDouble needs to do special constraints for |
32 |
| -// its setup instruction. |
33 |
| - |
34 |
| -#[derive(Clone)] |
35 |
| -pub struct EcDoubleCoreAir { |
36 |
| - pub expr: FieldExpr, |
37 |
| - pub offset: usize, |
38 |
| - pub a_biguint: BigUint, |
39 |
| -} |
40 |
| - |
41 |
| -impl EcDoubleCoreAir { |
42 |
| - pub fn new( |
43 |
| - config: ExprBuilderConfig, |
44 |
| - range_bus: VariableRangeCheckerBus, |
45 |
| - a_biguint: BigUint, |
46 |
| - offset: usize, |
47 |
| - ) -> Self { |
48 |
| - config.check_valid(); |
49 |
| - let builder = ExprBuilder::new(config, range_bus.range_max_bits); |
50 |
| - let builder = Rc::new(RefCell::new(builder)); |
51 |
| - |
52 |
| - let mut x1 = ExprBuilder::new_input(builder.clone()); |
53 |
| - let mut y1 = ExprBuilder::new_input(builder.clone()); |
54 |
| - let a = ExprBuilder::new_const(builder.clone(), a_biguint.clone()); |
55 |
| - let is_double_flag = builder.borrow_mut().new_flag(); |
56 |
| - // We need to prevent divide by zero when not double flag |
57 |
| - // (equivalently, when it is the setup opcode) |
58 |
| - let lambda_denom = FieldVariable::select( |
59 |
| - is_double_flag, |
60 |
| - &y1.int_mul(2), |
61 |
| - &ExprBuilder::new_const(builder.clone(), BigUint::one()), |
62 |
| - ); |
63 |
| - let mut lambda = (x1.square().int_mul(3) + a) / lambda_denom; |
64 |
| - let mut x3 = lambda.square() - x1.int_mul(2); |
65 |
| - x3.save_output(); |
66 |
| - let mut y3 = lambda * (x1 - x3.clone()) - y1; |
67 |
| - y3.save_output(); |
68 |
| - |
69 |
| - let builder = builder.borrow().clone(); |
70 |
| - let expr = FieldExpr::new(builder, range_bus, true); |
71 |
| - Self { |
72 |
| - expr, |
73 |
| - offset, |
74 |
| - a_biguint, |
75 |
| - } |
76 |
| - } |
77 |
| - |
78 |
| - pub fn output_indices(&self) -> &[usize] { |
79 |
| - &self.expr.output_indices |
80 |
| - } |
81 |
| -} |
82 |
| - |
83 |
| -impl<F: Field> BaseAir<F> for EcDoubleCoreAir { |
84 |
| - fn width(&self) -> usize { |
85 |
| - BaseAir::<F>::width(&self.expr) |
86 |
| - } |
87 |
| -} |
88 |
| - |
89 |
| -impl<F: Field> BaseAirWithPublicValues<F> for EcDoubleCoreAir {} |
90 |
| - |
91 |
| -impl<AB: InteractionBuilder, I> VmCoreAir<AB, I> for EcDoubleCoreAir |
92 |
| -where |
93 |
| - I: VmAdapterInterface<AB::Expr>, |
94 |
| - AdapterAirContext<AB::Expr, I>: |
95 |
| - From<AdapterAirContext<AB::Expr, DynAdapterInterface<AB::Expr>>>, |
96 |
| -{ |
97 |
| - fn eval( |
98 |
| - &self, |
99 |
| - builder: &mut AB, |
100 |
| - local: &[AB::Var], |
101 |
| - _from_pc: AB::Var, |
102 |
| - ) -> AdapterAirContext<AB::Expr, I> { |
103 |
| - assert_eq!(local.len(), BaseAir::<AB::F>::width(&self.expr)); |
104 |
| - self.expr.eval(builder, local); |
105 |
| - |
106 |
| - let FieldExprCols { |
107 |
| - is_valid, |
108 |
| - inputs, |
109 |
| - vars, |
110 |
| - flags, |
111 |
| - .. |
112 |
| - } = self.expr.load_vars(local); |
113 |
| - assert_eq!(inputs.len(), 2); |
114 |
| - assert_eq!(vars.len(), 3); // x1^2, x3, y3 |
115 |
| - assert_eq!(flags.len(), 1); // is_double_flag |
116 |
| - |
117 |
| - let reads: Vec<AB::Expr> = inputs.into_iter().flatten().map(Into::into).collect(); |
118 |
| - let writes: Vec<AB::Expr> = self |
119 |
| - .output_indices() |
120 |
| - .iter() |
121 |
| - .flat_map(|&i| vars[i].clone()) |
122 |
| - .map(Into::into) |
123 |
| - .collect(); |
124 |
| - |
125 |
| - let is_setup = is_valid - flags[0]; |
126 |
| - builder.assert_bool(is_setup.clone()); |
127 |
| - let local_opcode_idx = flags[0] |
128 |
| - * AB::Expr::from_canonical_usize(Rv32WeierstrassOpcode::EC_DOUBLE as usize) |
129 |
| - + is_setup.clone() |
130 |
| - * AB::Expr::from_canonical_usize(Rv32WeierstrassOpcode::SETUP_EC_DOUBLE as usize); |
131 |
| - // when is_setup, assert `reads` equals `(modulus, a)` |
132 |
| - for (lhs, &rhs) in zip_eq( |
133 |
| - &reads, |
134 |
| - iter::empty() |
135 |
| - .chain(&self.expr.builder.prime_limbs) |
136 |
| - .chain(&big_uint_to_num_limbs( |
137 |
| - &self.a_biguint, |
138 |
| - self.expr.builder.limb_bits, |
139 |
| - self.expr.builder.num_limbs, |
140 |
| - )), |
141 |
| - ) { |
142 |
| - builder |
143 |
| - .when(is_setup.clone()) |
144 |
| - .assert_eq(lhs.clone(), AB::F::from_canonical_usize(rhs)); |
145 |
| - } |
146 |
| - |
147 |
| - let instruction = MinimalInstruction { |
148 |
| - is_valid: is_valid.into(), |
149 |
| - opcode: local_opcode_idx + AB::Expr::from_canonical_usize(self.offset), |
150 |
| - }; |
151 |
| - |
152 |
| - let ctx: AdapterAirContext<_, DynAdapterInterface<_>> = AdapterAirContext { |
153 |
| - to_pc: None, |
154 |
| - reads: reads.into(), |
155 |
| - writes: writes.into(), |
156 |
| - instruction: instruction.into(), |
157 |
| - }; |
158 |
| - ctx.into() |
159 |
| - } |
160 |
| - |
161 |
| - fn start_offset(&self) -> usize { |
162 |
| - self.offset |
163 |
| - } |
164 |
| -} |
165 |
| - |
166 |
| -pub struct EcDoubleCoreChip { |
167 |
| - pub air: EcDoubleCoreAir, |
168 |
| - pub range_checker: SharedVariableRangeCheckerChip, |
169 |
| -} |
170 |
| - |
171 |
| -impl EcDoubleCoreChip { |
172 |
| - pub fn new( |
173 |
| - config: ExprBuilderConfig, |
174 |
| - range_checker: SharedVariableRangeCheckerChip, |
175 |
| - a_biguint: BigUint, |
176 |
| - offset: usize, |
177 |
| - ) -> Self { |
178 |
| - let air = EcDoubleCoreAir::new(config, range_checker.bus(), a_biguint, offset); |
179 |
| - Self { air, range_checker } |
180 |
| - } |
181 |
| -} |
182 |
| - |
183 |
| -#[serde_as] |
184 |
| -#[derive(Clone, Serialize, Deserialize)] |
185 |
| -pub struct EcDoubleCoreRecord { |
186 |
| - #[serde_as(as = "DisplayFromStr")] |
187 |
| - pub x: BigUint, |
188 |
| - #[serde_as(as = "DisplayFromStr")] |
189 |
| - pub y: BigUint, |
190 |
| - pub is_double_flag: bool, |
191 |
| -} |
192 |
| - |
193 |
| -impl<F: PrimeField32, I> VmCoreChip<F, I> for EcDoubleCoreChip |
194 |
| -where |
195 |
| - I: VmAdapterInterface<F>, |
196 |
| - I::Reads: Into<DynArray<F>>, |
197 |
| - AdapterRuntimeContext<F, I>: From<AdapterRuntimeContext<F, DynAdapterInterface<F>>>, |
198 |
| -{ |
199 |
| - type Record = EcDoubleCoreRecord; |
200 |
| - type Air = EcDoubleCoreAir; |
201 |
| - |
202 |
| - fn execute_instruction( |
203 |
| - &self, |
204 |
| - instruction: &Instruction<F>, |
205 |
| - _from_pc: u32, |
206 |
| - reads: I::Reads, |
207 |
| - ) -> Result<(AdapterRuntimeContext<F, I>, Self::Record)> { |
208 |
| - let num_limbs = self.air.expr.canonical_num_limbs(); |
209 |
| - let limb_bits = self.air.expr.canonical_limb_bits(); |
210 |
| - let Instruction { opcode, .. } = instruction.clone(); |
211 |
| - let local_opcode_idx = opcode.local_opcode_idx(self.air.offset); |
212 |
| - let data: DynArray<_> = reads.into(); |
213 |
| - let data = data.0; |
214 |
| - debug_assert_eq!(data.len(), 2 * num_limbs); |
215 |
| - |
216 |
| - let x = data[..num_limbs] |
217 |
| - .iter() |
218 |
| - .map(|x| x.as_canonical_u32()) |
219 |
| - .collect_vec(); |
220 |
| - let y = data[num_limbs..] |
221 |
| - .iter() |
222 |
| - .map(|x| x.as_canonical_u32()) |
223 |
| - .collect_vec(); |
224 |
| - |
225 |
| - let x_biguint = limbs_to_biguint(&x, limb_bits); |
226 |
| - let y_biguint = limbs_to_biguint(&y, limb_bits); |
227 |
| - |
228 |
| - let is_double_flag = local_opcode_idx == Rv32WeierstrassOpcode::EC_DOUBLE as usize; |
229 |
| - |
230 |
| - let vars = self.air.expr.execute( |
231 |
| - vec![x_biguint.clone(), y_biguint.clone()], |
232 |
| - vec![is_double_flag], |
233 |
| - ); |
234 |
| - assert_eq!(vars.len(), 3); // x1^2, x3, y3 |
235 |
| - |
236 |
| - let writes = self |
237 |
| - .air |
238 |
| - .output_indices() |
239 |
| - .iter() |
240 |
| - .flat_map(|&i| { |
241 |
| - let limbs = biguint_to_limbs_vec(vars[i].clone(), limb_bits, num_limbs); |
242 |
| - limbs.into_iter().map(F::from_canonical_u32) |
243 |
| - }) |
244 |
| - .collect_vec(); |
245 |
| - |
246 |
| - let ctx = AdapterRuntimeContext::<_, DynAdapterInterface<_>>::without_pc(writes); |
247 |
| - |
248 |
| - Ok(( |
249 |
| - ctx.into(), |
250 |
| - EcDoubleCoreRecord { |
251 |
| - x: x_biguint, |
252 |
| - y: y_biguint, |
253 |
| - is_double_flag, |
254 |
| - }, |
255 |
| - )) |
256 |
| - } |
257 |
| - |
258 |
| - fn get_opcode_name(&self, _opcode: usize) -> String { |
259 |
| - "EcDouble".to_string() |
260 |
| - } |
261 |
| - |
262 |
| - fn generate_trace_row(&self, row_slice: &mut [F], record: Self::Record) { |
263 |
| - self.air.expr.generate_subrow( |
264 |
| - ( |
265 |
| - self.range_checker.as_ref(), |
266 |
| - vec![record.x, record.y], |
267 |
| - vec![record.is_double_flag], |
268 |
| - ), |
269 |
| - row_slice, |
270 |
| - ); |
271 |
| - } |
272 |
| - |
273 |
| - fn air(&self) -> &Self::Air { |
274 |
| - &self.air |
275 |
| - } |
276 |
| - |
277 |
| - // We need finalize for double, as it might have a constant (a of y^2 = x^3 + ax + b) |
278 |
| - fn finalize(&self, trace: &mut RowMajorMatrix<F>, num_records: usize) { |
279 |
| - if num_records == 0 { |
280 |
| - return; |
281 |
| - } |
282 |
| - let core_width = <Self::Air as BaseAir<F>>::width(&self.air); |
283 |
| - let adapter_width = trace.width() - core_width; |
284 |
| - let dummy_row = self.generate_dummy_trace_row(adapter_width, core_width); |
285 |
| - for row in trace.rows_mut().skip(num_records) { |
286 |
| - row.copy_from_slice(&dummy_row); |
287 |
| - } |
288 |
| - } |
289 |
| -} |
290 |
| - |
291 |
| -impl EcDoubleCoreChip { |
292 |
| - // We will be setting is_valid = 0. That forces is_double to be 0 (otherwise setup will be -1). |
293 |
| - // We generate a dummy row with is_double = 0, then we set is_valid = 0. |
294 |
| - fn generate_dummy_trace_row<F: PrimeField32>( |
295 |
| - &self, |
296 |
| - adapter_width: usize, |
297 |
| - core_width: usize, |
298 |
| - ) -> Vec<F> { |
299 |
| - let record = EcDoubleCoreRecord { |
300 |
| - x: BigUint::zero(), |
301 |
| - y: BigUint::zero(), |
302 |
| - is_double_flag: false, |
303 |
| - }; |
304 |
| - let mut row = vec![F::ZERO; adapter_width + core_width]; |
305 |
| - let core_row = &mut row[adapter_width..]; |
306 |
| - // We **do not** want this trace row to update the range checker |
307 |
| - // so we must create a temporary range checker |
308 |
| - let tmp_range_checker = SharedVariableRangeCheckerChip::new(self.range_checker.bus()); |
309 |
| - self.air.expr.generate_subrow( |
310 |
| - ( |
311 |
| - tmp_range_checker.as_ref(), |
312 |
| - vec![record.x, record.y], |
313 |
| - vec![record.is_double_flag], |
314 |
| - ), |
315 |
| - core_row, |
316 |
| - ); |
317 |
| - core_row[0] = F::ZERO; // is_valid = 0 |
318 |
| - row |
319 |
| - } |
| 4 | +use num_traits::One; |
| 5 | +use openvm_circuit_primitives::var_range::VariableRangeCheckerBus; |
| 6 | +use openvm_mod_circuit_builder::{ExprBuilder, ExprBuilderConfig, FieldExpr, FieldVariable}; |
| 7 | + |
| 8 | +pub fn ec_double_ne_expr( |
| 9 | + config: ExprBuilderConfig, // The coordinate field. |
| 10 | + range_bus: VariableRangeCheckerBus, |
| 11 | + a_biguint: BigUint, |
| 12 | +) -> FieldExpr { |
| 13 | + config.check_valid(); |
| 14 | + let builder = ExprBuilder::new(config, range_bus.range_max_bits); |
| 15 | + let builder = Rc::new(RefCell::new(builder)); |
| 16 | + |
| 17 | + let mut x1 = ExprBuilder::new_input(builder.clone()); |
| 18 | + let mut y1 = ExprBuilder::new_input(builder.clone()); |
| 19 | + let a = ExprBuilder::new_const(builder.clone(), a_biguint.clone()); |
| 20 | + let is_double_flag = builder.borrow_mut().new_flag(); |
| 21 | + // We need to prevent divide by zero when not double flag |
| 22 | + // (equivalently, when it is the setup opcode) |
| 23 | + let lambda_denom = FieldVariable::select( |
| 24 | + is_double_flag, |
| 25 | + &y1.int_mul(2), |
| 26 | + &ExprBuilder::new_const(builder.clone(), BigUint::one()), |
| 27 | + ); |
| 28 | + let mut lambda = (x1.square().int_mul(3) + a) / lambda_denom; |
| 29 | + let mut x3 = lambda.square() - x1.int_mul(2); |
| 30 | + x3.save_output(); |
| 31 | + let mut y3 = lambda * (x1 - x3.clone()) - y1; |
| 32 | + y3.save_output(); |
| 33 | + |
| 34 | + let builder = builder.borrow().clone(); |
| 35 | + FieldExpr::new_with_setup_values(builder, range_bus, true, vec![a_biguint]) |
320 | 36 | }
|
0 commit comments