Skip to content

Commit 08e274d

Browse files
updated weierstrass doubling chip to use (new) mod-builder
1 parent 79feb7f commit 08e274d

File tree

2 files changed

+47
-319
lines changed

2 files changed

+47
-319
lines changed
Lines changed: 33 additions & 317 deletions
Original file line numberDiff line numberDiff line change
@@ -1,320 +1,36 @@
1-
use std::{cell::RefCell, iter, rc::Rc};
1+
use std::{cell::RefCell, rc::Rc};
22

3-
use itertools::{zip_eq, Itertools};
43
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])
32036
}

extensions/ecc/circuit/src/weierstrass_chip/mod.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ pub struct EcDoubleChip<F: PrimeField32, const BLOCKS: usize, const BLOCK_SIZE:
6666
VmChipWrapper<
6767
F,
6868
Rv32VecHeapAdapterChip<F, 1, BLOCKS, BLOCKS, BLOCK_SIZE, BLOCK_SIZE>,
69-
EcDoubleCoreChip,
69+
FieldExpressionCoreChip,
7070
>,
7171
);
7272

@@ -81,7 +81,19 @@ impl<F: PrimeField32, const BLOCKS: usize, const BLOCK_SIZE: usize>
8181
a: BigUint,
8282
offline_memory: Arc<Mutex<OfflineMemory<F>>>,
8383
) -> Self {
84-
let core = EcDoubleCoreChip::new(config, range_checker.clone(), a, offset);
84+
let expr = ec_double_ne_expr(config, range_checker.bus(), a);
85+
let core = FieldExpressionCoreChip::new(
86+
expr,
87+
offset,
88+
vec![
89+
Rv32WeierstrassOpcode::EC_DOUBLE as usize,
90+
Rv32WeierstrassOpcode::SETUP_EC_DOUBLE as usize,
91+
],
92+
vec![],
93+
range_checker,
94+
"EcDouble",
95+
true,
96+
);
8597
Self(VmChipWrapper::new(adapter, core, offline_memory))
8698
}
8799
}

0 commit comments

Comments
 (0)