diff --git a/nova/src/circuit.rs b/nova/src/circuit.rs new file mode 100644 index 0000000000..226dd2ffdd --- /dev/null +++ b/nova/src/circuit.rs @@ -0,0 +1,397 @@ +use std::{ + collections::BTreeMap, + marker::PhantomData, + sync::{Arc, Mutex}, +}; + +use ast::{ + analyzed::{Analyzed, Expression, IdentityKind, PolynomialReference}, + parsed::{asm::Param, BinaryOperator}, +}; +use bellperson::{ + gadgets::{boolean::Boolean, num::AllocatedNum}, + ConstraintSystem, SynthesisError, +}; +use ff::PrimeField; +use itertools::Itertools; +use log::warn; +use nova_snark::traits::{circuit_supernova::StepCircuit, PrimeFieldExt}; +use number::FieldElement; + +use crate::{ + nonnative::{bignat::BigNat, util::Num}, + utils::{ + add_allocated_num, alloc_const, alloc_num_equals, alloc_one, conditionally_select, + evaluate_expr, find_pc_expression, get_num_at_index, WitnessGen, + }, + LIMB_WIDTH, +}; + +/// this NovaStepCircuit can compile single instruction in PIL into R1CS constraints +#[derive(Clone, Debug)] +pub struct NovaStepCircuit<'a, F: PrimeField, T: FieldElement> { + _p: PhantomData, + augmented_circuit_index: usize, + rom_len: usize, + identity_name: String, + io_params: &'a (Vec, Vec), // input,output index + analyzed: &'a Analyzed, + num_registers: usize, + witgen: Arc>>, +} + +impl<'a, F, T> NovaStepCircuit<'a, F, T> +where + F: PrimeField, + T: FieldElement, +{ + /// new + pub fn new( + rom_len: usize, + augmented_circuit_index: usize, + identity_name: String, + analyzed: &'a Analyzed, + io_params: &'a (Vec, Vec), + num_registers: usize, + witgen: Arc>>, + ) -> Self { + NovaStepCircuit { + rom_len, + augmented_circuit_index, + identity_name, + analyzed, + io_params, + num_registers, + witgen, + _p: PhantomData, + } + } +} + +impl<'a, F, T> StepCircuit for NovaStepCircuit<'a, F, T> +where + F: PrimeFieldExt, + T: FieldElement, +{ + fn arity(&self) -> usize { + self.num_registers + self.rom_len + } + + fn synthesize>( + &self, + cs: &mut CS, + _pc_counter: &AllocatedNum, + z: &[AllocatedNum], + ) -> Result<(AllocatedNum, Vec>), SynthesisError> { + // mapping to AllocatedNum + let mut poly_map = BTreeMap::new(); + + // process pc + poly_map.insert("pc".to_string(), _pc_counter.clone()); + + // process constants and build map for its reference + self.analyzed.constants.iter().try_for_each(|(k, v)| { + let mut v_le = v.to_bytes_le(); + v_le.resize(64, 0); + let v = alloc_const( + cs.namespace(|| format!("const {:?}", v)), + F::from_uniform(&v_le[..]), + 64, + )?; + poly_map.insert(k.clone(), v); + Ok::<(), SynthesisError>(()) + })?; + // add constant 1 + poly_map.insert("ONE".to_string(), alloc_one(cs.namespace(|| "constant 1"))?); + + // parse inst part to construct step circuit + // decompose ROM[pc] into linear combination lc(opcode_index, operand_index1, operand_index2, ... operand_output) + // Noted that here only support single output + // register value can be constrait via `multiple select + sum` with register index on nova `zi` state + // output register need to be constraints in the last of synthesize + + // NOTES: things that do not support + // 1. next query. only support pc as next, other value are all query on same rotation + // ... + + let rom_value = get_num_at_index( + cs.namespace(|| "rom value"), + _pc_counter, + z, + self.num_registers, + )?; + let (input_params, output_params) = self.io_params; + // ------------- + let rom_value_bignat = BigNat::from_num( + cs.namespace(|| "rom value bignat"), + &Num::from(rom_value), + LIMB_WIDTH as usize, + 1 + input_params.len() + output_params.len(), // 1 is opcode_index + )?; + let input_output_params_allocnum = rom_value_bignat + .as_limbs() + .iter() + .enumerate() + .map(|(limb_index, limb)| { + limb.as_allocated_num( + cs.namespace(|| format!("rom decompose index {}", limb_index)), + ) + }) + .collect::>, SynthesisError>>()?; + + let opcode_index_from_rom = &input_output_params_allocnum[0]; // opcode_index in 0th element + let mut offset = 1; + let input_params_allocnum = // fetch range belongs to input params + &input_output_params_allocnum[offset..offset + input_params.len()]; + offset += input_params.len(); + let output_params_allocnum = // fetch range belongs to output params + &input_output_params_allocnum[offset..offset + output_params.len()]; + + let expected_opcode_index = + AllocatedNum::alloc(cs.namespace(|| "expected_opcode_index"), || { + Ok(F::from(self.augmented_circuit_index as u64)) + })?; + cs.enforce( + || "opcode equality", + |lc| lc + opcode_index_from_rom.get_variable(), + |lc| lc + CS::one(), + |lc| lc + expected_opcode_index.get_variable(), + ); + + for id in &self.analyzed.identities { + match id.kind { + IdentityKind::Polynomial => { + // everthing should be in left.selector only + assert_eq!(id.right.expressions.len(), 0); + assert_eq!(id.right.selector, None); + assert_eq!(id.left.expressions.len(), 0); + + let exp = id.expression_for_poly_id(); + + match exp { + Expression::BinaryOperation( + box Expression::BinaryOperation( + box Expression::PolynomialReference( + PolynomialReference { name, .. }, + .., + ), + BinaryOperator::Mul, + box rhs, + ), + .., + ) => { + // only build constraints on matched identities + // TODO replace with better regular expression ? + if !(name.starts_with("main.instr") + && name.ends_with(&self.identity_name[..])) + { + continue; + } + + let contains_next_ref = exp.contains_next_ref(); + if contains_next_ref == true { + unimplemented!("not support column next in folding scheme") + } + let mut cs = cs.namespace(|| format!("rhs {}", rhs)); + let num_eval = evaluate_expr( + cs.namespace(|| format!("constraint {}", name)), + &mut poly_map, + rhs, + self.witgen.clone(), + )?; + cs.enforce( + || format!("constraint {} = 0", name), + |lc| lc + num_eval.get_variable(), + |lc| lc + CS::one(), + |lc| lc, + ); + } + _ => (), // println!("exp {:?} not support", exp), + } + } + _ => (), + } + } + + // constraint input name to index value + input_params_allocnum + .iter() + .zip_eq(input_params.iter()) + .try_for_each(|(index, params)| { + let (name, value) = match params { + // register + Param { name, ty: None } => ( + name.clone(), + get_num_at_index( + cs.namespace(|| format!("regname {}", name)), + index, + z, + 0, + )?, + ), + // constant + Param { name, ty: Some(ty) } if ty == "signed" || ty == "unsigned" => { + ( + format!("instr_{}_param_{}", self.identity_name, name), + index.clone(), + ) + }, + s => { + unimplemented!("not support {}", s) + }, + }; + if let Some(reg) = poly_map.get(&name) { + cs.enforce( + || format!("{} - reg[{}_index] = 0", params, params), + |lc| lc + value.get_variable(), + |lc| lc + CS::one(), + |lc| lc + reg.get_variable(), + ); + Ok::<(), SynthesisError>(()) + } else { + warn!( + "missing input reg name {} in polymap with key {:?}, Probably instr {} defined but never used", + params, + poly_map.keys(), + self.identity_name, + ); + Ok::<(), SynthesisError>(()) + } + })?; + + // constraint zi_next[index] = (index == output_index) ? reg_assigned[output_reg_name]: zi[index] + let zi_next = output_params_allocnum.iter().zip_eq(output_params.iter()).try_fold( + z.to_vec(), + |acc, (output_index, param)| { + assert!(param.ty.is_none()); // output only accept register + (0..z.len()) + .map(|i| { + let i_alloc = AllocatedNum::alloc( + cs.namespace(|| format!("output reg i{} allocated", i)), + || Ok(F::from(i as u64)), + )?; + let equal_bit = Boolean::from(alloc_num_equals( + cs.namespace(|| format!("check reg {} equal bit", i)), + &i_alloc, + &output_index, + )?); + if let Some(output) = poly_map.get(¶m.name) { + conditionally_select( + cs.namespace(|| { + format!( + "zi_next constraint with register index {} on reg name {}", + i, param + ) + }), + output, + &acc[i], + &equal_bit, + ) + } else { + warn!( + "missing output reg name {} in polymap with key {:?}, Probably instr {} defined but never used", + param, + poly_map.keys(), + self.identity_name, + ); + Ok(acc[i].clone()) + } + }) + .collect::>, SynthesisError>>() + }, + )?; + + // process pc_next + // TODO: very inefficient to go through all identities for each folding, need optimisation + for id in &self.analyzed.identities { + match id.kind { + IdentityKind::Polynomial => { + // everthing should be in left.selector only + assert_eq!(id.right.expressions.len(), 0); + assert_eq!(id.right.selector, None); + assert_eq!(id.left.expressions.len(), 0); + + let exp = id.expression_for_poly_id(); + + if let Expression::BinaryOperation( + box Expression::PolynomialReference( + PolynomialReference { name, next, .. }, + .., + ), + BinaryOperator::Sub, + box rhs, + .., + ) = exp + { + // lhs is `pc'` + if name == "main.pc" && *next == true { + let identity_name = format!("main.instr_{}", self.identity_name); + let exp = find_pc_expression::(rhs, &identity_name); + let pc_next = exp + .and_then(|expr| { + evaluate_expr( + // evaluate rhs pc bumping logic + cs.namespace(|| format!("pc eval on {}", expr)), + &mut poly_map, + &expr, + self.witgen.clone(), + ) + .ok() + }) + .unwrap_or_else(|| { + // by default pc + 1 + add_allocated_num( + cs.namespace(|| format!("instr {} pc + 1", identity_name)), + &poly_map["pc"], + &poly_map["ONE"], + ) + .unwrap() + }); + poly_map.insert("pc_next".to_string(), pc_next); + } + } + } + _ => (), + } + } + Ok((poly_map["pc_next"].clone(), zi_next)) + } +} + +/// A trivial step circuit that simply returns the input +#[derive(Clone, Debug, Default)] +pub struct SecondaryStepCircuit { + _p: PhantomData, + arity_len: usize, +} + +impl SecondaryStepCircuit +where + F: PrimeField, +{ + /// new TrivialTestCircuit + pub fn new(arity_len: usize) -> Self { + SecondaryStepCircuit { + arity_len, + _p: PhantomData, + } + } +} + +impl StepCircuit for SecondaryStepCircuit +where + F: PrimeField, +{ + fn arity(&self) -> usize { + self.arity_len + } + + fn synthesize>( + &self, + _cs: &mut CS, + _pc_counter: &AllocatedNum, + z: &[AllocatedNum], + ) -> Result<(AllocatedNum, Vec>), SynthesisError> { + Ok((_pc_counter.clone(), z.to_vec())) + } +} diff --git a/nova/src/circuit_builder.rs b/nova/src/circuit_builder.rs index 5ec4c223f2..4b9880ae3b 100644 --- a/nova/src/circuit_builder.rs +++ b/nova/src/circuit_builder.rs @@ -2,93 +2,35 @@ use core::panic; use std::{ collections::BTreeMap, iter, - marker::PhantomData, sync::{Arc, Mutex}, }; use ast::{ - analyzed::{Analyzed, Expression, IdentityKind, PolynomialReference}, + analyzed::Analyzed, asm_analysis::{AssignmentStatement, FunctionStatement, InstructionStatement, Machine}, parsed::{ asm::{FunctionCall, Param}, - BinaryOperator, Expression::Number, UnaryOperator, }, }; -use bellperson::{ - gadgets::{boolean::Boolean, num::AllocatedNum}, - ConstraintSystem, SynthesisError, -}; use ff::{Field, PrimeField}; -use itertools::Itertools; -use log::warn; +use nova_snark::provider::bn256_grumpkin; use nova_snark::{ compute_digest, supernova::{gen_commitmentkey_by_r1cs, PublicParams, RecursiveSNARK, RunningClaim}, - traits::{circuit_supernova::StepCircuit, Group}, + traits::Group, }; -use nova_snark::{provider::bn256_grumpkin, traits::PrimeFieldExt}; use num_bigint::BigUint; use number::{BigInt, FieldElement}; use crate::{ - nonnative::{ - bignat::{limbs_to_nat, BigNat}, - util::Num, - }, - utils::{ - add_allocated_num, alloc_const, alloc_num_equals, alloc_one, alloc_zero, - conditionally_select, mul_allocated_num, WitnessGen, - }, + circuit::{NovaStepCircuit, SecondaryStepCircuit}, + nonnative::bignat::limbs_to_nat, + utils::WitnessGen, + LIMB_WIDTH, }; -/// LIMB WITHD as base, represent 2^16 -const LIMB_WIDTH: usize = 16; - -fn get_num_at_index>( - mut cs: CS, - index_before_offset: &AllocatedNum, - z: &[AllocatedNum], - offset: usize, -) -> Result, SynthesisError> { - let index = AllocatedNum::alloc(cs.namespace(|| "index"), || { - Ok(index_before_offset.get_value().unwrap() + F::from(offset as u64)) - })?; - - // select target when index match or empty - let zero = alloc_zero(cs.namespace(|| "zero"))?; - let _selected_num = z - .iter() - .enumerate() - .map(|(i, z)| { - let i_alloc = AllocatedNum::alloc( - cs.namespace(|| format!("_selected_circuit_index i{} allocated", i)), - || Ok(F::from(i as u64)), - )?; - let equal_bit = Boolean::from(alloc_num_equals( - cs.namespace(|| format!("check selected_circuit_index {} equal bit", i)), - &i_alloc, - &index, - )?); - conditionally_select( - cs.namespace(|| format!("select on index namespace {}", i)), - z, - &zero, - &equal_bit, - ) - }) - .collect::>, SynthesisError>>()?; - - let selected_num = _selected_num - .iter() - .enumerate() - .try_fold(zero, |agg, (i, _num)| { - add_allocated_num(cs.namespace(|| format!("selected_num {}", i)), _num, &agg) - })?; - Ok(selected_num) -} - // TODO support other cycling curve type G1 = bn256_grumpkin::bn256::Point; type G2 = bn256_grumpkin::grumpkin::Point; @@ -397,480 +339,3 @@ pub(crate) fn nova_prove( // println!("zi_secondary: {:?}", zi_secondary); // println!("final program_counter: {:?}", program_counter); } - -// TODO optmize constraints to leverage R1CS cost-free additive -// TODO combine FieldElement & PrimeField -fn evaluate_expr<'a, T: FieldElement, F: PrimeFieldExt, CS: ConstraintSystem>( - mut cs: CS, - poly_map: &mut BTreeMap>, - expr: &Expression, - witgen: Arc>>, -) -> Result, SynthesisError> { - match expr { - Expression::Number(n) => { - AllocatedNum::alloc(cs.namespace(|| format!("{:x?}", n.to_string())), || { - let mut n_le = n.to_bytes_le(); - n_le.resize(64, 0); - Ok(F::from_uniform(&n_le[..])) - }) - } - // this is refer to another polynomial, in other word, witness - Expression::PolynomialReference(PolynomialReference { - index, name, next, .. - }) => { - let name = name.strip_prefix("main.").unwrap(); // TODO FIXME: trim namespace should be happened in unified place - assert_eq!(*index, None); - assert_eq!(*next, false); - - Ok(poly_map - .entry(name.to_string()) - .or_insert_with(|| { - AllocatedNum::alloc(cs.namespace(|| format!("{}.{}", expr, name)), || { - let wit_value = witgen.lock().unwrap().get_wit_by_key(name).cloned(); - let mut n_le = wit_value.unwrap().to_bytes_le(); - n_le.resize(64, 0); - let f = F::from_uniform(&n_le[..]); - Ok(f) - }) - .unwrap() - }) - .clone()) - } - Expression::BinaryOperation(lhe, op, rhe) => { - let lhe = evaluate_expr(cs.namespace(|| "lhe"), poly_map, lhe, witgen.clone())?; - let rhe = evaluate_expr(cs.namespace(|| "rhe"), poly_map, rhe, witgen)?; - match op { - BinaryOperator::Add => add_allocated_num(cs, &lhe, &rhe), - BinaryOperator::Sub => { - let rhe_neg: AllocatedNum = - AllocatedNum::alloc(cs.namespace(|| "inv"), || { - rhe.get_value() - .map(|v| v.neg()) - .ok_or_else(|| SynthesisError::AssignmentMissing) - })?; - - // (a + a_neg) * 1 = 0 - cs.enforce( - || "(a + a_neg) * 1 = 0", - |lc| lc + rhe.get_variable() + rhe_neg.get_variable(), - |lc| lc + CS::one(), - |lc| lc, - ); - - // let neg = &rhe.mul(cs.namespace(|| "neg"), &poly_map.get("-1").unwrap())?; - add_allocated_num(cs, &lhe, &rhe_neg) - } - BinaryOperator::Mul => mul_allocated_num(cs, &lhe, &rhe), - _ => unimplemented!("{}", expr), - } - } - Expression::Constant(constant_name) => { - poly_map - .get(constant_name) - .cloned() - .ok_or_else(|| SynthesisError::AssignmentMissing) // constant must exist - } - _ => unimplemented!("{}", expr), - } -} - -// find rhs expression where the left hand side is the instr_name, e.g. instr_name * () -// this is a workaround and inefficient way, since we are working on PIL file. Need to optimise it. -fn find_pc_expression>( - expr: &Expression, - instr_name: &String, -) -> Option>> { - match expr { - Expression::Number(_) => None, - // this is refer to another polynomial, in other word, witness - Expression::PolynomialReference(PolynomialReference { .. }) => None, - Expression::BinaryOperation(lhe, operator, rhe) => { - let find_match_expr = match lhe { - // early pattern match on lhs to retrive the instr * () - box Expression::PolynomialReference(PolynomialReference { name, .. }) => { - if name == instr_name && *operator == BinaryOperator::Mul { - Some(rhe) - } else { - None - } - } - _ => None, - }; - find_match_expr - .cloned() - .or_else(|| find_pc_expression::(rhe, instr_name)) - .or_else(|| find_pc_expression::(lhe, instr_name)) - } - Expression::Constant(_) => None, - _ => unimplemented!("{}", expr), - } -} - -/// this NovaStepCircuit can compile single instruction in PIL into R1CS constraints -#[derive(Clone, Debug)] -pub struct NovaStepCircuit<'a, F: PrimeField, T: FieldElement> { - _p: PhantomData, - augmented_circuit_index: usize, - rom_len: usize, - identity_name: String, - io_params: &'a (Vec, Vec), // input,output index - analyzed: &'a Analyzed, - num_registers: usize, - witgen: Arc>>, -} - -impl<'a, F, T> NovaStepCircuit<'a, F, T> -where - F: PrimeField, - T: FieldElement, -{ - /// new - fn new( - rom_len: usize, - augmented_circuit_index: usize, - identity_name: String, - analyzed: &'a Analyzed, - io_params: &'a (Vec, Vec), - num_registers: usize, - witgen: Arc>>, - ) -> Self { - NovaStepCircuit { - rom_len, - augmented_circuit_index, - identity_name, - analyzed, - io_params, - num_registers, - witgen, - _p: PhantomData, - } - } -} - -impl<'a, F, T> StepCircuit for NovaStepCircuit<'a, F, T> -where - F: PrimeFieldExt, - T: FieldElement, -{ - fn arity(&self) -> usize { - self.num_registers + self.rom_len - } - - fn synthesize>( - &self, - cs: &mut CS, - _pc_counter: &AllocatedNum, - z: &[AllocatedNum], - ) -> Result<(AllocatedNum, Vec>), SynthesisError> { - // mapping to AllocatedNum - let mut poly_map = BTreeMap::new(); - - // process pc - poly_map.insert("pc".to_string(), _pc_counter.clone()); - - // process constants and build map for its reference - self.analyzed.constants.iter().try_for_each(|(k, v)| { - let mut v_le = v.to_bytes_le(); - v_le.resize(64, 0); - let v = alloc_const( - cs.namespace(|| format!("const {:?}", v)), - F::from_uniform(&v_le[..]), - 64, - )?; - poly_map.insert(k.clone(), v); - Ok::<(), SynthesisError>(()) - })?; - // add constant 1 - poly_map.insert("ONE".to_string(), alloc_one(cs.namespace(|| "constant 1"))?); - - // parse inst part to construct step circuit - // decompose ROM[pc] into linear combination lc(opcode_index, operand_index1, operand_index2, ... operand_output) - // Noted that here only support single output - // register value can be constrait via `multiple select + sum` with register index on nova `zi` state - // output register need to be constraints in the last of synthesize - - // NOTES: things that do not support - // 1. next query. only support pc as next, other value are all query on same rotation - // ... - - let rom_value = get_num_at_index( - cs.namespace(|| "rom value"), - _pc_counter, - z, - self.num_registers, - )?; - let (input_params, output_params) = self.io_params; - // ------------- - let rom_value_bignat = BigNat::from_num( - cs.namespace(|| "rom value bignat"), - &Num::from(rom_value), - LIMB_WIDTH as usize, - 1 + input_params.len() + output_params.len(), // 1 is opcode_index - )?; - let input_output_params_allocnum = rom_value_bignat - .as_limbs() - .iter() - .enumerate() - .map(|(limb_index, limb)| { - limb.as_allocated_num( - cs.namespace(|| format!("rom decompose index {}", limb_index)), - ) - }) - .collect::>, SynthesisError>>()?; - - let opcode_index_from_rom = &input_output_params_allocnum[0]; // opcode_index in 0th element - let mut offset = 1; - let input_params_allocnum = // fetch range belongs to input params - &input_output_params_allocnum[offset..offset + input_params.len()]; - offset += input_params.len(); - let output_params_allocnum = // fetch range belongs to output params - &input_output_params_allocnum[offset..offset + output_params.len()]; - - let expected_opcode_index = - AllocatedNum::alloc(cs.namespace(|| "expected_opcode_index"), || { - Ok(F::from(self.augmented_circuit_index as u64)) - })?; - cs.enforce( - || "opcode equality", - |lc| lc + opcode_index_from_rom.get_variable(), - |lc| lc + CS::one(), - |lc| lc + expected_opcode_index.get_variable(), - ); - - for id in &self.analyzed.identities { - match id.kind { - IdentityKind::Polynomial => { - // everthing should be in left.selector only - assert_eq!(id.right.expressions.len(), 0); - assert_eq!(id.right.selector, None); - assert_eq!(id.left.expressions.len(), 0); - - let exp = id.expression_for_poly_id(); - - match exp { - Expression::BinaryOperation( - box Expression::BinaryOperation( - box Expression::PolynomialReference( - PolynomialReference { name, .. }, - .., - ), - BinaryOperator::Mul, - box rhs, - ), - .., - ) => { - // only build constraints on matched identities - // TODO replace with better regular expression ? - if !(name.starts_with("main.instr") - && name.ends_with(&self.identity_name[..])) - { - continue; - } - - let contains_next_ref = exp.contains_next_ref(); - if contains_next_ref == true { - unimplemented!("not support column next in folding scheme") - } - let mut cs = cs.namespace(|| format!("rhs {}", rhs)); - let num_eval = evaluate_expr( - cs.namespace(|| format!("constraint {}", name)), - &mut poly_map, - rhs, - self.witgen.clone(), - )?; - cs.enforce( - || format!("constraint {} = 0", name), - |lc| lc + num_eval.get_variable(), - |lc| lc + CS::one(), - |lc| lc, - ); - } - _ => (), // println!("exp {:?} not support", exp), - } - } - _ => (), - } - } - - // constraint input name to index value - input_params_allocnum - .iter() - .zip_eq(input_params.iter()) - .try_for_each(|(index, params)| { - let (name, value) = match params { - // register - Param { name, ty: None } => ( - name.clone(), - get_num_at_index( - cs.namespace(|| format!("regname {}", name)), - index, - z, - 0, - )?, - ), - // constant - Param { name, ty: Some(ty) } if ty == "signed" || ty == "unsigned" => { - ( - format!("instr_{}_param_{}", self.identity_name, name), - index.clone(), - ) - }, - s => { - unimplemented!("not support {}", s) - }, - }; - if let Some(reg) = poly_map.get(&name) { - cs.enforce( - || format!("{} - reg[{}_index] = 0", params, params), - |lc| lc + value.get_variable(), - |lc| lc + CS::one(), - |lc| lc + reg.get_variable(), - ); - Ok::<(), SynthesisError>(()) - } else { - warn!( - "missing input reg name {} in polymap with key {:?}, Probably instr {} defined but never used", - params, - poly_map.keys(), - self.identity_name, - ); - Ok::<(), SynthesisError>(()) - } - })?; - - // constraint zi_next[index] = (index == output_index) ? reg_assigned[output_reg_name]: zi[index] - let zi_next = output_params_allocnum.iter().zip_eq(output_params.iter()).try_fold( - z.to_vec(), - |acc, (output_index, param)| { - assert!(param.ty.is_none()); // output only accept register - (0..z.len()) - .map(|i| { - let i_alloc = AllocatedNum::alloc( - cs.namespace(|| format!("output reg i{} allocated", i)), - || Ok(F::from(i as u64)), - )?; - let equal_bit = Boolean::from(alloc_num_equals( - cs.namespace(|| format!("check reg {} equal bit", i)), - &i_alloc, - &output_index, - )?); - if let Some(output) = poly_map.get(¶m.name) { - conditionally_select( - cs.namespace(|| { - format!( - "zi_next constraint with register index {} on reg name {}", - i, param - ) - }), - output, - &acc[i], - &equal_bit, - ) - } else { - warn!( - "missing output reg name {} in polymap with key {:?}, Probably instr {} defined but never used", - param, - poly_map.keys(), - self.identity_name, - ); - Ok(acc[i].clone()) - } - }) - .collect::>, SynthesisError>>() - }, - )?; - - // process pc_next - // TODO: very inefficient to go through all identities for each folding, need optimisation - for id in &self.analyzed.identities { - match id.kind { - IdentityKind::Polynomial => { - // everthing should be in left.selector only - assert_eq!(id.right.expressions.len(), 0); - assert_eq!(id.right.selector, None); - assert_eq!(id.left.expressions.len(), 0); - - let exp = id.expression_for_poly_id(); - - if let Expression::BinaryOperation( - box Expression::PolynomialReference( - PolynomialReference { name, next, .. }, - .., - ), - BinaryOperator::Sub, - box rhs, - .., - ) = exp - { - // lhs is `pc'` - if name == "main.pc" && *next == true { - let identity_name = format!("main.instr_{}", self.identity_name); - let exp = find_pc_expression::(rhs, &identity_name); - let pc_next = exp - .and_then(|expr| { - evaluate_expr( - // evaluate rhs pc bumping logic - cs.namespace(|| format!("pc eval on {}", expr)), - &mut poly_map, - &expr, - self.witgen.clone(), - ) - .ok() - }) - .unwrap_or_else(|| { - // by default pc + 1 - add_allocated_num( - cs.namespace(|| format!("instr {} pc + 1", identity_name)), - &poly_map["pc"], - &poly_map["ONE"], - ) - .unwrap() - }); - poly_map.insert("pc_next".to_string(), pc_next); - } - } - } - _ => (), - } - } - Ok((poly_map["pc_next"].clone(), zi_next)) - } -} - -/// A trivial step circuit that simply returns the input -#[derive(Clone, Debug, Default)] -pub struct SecondaryStepCircuit { - _p: PhantomData, - arity_len: usize, -} - -impl SecondaryStepCircuit -where - F: PrimeField, -{ - /// new TrivialTestCircuit - pub fn new(arity_len: usize) -> Self { - SecondaryStepCircuit { - arity_len, - _p: PhantomData, - } - } -} - -impl StepCircuit for SecondaryStepCircuit -where - F: PrimeField, -{ - fn arity(&self) -> usize { - self.arity_len - } - - fn synthesize>( - &self, - _cs: &mut CS, - _pc_counter: &AllocatedNum, - z: &[AllocatedNum], - ) -> Result<(AllocatedNum, Vec>), SynthesisError> { - Ok((_pc_counter.clone(), z.to_vec())) - } -} diff --git a/nova/src/lib.rs b/nova/src/lib.rs index 257132ffa3..07cd801e23 100644 --- a/nova/src/lib.rs +++ b/nova/src/lib.rs @@ -1,11 +1,11 @@ #![feature(box_patterns)] -// pub(crate) mod aggregation; +pub(crate) mod circuit; pub(crate) mod circuit_builder; pub(crate) mod nonnative; -pub(crate) mod utils; -// pub(crate) mod circuit_data; -// pub(crate) mod mock_prover; pub(crate) mod prover; +pub(crate) mod utils; -// pub use mock_prover::mock_prove; pub use prover::*; + +/// LIMB WITHD as base, represent 2^16 +pub(crate) const LIMB_WIDTH: usize = 16; diff --git a/nova/src/utils.rs b/nova/src/utils.rs index 4fc3beb1c1..345628fcf2 100644 --- a/nova/src/utils.rs +++ b/nova/src/utils.rs @@ -1,11 +1,16 @@ //! This module implements various util function, which is copy from Nova non-public mod //! https://github.com/microsoft/Nova/blob/main/src/gadgets/mod.rs#L5 -use std::collections::BTreeMap; - -use crate::nonnative::util::Bit; +use std::{ + collections::BTreeMap, + sync::{Arc, Mutex}, +}; #[allow(dead_code)] use super::nonnative::bignat::{nat_to_limbs, BigNat}; +use ast::{ + analyzed::{Expression, PolynomialReference}, + parsed::BinaryOperator, +}; use bellperson::{ gadgets::{ boolean::{AllocatedBit, Boolean}, @@ -18,7 +23,7 @@ use ff::{ derive::bitvec::{prelude::Lsb0, view::AsBits}, Field, PrimeField, PrimeFieldBits, }; -use nova_snark::traits::Group; +use nova_snark::traits::{Group, PrimeFieldExt}; use num_bigint::BigInt; use number::FieldElement; @@ -591,3 +596,153 @@ impl<'a, T: FieldElement> WitnessGen<'a, T> { .unwrap_or_default() } } + +pub fn get_num_at_index>( + mut cs: CS, + index_before_offset: &AllocatedNum, + z: &[AllocatedNum], + offset: usize, +) -> Result, SynthesisError> { + let index = AllocatedNum::alloc(cs.namespace(|| "index"), || { + Ok(index_before_offset.get_value().unwrap() + F::from(offset as u64)) + })?; + + // select target when index match or empty + let zero = alloc_zero(cs.namespace(|| "zero"))?; + let _selected_num = z + .iter() + .enumerate() + .map(|(i, z)| { + let i_alloc = AllocatedNum::alloc( + cs.namespace(|| format!("_selected_circuit_index i{} allocated", i)), + || Ok(F::from(i as u64)), + )?; + let equal_bit = Boolean::from(alloc_num_equals( + cs.namespace(|| format!("check selected_circuit_index {} equal bit", i)), + &i_alloc, + &index, + )?); + conditionally_select( + cs.namespace(|| format!("select on index namespace {}", i)), + z, + &zero, + &equal_bit, + ) + }) + .collect::>, SynthesisError>>()?; + + let selected_num = _selected_num + .iter() + .enumerate() + .try_fold(zero, |agg, (i, _num)| { + add_allocated_num(cs.namespace(|| format!("selected_num {}", i)), _num, &agg) + })?; + Ok(selected_num) +} + +// TODO optmize constraints to leverage R1CS cost-free additive +// TODO combine FieldElement & PrimeField +pub fn evaluate_expr<'a, T: FieldElement, F: PrimeFieldExt, CS: ConstraintSystem>( + mut cs: CS, + poly_map: &mut BTreeMap>, + expr: &Expression, + witgen: Arc>>, +) -> Result, SynthesisError> { + match expr { + Expression::Number(n) => { + AllocatedNum::alloc(cs.namespace(|| format!("{:x?}", n.to_string())), || { + let mut n_le = n.to_bytes_le(); + n_le.resize(64, 0); + Ok(F::from_uniform(&n_le[..])) + }) + } + // this is refer to another polynomial, in other word, witness + Expression::PolynomialReference(PolynomialReference { + index, name, next, .. + }) => { + let name = name.strip_prefix("main.").unwrap(); // TODO FIXME: trim namespace should be happened in unified place + assert_eq!(*index, None); + assert_eq!(*next, false); + + Ok(poly_map + .entry(name.to_string()) + .or_insert_with(|| { + AllocatedNum::alloc(cs.namespace(|| format!("{}.{}", expr, name)), || { + let wit_value = witgen.lock().unwrap().get_wit_by_key(name).cloned(); + let mut n_le = wit_value.unwrap().to_bytes_le(); + n_le.resize(64, 0); + let f = F::from_uniform(&n_le[..]); + Ok(f) + }) + .unwrap() + }) + .clone()) + } + Expression::BinaryOperation(lhe, op, rhe) => { + let lhe = evaluate_expr(cs.namespace(|| "lhe"), poly_map, lhe, witgen.clone())?; + let rhe = evaluate_expr(cs.namespace(|| "rhe"), poly_map, rhe, witgen)?; + match op { + BinaryOperator::Add => add_allocated_num(cs, &lhe, &rhe), + BinaryOperator::Sub => { + let rhe_neg: AllocatedNum = + AllocatedNum::alloc(cs.namespace(|| "inv"), || { + rhe.get_value() + .map(|v| v.neg()) + .ok_or_else(|| SynthesisError::AssignmentMissing) + })?; + + // (a + a_neg) * 1 = 0 + cs.enforce( + || "(a + a_neg) * 1 = 0", + |lc| lc + rhe.get_variable() + rhe_neg.get_variable(), + |lc| lc + CS::one(), + |lc| lc, + ); + + add_allocated_num(cs, &lhe, &rhe_neg) + } + BinaryOperator::Mul => mul_allocated_num(cs, &lhe, &rhe), + _ => unimplemented!("{}", expr), + } + } + Expression::Constant(constant_name) => { + poly_map + .get(constant_name) + .cloned() + .ok_or_else(|| SynthesisError::AssignmentMissing) // constant must exist + } + _ => unimplemented!("{}", expr), + } +} + +// find rhs expression where the left hand side is the instr_name, e.g. instr_name * () +// this is a workaround and inefficient way, since we are working on PIL file. Need to optimise it. +pub fn find_pc_expression>( + expr: &Expression, + instr_name: &String, +) -> Option>> { + match expr { + Expression::Number(_) => None, + // this is refer to another polynomial, in other word, witness + Expression::PolynomialReference(PolynomialReference { .. }) => None, + Expression::BinaryOperation(lhe, operator, rhe) => { + let find_match_expr = match lhe { + // early pattern match on lhs to retrive the instr * () + box Expression::PolynomialReference(PolynomialReference { name, .. }) => { + if name == instr_name && *operator == BinaryOperator::Mul { + Some(rhe) + } else { + None + } + } + _ => None, + }; + find_match_expr + .cloned() + .or_else(|| find_pc_expression::(rhe, instr_name)) + .or_else(|| find_pc_expression::(lhe, instr_name)) + } + Expression::Constant(_) => None, + _ => unimplemented!("{}", expr), + } +}