use ark_circom::{ethereum, CircomBuilder, CircomConfig}; use ark_std::rand::thread_rng; use color_eyre::Result; use ark_bn254::Bn254; use ark_groth16::{create_random_proof as prove, generate_random_parameters}; use ethers::{ contract::{abigen, ContractError, ContractFactory}, providers::{Http, Middleware, Provider}, utils::{compile_and_launch_ganache, Ganache, Solc}, }; use std::{convert::TryFrom, sync::Arc}; #[tokio::test] async fn solidity_verifier() -> Result<()> { let cfg = CircomConfig::::new( "./test-vectors/mycircuit.wasm", "./test-vectors/mycircuit.r1cs", )?; let mut builder = CircomBuilder::new(cfg); builder.push_input("a", 3); builder.push_input("b", 11); // create an empty instance for setting it up let circom = builder.setup(); let mut rng = thread_rng(); let params = generate_random_parameters::(circom, &mut rng)?; let circom = builder.build()?; let inputs = circom.get_public_inputs().unwrap(); let proof = prove(circom, ¶ms, &mut rng)?; // launch the network & compile the verifier let (compiled, ganache) = compile_and_launch_ganache(Solc::new("./tests/verifier.sol"), Ganache::new()).await?; let acc = ganache.addresses()[0]; let provider = Provider::::try_from(ganache.endpoint())?; let provider = provider.with_sender(acc); let provider = Arc::new(provider); // deploy the verifier let contract = { let contract = compiled .get("TestVerifier") .expect("could not find contract"); let factory = ContractFactory::new( contract.abi.clone(), contract.bytecode.clone(), provider.clone(), ); let contract = factory.deploy(())?.send().await?; let addr = contract.address(); Groth16Verifier::new(addr, provider) }; // check the proof let verified = contract .check_proof(proof, params.vk, inputs.as_slice()) .await?; assert!(verified); Ok(()) } // We need to implement the conversion from the Ark-Circom's internal Ethereum types to // the ones expected by the abigen'd types. Could we maybe provide a convenience // macro for these, given that there's room for implementation error? abigen!(Groth16Verifier, "./tests/verifier_abi.json"); use groth16verifier_mod::{G1Point, G2Point, Proof, VerifyingKey}; impl From for G1Point { fn from(src: ethereum::G1) -> Self { Self { x: src.x, y: src.y } } } impl From for G2Point { fn from(src: ethereum::G2) -> Self { // We should use the `.as_tuple()` method which handles converting // the G2 elements to have the second limb first let src = src.as_tuple(); Self { x: src.0, y: src.1 } } } impl From for Proof { fn from(src: ethereum::Proof) -> Self { Self { a: src.a.into(), b: src.b.into(), c: src.c.into(), } } } impl From for VerifyingKey { fn from(src: ethereum::VerifyingKey) -> Self { Self { alfa_1: src.alpha1.into(), beta_2: src.beta2.into(), gamma_2: src.gamma2.into(), delta_2: src.delta2.into(), ic: src.ic.into_iter().map(|i| i.into()).collect(), } } } impl Groth16Verifier { async fn check_proof< I: Into, P: Into, VK: Into, >( &self, proof: P, vk: VK, inputs: I, ) -> Result> { // convert into the expected format by the contract let proof = proof.into().into(); let vk = vk.into().into(); let inputs = inputs.into().0; // query the contract let res = self.verify(inputs, proof, vk).call().await?; Ok(res) } }