Skip to content

Commit 34ef7d9

Browse files
committed
nova refactor to separate codebase into different mod
1 parent a3078ae commit 34ef7d9

4 files changed

Lines changed: 568 additions & 551 deletions

File tree

nova/src/circuit.rs

Lines changed: 397 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,397 @@
1+
use std::{
2+
collections::BTreeMap,
3+
marker::PhantomData,
4+
sync::{Arc, Mutex},
5+
};
6+
7+
use ast::{
8+
analyzed::{Analyzed, Expression, IdentityKind, PolynomialReference},
9+
parsed::{asm::Param, BinaryOperator},
10+
};
11+
use bellperson::{
12+
gadgets::{boolean::Boolean, num::AllocatedNum},
13+
ConstraintSystem, SynthesisError,
14+
};
15+
use ff::PrimeField;
16+
use itertools::Itertools;
17+
use log::warn;
18+
use nova_snark::traits::{circuit_supernova::StepCircuit, PrimeFieldExt};
19+
use number::FieldElement;
20+
21+
use crate::{
22+
nonnative::{bignat::BigNat, util::Num},
23+
utils::{
24+
add_allocated_num, alloc_const, alloc_num_equals, alloc_one, conditionally_select,
25+
evaluate_expr, find_pc_expression, get_num_at_index, WitnessGen,
26+
},
27+
LIMB_WIDTH,
28+
};
29+
30+
/// this NovaStepCircuit can compile single instruction in PIL into R1CS constraints
31+
#[derive(Clone, Debug)]
32+
pub struct NovaStepCircuit<'a, F: PrimeField, T: FieldElement> {
33+
_p: PhantomData<F>,
34+
augmented_circuit_index: usize,
35+
rom_len: usize,
36+
identity_name: String,
37+
io_params: &'a (Vec<Param>, Vec<Param>), // input,output index
38+
analyzed: &'a Analyzed<T>,
39+
num_registers: usize,
40+
witgen: Arc<Mutex<WitnessGen<'a, T>>>,
41+
}
42+
43+
impl<'a, F, T> NovaStepCircuit<'a, F, T>
44+
where
45+
F: PrimeField,
46+
T: FieldElement,
47+
{
48+
/// new
49+
pub fn new(
50+
rom_len: usize,
51+
augmented_circuit_index: usize,
52+
identity_name: String,
53+
analyzed: &'a Analyzed<T>,
54+
io_params: &'a (Vec<Param>, Vec<Param>),
55+
num_registers: usize,
56+
witgen: Arc<Mutex<WitnessGen<'a, T>>>,
57+
) -> Self {
58+
NovaStepCircuit {
59+
rom_len,
60+
augmented_circuit_index,
61+
identity_name,
62+
analyzed,
63+
io_params,
64+
num_registers,
65+
witgen,
66+
_p: PhantomData,
67+
}
68+
}
69+
}
70+
71+
impl<'a, F, T> StepCircuit<F> for NovaStepCircuit<'a, F, T>
72+
where
73+
F: PrimeFieldExt,
74+
T: FieldElement,
75+
{
76+
fn arity(&self) -> usize {
77+
self.num_registers + self.rom_len
78+
}
79+
80+
fn synthesize<CS: ConstraintSystem<F>>(
81+
&self,
82+
cs: &mut CS,
83+
_pc_counter: &AllocatedNum<F>,
84+
z: &[AllocatedNum<F>],
85+
) -> Result<(AllocatedNum<F>, Vec<AllocatedNum<F>>), SynthesisError> {
86+
// mapping <name| can be register, constant, ...> to AllocatedNum<F>
87+
let mut poly_map = BTreeMap::new();
88+
89+
// process pc
90+
poly_map.insert("pc".to_string(), _pc_counter.clone());
91+
92+
// process constants and build map for its reference
93+
self.analyzed.constants.iter().try_for_each(|(k, v)| {
94+
let mut v_le = v.to_bytes_le();
95+
v_le.resize(64, 0);
96+
let v = alloc_const(
97+
cs.namespace(|| format!("const {:?}", v)),
98+
F::from_uniform(&v_le[..]),
99+
64,
100+
)?;
101+
poly_map.insert(k.clone(), v);
102+
Ok::<(), SynthesisError>(())
103+
})?;
104+
// add constant 1
105+
poly_map.insert("ONE".to_string(), alloc_one(cs.namespace(|| "constant 1"))?);
106+
107+
// parse inst part to construct step circuit
108+
// decompose ROM[pc] into linear combination lc(opcode_index, operand_index1, operand_index2, ... operand_output)
109+
// Noted that here only support single output
110+
// register value can be constrait via `multiple select + sum` with register index on nova `zi` state
111+
// output register need to be constraints in the last of synthesize
112+
113+
// NOTES: things that do not support
114+
// 1. next query. only support pc as next, other value are all query on same rotation
115+
// ...
116+
117+
let rom_value = get_num_at_index(
118+
cs.namespace(|| "rom value"),
119+
_pc_counter,
120+
z,
121+
self.num_registers,
122+
)?;
123+
let (input_params, output_params) = self.io_params;
124+
// -------------
125+
let rom_value_bignat = BigNat::from_num(
126+
cs.namespace(|| "rom value bignat"),
127+
&Num::from(rom_value),
128+
LIMB_WIDTH as usize,
129+
1 + input_params.len() + output_params.len(), // 1 is opcode_index
130+
)?;
131+
let input_output_params_allocnum = rom_value_bignat
132+
.as_limbs()
133+
.iter()
134+
.enumerate()
135+
.map(|(limb_index, limb)| {
136+
limb.as_allocated_num(
137+
cs.namespace(|| format!("rom decompose index {}", limb_index)),
138+
)
139+
})
140+
.collect::<Result<Vec<AllocatedNum<F>>, SynthesisError>>()?;
141+
142+
let opcode_index_from_rom = &input_output_params_allocnum[0]; // opcode_index in 0th element
143+
let mut offset = 1;
144+
let input_params_allocnum = // fetch range belongs to input params
145+
&input_output_params_allocnum[offset..offset + input_params.len()];
146+
offset += input_params.len();
147+
let output_params_allocnum = // fetch range belongs to output params
148+
&input_output_params_allocnum[offset..offset + output_params.len()];
149+
150+
let expected_opcode_index =
151+
AllocatedNum::alloc(cs.namespace(|| "expected_opcode_index"), || {
152+
Ok(F::from(self.augmented_circuit_index as u64))
153+
})?;
154+
cs.enforce(
155+
|| "opcode equality",
156+
|lc| lc + opcode_index_from_rom.get_variable(),
157+
|lc| lc + CS::one(),
158+
|lc| lc + expected_opcode_index.get_variable(),
159+
);
160+
161+
for id in &self.analyzed.identities {
162+
match id.kind {
163+
IdentityKind::Polynomial => {
164+
// everthing should be in left.selector only
165+
assert_eq!(id.right.expressions.len(), 0);
166+
assert_eq!(id.right.selector, None);
167+
assert_eq!(id.left.expressions.len(), 0);
168+
169+
let exp = id.expression_for_poly_id();
170+
171+
match exp {
172+
Expression::BinaryOperation(
173+
box Expression::BinaryOperation(
174+
box Expression::PolynomialReference(
175+
PolynomialReference { name, .. },
176+
..,
177+
),
178+
BinaryOperator::Mul,
179+
box rhs,
180+
),
181+
..,
182+
) => {
183+
// only build constraints on matched identities
184+
// TODO replace with better regular expression ?
185+
if !(name.starts_with("main.instr")
186+
&& name.ends_with(&self.identity_name[..]))
187+
{
188+
continue;
189+
}
190+
191+
let contains_next_ref = exp.contains_next_ref();
192+
if contains_next_ref == true {
193+
unimplemented!("not support column next in folding scheme")
194+
}
195+
let mut cs = cs.namespace(|| format!("rhs {}", rhs));
196+
let num_eval = evaluate_expr(
197+
cs.namespace(|| format!("constraint {}", name)),
198+
&mut poly_map,
199+
rhs,
200+
self.witgen.clone(),
201+
)?;
202+
cs.enforce(
203+
|| format!("constraint {} = 0", name),
204+
|lc| lc + num_eval.get_variable(),
205+
|lc| lc + CS::one(),
206+
|lc| lc,
207+
);
208+
}
209+
_ => (), // println!("exp {:?} not support", exp),
210+
}
211+
}
212+
_ => (),
213+
}
214+
}
215+
216+
// constraint input name to index value
217+
input_params_allocnum
218+
.iter()
219+
.zip_eq(input_params.iter())
220+
.try_for_each(|(index, params)| {
221+
let (name, value) = match params {
222+
// register
223+
Param { name, ty: None } => (
224+
name.clone(),
225+
get_num_at_index(
226+
cs.namespace(|| format!("regname {}", name)),
227+
index,
228+
z,
229+
0,
230+
)?,
231+
),
232+
// constant
233+
Param { name, ty: Some(ty) } if ty == "signed" || ty == "unsigned" => {
234+
(
235+
format!("instr_{}_param_{}", self.identity_name, name),
236+
index.clone(),
237+
)
238+
},
239+
s => {
240+
unimplemented!("not support {}", s)
241+
},
242+
};
243+
if let Some(reg) = poly_map.get(&name) {
244+
cs.enforce(
245+
|| format!("{} - reg[{}_index] = 0", params, params),
246+
|lc| lc + value.get_variable(),
247+
|lc| lc + CS::one(),
248+
|lc| lc + reg.get_variable(),
249+
);
250+
Ok::<(), SynthesisError>(())
251+
} else {
252+
warn!(
253+
"missing input reg name {} in polymap with key {:?}, Probably instr {} defined but never used",
254+
params,
255+
poly_map.keys(),
256+
self.identity_name,
257+
);
258+
Ok::<(), SynthesisError>(())
259+
}
260+
})?;
261+
262+
// constraint zi_next[index] = (index == output_index) ? reg_assigned[output_reg_name]: zi[index]
263+
let zi_next = output_params_allocnum.iter().zip_eq(output_params.iter()).try_fold(
264+
z.to_vec(),
265+
|acc, (output_index, param)| {
266+
assert!(param.ty.is_none()); // output only accept register
267+
(0..z.len())
268+
.map(|i| {
269+
let i_alloc = AllocatedNum::alloc(
270+
cs.namespace(|| format!("output reg i{} allocated", i)),
271+
|| Ok(F::from(i as u64)),
272+
)?;
273+
let equal_bit = Boolean::from(alloc_num_equals(
274+
cs.namespace(|| format!("check reg {} equal bit", i)),
275+
&i_alloc,
276+
&output_index,
277+
)?);
278+
if let Some(output) = poly_map.get(&param.name) {
279+
conditionally_select(
280+
cs.namespace(|| {
281+
format!(
282+
"zi_next constraint with register index {} on reg name {}",
283+
i, param
284+
)
285+
}),
286+
output,
287+
&acc[i],
288+
&equal_bit,
289+
)
290+
} else {
291+
warn!(
292+
"missing output reg name {} in polymap with key {:?}, Probably instr {} defined but never used",
293+
param,
294+
poly_map.keys(),
295+
self.identity_name,
296+
);
297+
Ok(acc[i].clone())
298+
}
299+
})
300+
.collect::<Result<Vec<AllocatedNum<F>>, SynthesisError>>()
301+
},
302+
)?;
303+
304+
// process pc_next
305+
// TODO: very inefficient to go through all identities for each folding, need optimisation
306+
for id in &self.analyzed.identities {
307+
match id.kind {
308+
IdentityKind::Polynomial => {
309+
// everthing should be in left.selector only
310+
assert_eq!(id.right.expressions.len(), 0);
311+
assert_eq!(id.right.selector, None);
312+
assert_eq!(id.left.expressions.len(), 0);
313+
314+
let exp = id.expression_for_poly_id();
315+
316+
if let Expression::BinaryOperation(
317+
box Expression::PolynomialReference(
318+
PolynomialReference { name, next, .. },
319+
..,
320+
),
321+
BinaryOperator::Sub,
322+
box rhs,
323+
..,
324+
) = exp
325+
{
326+
// lhs is `pc'`
327+
if name == "main.pc" && *next == true {
328+
let identity_name = format!("main.instr_{}", self.identity_name);
329+
let exp = find_pc_expression::<T, F, CS>(rhs, &identity_name);
330+
let pc_next = exp
331+
.and_then(|expr| {
332+
evaluate_expr(
333+
// evaluate rhs pc bumping logic
334+
cs.namespace(|| format!("pc eval on {}", expr)),
335+
&mut poly_map,
336+
&expr,
337+
self.witgen.clone(),
338+
)
339+
.ok()
340+
})
341+
.unwrap_or_else(|| {
342+
// by default pc + 1
343+
add_allocated_num(
344+
cs.namespace(|| format!("instr {} pc + 1", identity_name)),
345+
&poly_map["pc"],
346+
&poly_map["ONE"],
347+
)
348+
.unwrap()
349+
});
350+
poly_map.insert("pc_next".to_string(), pc_next);
351+
}
352+
}
353+
}
354+
_ => (),
355+
}
356+
}
357+
Ok((poly_map["pc_next"].clone(), zi_next))
358+
}
359+
}
360+
361+
/// A trivial step circuit that simply returns the input
362+
#[derive(Clone, Debug, Default)]
363+
pub struct SecondaryStepCircuit<F: PrimeField> {
364+
_p: PhantomData<F>,
365+
arity_len: usize,
366+
}
367+
368+
impl<F> SecondaryStepCircuit<F>
369+
where
370+
F: PrimeField,
371+
{
372+
/// new TrivialTestCircuit
373+
pub fn new(arity_len: usize) -> Self {
374+
SecondaryStepCircuit {
375+
arity_len,
376+
_p: PhantomData,
377+
}
378+
}
379+
}
380+
381+
impl<F> StepCircuit<F> for SecondaryStepCircuit<F>
382+
where
383+
F: PrimeField,
384+
{
385+
fn arity(&self) -> usize {
386+
self.arity_len
387+
}
388+
389+
fn synthesize<CS: ConstraintSystem<F>>(
390+
&self,
391+
_cs: &mut CS,
392+
_pc_counter: &AllocatedNum<F>,
393+
z: &[AllocatedNum<F>],
394+
) -> Result<(AllocatedNum<F>, Vec<AllocatedNum<F>>), SynthesisError> {
395+
Ok((_pc_counter.clone(), z.to_vec()))
396+
}
397+
}

0 commit comments

Comments
 (0)