use bellman::{gadgets::Assignment, groth16, Circuit, ConstraintSystem, SynthesisError}; use bls12_381::Bls12; use bls12_381::Scalar; use ff::{Field, PrimeField}; use rand::rngs::OsRng; use std::ops::{AddAssign, MulAssign, SubAssign}; use std::time::Instant; use crate::error::Result; pub struct ZkVirtualMachine { pub constants: Vec, pub alloc: Vec<(AllocType, VariableIndex)>, pub ops: Vec, pub constraints: Vec, pub aux: Vec, pub params: Option>, pub verifying_key: Option>, } pub type VariableIndex = usize; pub enum VariableRef { Aux(VariableIndex), Local(VariableIndex), } pub enum CryptoOperation { Set(VariableRef, VariableRef), Mul(VariableRef, VariableRef), Add(VariableRef, VariableRef), Sub(VariableRef, VariableRef), Load(VariableRef, VariableIndex), Divide(VariableRef, VariableRef), Double(VariableRef), Square(VariableRef), Invert(VariableRef), UnpackBits(VariableRef, VariableRef, VariableRef), Local, Debug(String, VariableRef), DumpAlloc, DumpLocal, } #[derive(Clone)] pub enum AllocType { Private, Public, } #[derive(Debug, Clone)] pub enum ConstraintInstruction { Lc0Add(VariableIndex), Lc1Add(VariableIndex), Lc2Add(VariableIndex), Lc0Sub(VariableIndex), Lc1Sub(VariableIndex), Lc2Sub(VariableIndex), Lc0AddOne, Lc1AddOne, Lc2AddOne, Lc0SubOne, Lc1SubOne, Lc2SubOne, Lc0AddCoeff(VariableIndex, VariableIndex), Lc1AddCoeff(VariableIndex, VariableIndex), Lc2AddCoeff(VariableIndex, VariableIndex), Lc0AddConstant(VariableIndex), Lc1AddConstant(VariableIndex), Lc2AddConstant(VariableIndex), Enforce, LcCoeffReset, LcCoeffDouble, } #[derive(Debug)] pub enum ZkVmError { DivisionByZero, MalformedRange, } impl ZkVirtualMachine { pub fn initialize( &mut self, params: &Vec<(VariableIndex, Scalar)>, ) -> std::result::Result<(), ZkVmError> { // Resize array self.aux = vec![Scalar::zero(); self.alloc.len()]; // Copy over the parameters for (index, value) in params { //println!("Setting {} to {:?}", index, value); self.aux[*index] = *value; } let mut local_stack: Vec = Vec::new(); for op in &self.ops { match op { CryptoOperation::Set(self_, other) => { let other = match other { VariableRef::Aux(index) => self.aux[*index], VariableRef::Local(index) => local_stack[*index], }; let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; *self_ = other; } CryptoOperation::Mul(self_, other) => { let other = match other { VariableRef::Aux(index) => self.aux[*index], VariableRef::Local(index) => local_stack[*index], }; let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; self_.mul_assign(other); } CryptoOperation::Add(self_, other) => { let other = match other { VariableRef::Aux(index) => self.aux[*index], VariableRef::Local(index) => local_stack[*index], }; let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; self_.add_assign(other); } CryptoOperation::Sub(self_, other) => { let other = match other { VariableRef::Aux(index) => self.aux[*index], VariableRef::Local(index) => local_stack[*index], }; let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; self_.sub_assign(other); } CryptoOperation::Load(self_, const_index) => { let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; *self_ = self.constants[*const_index]; } CryptoOperation::Divide(self_, other) => { let other = match other { VariableRef::Aux(index) => self.aux[*index], VariableRef::Local(index) => local_stack[*index], }; let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; let ret = other.invert().map(|other| *self_ * other); if bool::from(ret.is_some()) { *self_ = ret.unwrap(); } else { return Err(ZkVmError::DivisionByZero); } } CryptoOperation::Double(self_) => { let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; *self_ = self_.double(); } CryptoOperation::Square(self_) => { let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; *self_ = self_.square(); } CryptoOperation::Invert(self_) => { let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; if self_.is_zero() { return Err(ZkVmError::DivisionByZero); } else { *self_ = self_.invert().unwrap(); } } CryptoOperation::UnpackBits(value, start, end) => { let value = match value { VariableRef::Aux(index) => self.aux[*index], VariableRef::Local(index) => local_stack[*index], }; let (self_, start_index, end_index) = match start { VariableRef::Aux(start_index) => match end { VariableRef::Aux(end_index) => (&mut self.aux, start_index, end_index), VariableRef::Local(_) => { return Err(ZkVmError::MalformedRange); } }, VariableRef::Local(start_index) => match end { VariableRef::Aux(_) => { return Err(ZkVmError::MalformedRange); } VariableRef::Local(end_index) => { (&mut local_stack, start_index, end_index) } }, }; if start_index > end_index { return Err(ZkVmError::MalformedRange); } if (end_index + 1) - start_index != 256 { return Err(ZkVmError::MalformedRange); } if *end_index >= self_.len() { return Err(ZkVmError::MalformedRange); } for (i, bit) in value.to_le_bits().into_iter().cloned().enumerate() { match bit { true => self_[start_index + i] = Scalar::one(), false => self_[start_index + i] = Scalar::zero(), } } } CryptoOperation::Local => { local_stack.push(Scalar::zero()); } CryptoOperation::Debug(debug_str, self_) => { let self_ = match self_ { VariableRef::Aux(index) => &mut self.aux[*index], VariableRef::Local(index) => &mut local_stack[*index], }; println!("{}", debug_str); println!("value = {:?}", self_); } CryptoOperation::DumpAlloc => { println!("-------------------"); println!("alloc"); println!("-------------------"); for (i, value) in self.aux.iter().enumerate() { println!("{}: {:?}", i, value); } println!("-------------------"); } CryptoOperation::DumpLocal => { println!("-------------------"); println!("local"); println!("-------------------"); for (i, value) in local_stack.iter().enumerate() { println!("{}: {:?}", i, value); } println!("-------------------"); } } } Ok(()) } pub fn public(&self) -> Vec<(VariableIndex, Scalar)> { let mut publics = Vec::new(); for (alloc_type, index) in &self.alloc { match alloc_type { AllocType::Private => {} AllocType::Public => { let scalar = self.aux[*index]; publics.push((*index, scalar)); } } } publics } pub fn setup(&mut self) -> Result<()> { let start = Instant::now(); // Create parameters for our circuit. In a production deployment these would // be generated securely using a multiparty computation. self.params = Some({ let circuit = ZkVmCircuit { aux: vec![None; self.aux.len()], alloc: self.alloc.clone(), constraints: self.constraints.clone(), constants: self.constants.clone(), }; groth16::generate_random_parameters::(circuit, &mut OsRng)? }); println!("Setup: [{:?}]", start.elapsed()); self.verifying_key = Some(groth16::prepare_verifying_key( &self.params.as_ref().unwrap().vk, )); Ok(()) } pub fn prove(&self) -> groth16::Proof { let aux = self.aux.iter().map(|scalar| Some(*scalar)).collect(); // Create an instance of our circuit (with the preimage as a witness). let circuit = ZkVmCircuit { aux, alloc: self.alloc.clone(), constraints: self.constraints.clone(), constants: self.constants.clone(), }; let start = Instant::now(); // Create a Groth16 proof with our parameters. let proof = groth16::create_random_proof(circuit, self.params.as_ref().unwrap(), &mut OsRng) .unwrap(); println!("Prove: [{:?}]", start.elapsed()); proof } pub fn verify(&self, proof: &groth16::Proof, public_values: &[Scalar]) -> bool { let start = Instant::now(); let is_passed = groth16::verify_proof(self.verifying_key.as_ref().unwrap(), proof, public_values) .is_ok(); println!("Verify: [{:?}]", start.elapsed()); is_passed } } pub struct ZkVmCircuit { aux: Vec>, alloc: Vec<(AllocType, VariableIndex)>, constraints: Vec, constants: Vec, } impl Circuit for ZkVmCircuit { fn synthesize>( self, cs: &mut CS, ) -> std::result::Result<(), SynthesisError> { let mut variables = Vec::new(); for (alloc_type, index) in &self.alloc { match alloc_type { AllocType::Private => { let var = cs.alloc(|| "private alloc", || Ok(*self.aux[*index].get()?))?; variables.push(var); } AllocType::Public => { let var = cs.alloc_input(|| "public alloc", || Ok(*self.aux[*index].get()?))?; variables.push(var); } } } let mut coeff = bls12_381::Scalar::one(); let mut lc0 = bellman::LinearCombination::::zero(); let mut lc1 = bellman::LinearCombination::::zero(); let mut lc2 = bellman::LinearCombination::::zero(); for constraint in self.constraints { match constraint { ConstraintInstruction::Lc0Add(index) => { lc0 = lc0 + (coeff, variables[index]); } ConstraintInstruction::Lc1Add(index) => { lc1 = lc1 + (coeff, variables[index]); } ConstraintInstruction::Lc2Add(index) => { lc2 = lc2 + (coeff, variables[index]); } ConstraintInstruction::Lc0Sub(index) => { lc0 = lc0 - (coeff, variables[index]); } ConstraintInstruction::Lc1Sub(index) => { lc1 = lc1 - (coeff, variables[index]); } ConstraintInstruction::Lc2Sub(index) => { lc2 = lc2 - (coeff, variables[index]); } ConstraintInstruction::Lc0AddOne => { lc0 = lc0 + CS::one(); } ConstraintInstruction::Lc1AddOne => { lc1 = lc1 + CS::one(); } ConstraintInstruction::Lc2AddOne => { lc2 = lc2 + CS::one(); } ConstraintInstruction::Lc0SubOne => { lc0 = lc0 - CS::one(); } ConstraintInstruction::Lc1SubOne => { lc1 = lc1 - CS::one(); } ConstraintInstruction::Lc2SubOne => { lc2 = lc2 - CS::one(); } ConstraintInstruction::Lc0AddCoeff(const_index, index) => { lc0 = lc0 + (self.constants[const_index], variables[index]); } ConstraintInstruction::Lc1AddCoeff(const_index, index) => { lc1 = lc1 + (self.constants[const_index], variables[index]); } ConstraintInstruction::Lc2AddCoeff(const_index, index) => { lc2 = lc2 + (self.constants[const_index], variables[index]); } ConstraintInstruction::Lc0AddConstant(const_index) => { lc0 = lc0 + (self.constants[const_index], CS::one()); } ConstraintInstruction::Lc1AddConstant(const_index) => { lc1 = lc1 + (self.constants[const_index], CS::one()); } ConstraintInstruction::Lc2AddConstant(const_index) => { lc2 = lc2 + (self.constants[const_index], CS::one()); } ConstraintInstruction::Enforce => { cs.enforce( || "constraint", |_| lc0.clone(), |_| lc1.clone(), |_| lc2.clone(), ); coeff = bls12_381::Scalar::one(); lc0 = bellman::LinearCombination::::zero(); lc1 = bellman::LinearCombination::::zero(); lc2 = bellman::LinearCombination::::zero(); } ConstraintInstruction::LcCoeffReset => { coeff = bls12_381::Scalar::one(); } ConstraintInstruction::LcCoeffDouble => { coeff = coeff.double(); } } } Ok(()) } }