use serde::{Deserialize, Serialize}; use serde_json::to_string_pretty; use std::convert::TryFrom; use std::io::Cursor; use std::path::PathBuf; use typed_arena::Arena; use wasm_bindgen::prelude::*; use zokrates_abi::{parse_strict, Decode, Encode, Inputs}; use zokrates_ark::Ark; use zokrates_ast::ir; use zokrates_ast::ir::ProgEnum; use zokrates_ast::typed::abi::Abi; use zokrates_ast::typed::types::{ConcreteSignature, ConcreteType, GTupleType}; use zokrates_circom::{write_r1cs, write_witness}; use zokrates_common::helpers::{CurveParameter, SchemeParameter}; use zokrates_common::Resolver; use zokrates_core::compile::{ compile as core_compile, CompilationArtifacts, CompileConfig, CompileError, }; use zokrates_core::imports::Error; use zokrates_field::{Bls12_377Field, Bls12_381Field, Bn128Field, Bw6_761Field, Field}; use zokrates_proof_systems::groth16::G16; use zokrates_proof_systems::{ Backend, Marlin, NonUniversalBackend, NonUniversalScheme, Proof, Scheme, SolidityCompatibleField, SolidityCompatibleScheme, TaggedKeypair, TaggedProof, UniversalBackend, UniversalScheme, GM17, }; #[wasm_bindgen] pub struct CompilationResult { program: Vec, abi: Abi, snarkjs_program: Option>, } #[wasm_bindgen] impl CompilationResult { pub fn program(&self) -> js_sys::Uint8Array { let arr = js_sys::Uint8Array::new_with_length(self.program.len() as u32); arr.copy_from(&self.program); arr } pub fn abi(&self) -> JsValue { JsValue::from_serde(&self.abi).unwrap() } pub fn snarkjs_program(&self) -> Option { self.snarkjs_program.as_ref().map(|p| { let arr = js_sys::Uint8Array::new_with_length(p.len() as u32); arr.copy_from(p); arr }) } } #[derive(Serialize, Deserialize)] pub struct ResolverResult { source: String, location: String, } #[wasm_bindgen] pub struct ComputationResult { witness: String, output: String, snarkjs_witness: Option>, } #[wasm_bindgen] impl ComputationResult { pub fn witness(&self) -> JsValue { JsValue::from_str(&self.witness) } pub fn output(&self) -> JsValue { JsValue::from_str(&self.output) } pub fn snarkjs_witness(&self) -> Option { self.snarkjs_witness.as_ref().map(|w| { let arr = js_sys::Uint8Array::new_with_length(w.len() as u32); arr.copy_from(w); arr }) } } pub struct JsResolver<'a> { callback: &'a js_sys::Function, } impl<'a> JsResolver<'a> { pub fn new(callback: &'a js_sys::Function) -> Self { JsResolver { callback } } } impl<'a> Resolver for JsResolver<'a> { fn resolve( &self, current_location: PathBuf, import_location: PathBuf, ) -> Result<(String, PathBuf), Error> { let value = self .callback .call2( &JsValue::UNDEFINED, ¤t_location.to_str().unwrap().into(), &import_location.to_str().unwrap().into(), ) .map_err(|_| { Error::new(format!( "Could not resolve `{}`: error thrown in resolve callback", import_location.display() )) })?; if value.is_null() || value.is_undefined() { Err(Error::new(format!( "Could not resolve `{}`", import_location.display() ))) } else { let result: ResolverResult = value.into_serde().unwrap(); Ok((result.source, PathBuf::from(result.location))) } } } mod internal { use super::*; pub fn compile( source: JsValue, location: JsValue, resolve_callback: &js_sys::Function, config: JsValue, ) -> Result { let resolver = JsResolver::new(resolve_callback); let config: serde_json::Value = config.into_serde().unwrap(); let with_snarkjs_program = config .get("snarkjs") .map(|v| *v == serde_json::Value::Bool(true)) .unwrap_or(false); let config: CompileConfig = serde_json::from_value(config).unwrap_or_default(); let fmt_error = |e: &CompileError| format!("{}:{}", e.file().display(), e.value()); let arena = Arena::new(); let artifacts: CompilationArtifacts = core_compile( source.as_string().unwrap(), PathBuf::from(location.as_string().unwrap()), Some(&resolver), config, &arena, ) .map_err(|ce| { JsValue::from_str( &ce.0 .iter() .map(|e| fmt_error(e)) .collect::>() .join("\n"), ) })?; let abi = artifacts.abi().clone(); let program = artifacts.prog().collect(); let snarkjs_program = with_snarkjs_program.then(|| { let mut buffer = Cursor::new(vec![]); write_r1cs(&mut buffer, program.clone()).unwrap(); buffer.into_inner() }); let mut buffer = Cursor::new(vec![]); let _ = program.serialize(&mut buffer); Ok(CompilationResult { abi, program: buffer.into_inner(), snarkjs_program, }) } pub fn compute( program: ir::Prog, abi: JsValue, args: JsValue, config: JsValue, ) -> Result { let input = args.as_string().unwrap(); let config: serde_json::Value = config.into_serde().unwrap(); let with_snarkjs_witness = config .get("snarkjs") .map(|v| *v == serde_json::Value::Bool(true)) .unwrap_or(false); let (inputs, signature) = if abi.is_object() { let abi: Abi = abi.into_serde().map_err(|err| { JsValue::from_str(&format!("Could not deserialize `abi`: {}", err)) })?; let signature = abi.signature(); let inputs = parse_strict(&input, signature.inputs.clone()) .map(Inputs::Abi) .map_err(|err| JsValue::from_str(&err.to_string()))?; (inputs, signature) } else { let signature = ConcreteSignature::new() .inputs(vec![ConcreteType::FieldElement; program.arguments.len()]) .output(ConcreteType::Tuple(GTupleType::new( vec![ConcreteType::FieldElement; program.return_count], ))); let inputs = parse_strict(&input, signature.inputs.clone()) .map(Inputs::Abi) .map_err(|err| JsValue::from_str(&err.to_string()))?; (inputs, signature) }; let interpreter = zokrates_interpreter::Interpreter::default(); let public_inputs = program.public_inputs(); let witness = interpreter .execute(program, &inputs.encode()) .map_err(|err| JsValue::from_str(&format!("Execution failed: {}", err)))?; let return_values: serde_json::Value = zokrates_abi::Value::decode(witness.return_values(), *signature.output) .into_serde_json(); let snarkjs_witness = with_snarkjs_witness.then(|| { let mut buffer = Cursor::new(vec![]); write_witness(&mut buffer, witness.clone(), public_inputs).unwrap(); buffer.into_inner() }); Ok(ComputationResult { witness: format!("{}", witness), output: to_string_pretty(&return_values).unwrap(), snarkjs_witness, }) } pub fn setup_non_universal< T: Field, S: NonUniversalScheme + Serialize, B: NonUniversalBackend, >( program: ir::Prog, ) -> JsValue { let keypair = B::setup(program); let tagged_keypair = TaggedKeypair::::new(keypair); JsValue::from_serde(&tagged_keypair).unwrap() } pub fn setup_universal< T: Field, I: IntoIterator>, S: UniversalScheme + Serialize, B: UniversalBackend, >( srs: &[u8], program: ir::ProgIterator, ) -> Result { let keypair = B::setup(srs.to_vec(), program).map_err(|e| JsValue::from_str(&e))?; Ok(JsValue::from_serde(&TaggedKeypair::::new(keypair)).unwrap()) } pub fn universal_setup_of_size, B: UniversalBackend>( size: u32, ) -> Vec { B::universal_setup(size) } pub fn generate_proof, B: Backend>( prog: ir::Prog, witness: JsValue, pk: &[u8], ) -> Result { let str_witness = witness.as_string().unwrap(); let ir_witness: ir::Witness = ir::Witness::read(str_witness.as_bytes()) .map_err(|err| JsValue::from_str(&format!("Could not read witness: {}", err)))?; let proof = B::generate_proof(prog, ir_witness, pk.to_vec()); Ok(JsValue::from_serde(&TaggedProof::::new(proof.proof, proof.inputs)).unwrap()) } pub fn verify, B: Backend>( vk: serde_json::Value, proof: serde_json::Value, ) -> Result { let vk: S::VerificationKey = serde_json::from_value(vk).map_err(|e| JsValue::from_str(&e.to_string()))?; let proof: Proof = serde_json::from_value(proof).map_err(|e| JsValue::from_str(&e.to_string()))?; let result = B::verify(vk, proof); Ok(JsValue::from_serde(&result).unwrap()) } pub fn format_proof>( proof: serde_json::Value, ) -> Result { use serde_json::json; let proof: Proof = serde_json::from_value(proof).map_err(|err| JsValue::from_str(&format!("{}", err)))?; let res = S::Proof::from(proof.proof); let proof_object = serde_json::to_value(&res).unwrap(); let proof_vec = proof_object .as_object() .unwrap() .iter() .map(|(_, value)| value) .collect::>(); let result = if !proof.inputs.is_empty() { json!([proof_vec, proof.inputs]) } else { json!([proof_vec]) }; Ok(JsValue::from_serde(&result).unwrap()) } pub fn export_solidity_verifier>( vk: serde_json::Value, ) -> Result { let vk: S::VerificationKey = serde_json::from_value(vk).map_err(|err| JsValue::from_str(&format!("{}", err)))?; Ok(JsValue::from_str(&S::export_solidity_verifier(vk))) } } #[wasm_bindgen] pub fn compile( source: JsValue, location: JsValue, resolve_callback: &js_sys::Function, config: JsValue, curve: JsValue, ) -> Result { let curve = CurveParameter::try_from(curve.as_string().unwrap().as_str()) .map_err(|e| JsValue::from_str(&e))?; match curve { CurveParameter::Bn128 => { internal::compile::(source, location, resolve_callback, config) } CurveParameter::Bls12_381 => { internal::compile::(source, location, resolve_callback, config) } CurveParameter::Bls12_377 => { internal::compile::(source, location, resolve_callback, config) } CurveParameter::Bw6_761 => { internal::compile::(source, location, resolve_callback, config) } } } #[wasm_bindgen] pub fn compute_witness( program: &[u8], abi: JsValue, args: JsValue, config: JsValue, ) -> Result { let prog = ir::ProgEnum::deserialize(program) .map_err(|err| JsValue::from_str(&err))? .collect(); match prog { ProgEnum::Bn128Program(p) => internal::compute::<_>(p, abi, args, config), ProgEnum::Bls12_381Program(p) => internal::compute::<_>(p, abi, args, config), ProgEnum::Bls12_377Program(p) => internal::compute::<_>(p, abi, args, config), ProgEnum::Bw6_761Program(p) => internal::compute::<_>(p, abi, args, config), } } #[wasm_bindgen] pub fn export_solidity_verifier(vk: JsValue) -> Result { let vk: serde_json::Value = vk .into_serde() .map_err(|e| JsValue::from_str(&e.to_string()))?; let curve = CurveParameter::try_from( vk["curve"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid verification key: missing field `curve`"))?, ) .map_err(|e| JsValue::from_str(&e))?; let scheme = SchemeParameter::try_from(vk["scheme"].as_str().ok_or_else(|| { JsValue::from_str("Invalid verification key: missing field `scheme`") })?) .map_err(|e| JsValue::from_str(&e))?; match (curve, scheme) { (CurveParameter::Bn128, SchemeParameter::G16) => { internal::export_solidity_verifier::(vk) } (CurveParameter::Bn128, SchemeParameter::GM17) => { internal::export_solidity_verifier::(vk) } (CurveParameter::Bn128, SchemeParameter::MARLIN) => { internal::export_solidity_verifier::(vk) } _ => Err(JsValue::from_str("Not supported")), } } #[wasm_bindgen] pub fn setup(program: &[u8], options: JsValue) -> Result { let options: serde_json::Value = options.into_serde().unwrap(); let scheme = SchemeParameter::try_from( options["scheme"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid options: missing field `scheme`"))?, ) .map_err(|e| JsValue::from_str(&e))?; let prog = ir::ProgEnum::deserialize(program) .map_err(|err| JsValue::from_str(&err))? .collect(); match scheme { SchemeParameter::G16 => match prog { ProgEnum::Bn128Program(p) => Ok(internal::setup_non_universal::<_, G16, Ark>(p)), ProgEnum::Bls12_381Program(p) => Ok(internal::setup_non_universal::<_, G16, Ark>(p)), ProgEnum::Bls12_377Program(p) => Ok(internal::setup_non_universal::<_, G16, Ark>(p)), ProgEnum::Bw6_761Program(p) => Ok(internal::setup_non_universal::<_, G16, Ark>(p)), }, SchemeParameter::GM17 => match prog { ProgEnum::Bn128Program(p) => Ok(internal::setup_non_universal::<_, GM17, Ark>(p)), ProgEnum::Bls12_381Program(p) => Ok(internal::setup_non_universal::<_, GM17, Ark>(p)), ProgEnum::Bls12_377Program(p) => Ok(internal::setup_non_universal::<_, GM17, Ark>(p)), ProgEnum::Bw6_761Program(p) => Ok(internal::setup_non_universal::<_, GM17, Ark>(p)), }, _ => Err(JsValue::from_str("Unsupported scheme")), } } #[wasm_bindgen] pub fn setup_with_srs(srs: &[u8], program: &[u8], options: JsValue) -> Result { let options: serde_json::Value = options.into_serde().unwrap(); let scheme = SchemeParameter::try_from( options["scheme"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid options: missing field `scheme`"))?, ) .map_err(|e| JsValue::from_str(&e))?; let prog = ir::ProgEnum::deserialize(program) .map_err(|err| JsValue::from_str(&err))? .collect(); match scheme { SchemeParameter::MARLIN => match prog { ProgEnum::Bn128Program(p) => internal::setup_universal::<_, _, Marlin, Ark>(srs, p), ProgEnum::Bls12_381Program(p) => internal::setup_universal::<_, _, Marlin, Ark>(srs, p), ProgEnum::Bls12_377Program(p) => internal::setup_universal::<_, _, Marlin, Ark>(srs, p), ProgEnum::Bw6_761Program(p) => internal::setup_universal::<_, _, Marlin, Ark>(srs, p), }, _ => Err(JsValue::from_str("Given scheme is not universal")), } } #[wasm_bindgen] pub fn universal_setup(curve: JsValue, size: u32) -> Result, JsValue> { let curve = CurveParameter::try_from(curve.as_string().unwrap().as_str()) .map_err(|e| JsValue::from_str(&e))?; match curve { CurveParameter::Bn128 => { Ok(internal::universal_setup_of_size::(size)) } CurveParameter::Bls12_381 => Ok(internal::universal_setup_of_size::< Bls12_381Field, Marlin, Ark, >(size)), CurveParameter::Bls12_377 => Ok(internal::universal_setup_of_size::< Bls12_377Field, Marlin, Ark, >(size)), CurveParameter::Bw6_761 => { Ok(internal::universal_setup_of_size::(size)) } } } #[wasm_bindgen] pub fn generate_proof( program: &[u8], witness: JsValue, pk: &[u8], options: JsValue, ) -> Result { let options: serde_json::Value = options.into_serde().unwrap(); let scheme = SchemeParameter::try_from( options["scheme"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid options: missing field `scheme`"))?, ) .map_err(|e| JsValue::from_str(&e))?; let prog = ir::ProgEnum::deserialize(program) .map_err(|err| JsValue::from_str(&err))? .collect(); match scheme { SchemeParameter::G16 => match prog { ProgEnum::Bn128Program(p) => internal::generate_proof::<_, G16, Ark>(p, witness, pk), ProgEnum::Bls12_381Program(p) => { internal::generate_proof::<_, G16, Ark>(p, witness, pk) } ProgEnum::Bls12_377Program(p) => { internal::generate_proof::<_, G16, Ark>(p, witness, pk) } ProgEnum::Bw6_761Program(p) => internal::generate_proof::<_, G16, Ark>(p, witness, pk), }, SchemeParameter::GM17 => match prog { ProgEnum::Bn128Program(p) => internal::generate_proof::<_, GM17, Ark>(p, witness, pk), ProgEnum::Bls12_381Program(p) => { internal::generate_proof::<_, GM17, Ark>(p, witness, pk) } ProgEnum::Bls12_377Program(p) => { internal::generate_proof::<_, GM17, Ark>(p, witness, pk) } ProgEnum::Bw6_761Program(p) => internal::generate_proof::<_, GM17, Ark>(p, witness, pk), }, SchemeParameter::MARLIN => match prog { ProgEnum::Bn128Program(p) => internal::generate_proof::<_, Marlin, Ark>(p, witness, pk), ProgEnum::Bls12_381Program(p) => { internal::generate_proof::<_, Marlin, Ark>(p, witness, pk) } ProgEnum::Bls12_377Program(p) => { internal::generate_proof::<_, Marlin, Ark>(p, witness, pk) } ProgEnum::Bw6_761Program(p) => { internal::generate_proof::<_, Marlin, Ark>(p, witness, pk) } }, _ => Err(JsValue::from_str("Unsupported scheme")), } } #[wasm_bindgen] pub fn verify(vk: JsValue, proof: JsValue) -> Result { let vk: serde_json::Value = vk.into_serde().unwrap(); let proof: serde_json::Value = proof.into_serde().unwrap(); let vk_curve = CurveParameter::try_from( vk["curve"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid verification key: missing field `curve`"))?, ) .map_err(|e| JsValue::from_str(&e))?; let vk_scheme = SchemeParameter::try_from(vk["scheme"].as_str().ok_or_else(|| { JsValue::from_str("Invalid verification key: missing field `scheme`") })?) .map_err(|e| JsValue::from_str(&e))?; let proof_curve = CurveParameter::try_from( proof["curve"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid proof: missing field `curve`"))?, ) .map_err(|e| JsValue::from_str(&e))?; let proof_scheme = SchemeParameter::try_from( proof["scheme"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid proof: missing field `scheme`"))?, ) .map_err(|e| JsValue::from_str(&e))?; if proof_curve != vk_curve { return Err(JsValue::from_str( "Proof and verification key should have the same curve", )); } if proof_scheme != vk_scheme { return Err(JsValue::from_str( "Proof and verification key should have the same scheme", )); } let scheme = vk_scheme; let curve = vk_curve; match scheme { SchemeParameter::G16 => match curve { CurveParameter::Bn128 => internal::verify::(vk, proof), CurveParameter::Bls12_381 => internal::verify::(vk, proof), CurveParameter::Bls12_377 => internal::verify::(vk, proof), CurveParameter::Bw6_761 => internal::verify::(vk, proof), }, SchemeParameter::GM17 => match curve { CurveParameter::Bn128 => internal::verify::(vk, proof), CurveParameter::Bls12_381 => internal::verify::(vk, proof), CurveParameter::Bls12_377 => internal::verify::(vk, proof), CurveParameter::Bw6_761 => internal::verify::(vk, proof), }, SchemeParameter::MARLIN => match curve { CurveParameter::Bn128 => internal::verify::(vk, proof), CurveParameter::Bls12_381 => internal::verify::(vk, proof), CurveParameter::Bls12_377 => internal::verify::(vk, proof), CurveParameter::Bw6_761 => internal::verify::(vk, proof), }, _ => Err(JsValue::from_str("Unsupported scheme")), } } #[wasm_bindgen] pub fn format_proof(proof: JsValue) -> Result { let proof: serde_json::Value = proof.into_serde().unwrap(); let curve = CurveParameter::try_from( proof["curve"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid proof: missing field `curve`"))?, ) .map_err(|e| JsValue::from_str(&e))?; let scheme = SchemeParameter::try_from( proof["scheme"] .as_str() .ok_or_else(|| JsValue::from_str("Invalid proof: missing field `scheme`"))?, ) .map_err(|e| JsValue::from_str(&e))?; match (curve, scheme) { (CurveParameter::Bn128, SchemeParameter::G16) => { internal::format_proof::(proof) } (CurveParameter::Bn128, SchemeParameter::GM17) => { internal::format_proof::(proof) } (CurveParameter::Bn128, SchemeParameter::MARLIN) => { internal::format_proof::(proof) } _ => Err(JsValue::from_str("Unsupported options")), } } #[wasm_bindgen(start)] pub fn main_js() -> Result<(), JsValue> { std::panic::set_hook(Box::new(console_error_panic_hook::hook)); Ok(()) }