Skip to content

Commit

Permalink
Verifiers as libraries with embedded keys (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
JanKuczma authored Dec 6, 2024
1 parent 04dceb7 commit 244c5f4
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 168 deletions.
56 changes: 6 additions & 50 deletions contracts/Shielder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

pragma solidity 0.8.26;

import { Halo2Verifier as NewAccountVerifier } from "./NewAccountVerifier.sol";
import { Halo2Verifier as DepositVerifier } from "./DepositVerifier.sol";
import { Halo2Verifier as WithdrawVerifier } from "./WithdrawVerifier.sol";
import { IArbSys } from "./IArbSys.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { MerkleTree } from "./MerkleTree.sol";
Expand All @@ -10,14 +13,6 @@ import { PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/P
import { UIntSet } from "./UIntSet.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

interface IVerifier {
function verifyProof(
address vk,
bytes calldata proof,
uint256[] calldata instances
) external returns (bool);
}

/// @title Shielder
/// @author CardinalCryptography
contract Shielder is
Expand Down Expand Up @@ -59,16 +54,6 @@ contract Shielder is
0x0000000000000000000000000000000000000064;
IArbSys private arbSysPrecompile;

// verifier contracts
address public newAccountVerifier;
address public depositVerifier;
address public withdrawVerifier;

// verification key contracts
address public newAccountVerifyingKey;
address public depositVerifyingKey;
address public withdrawVerifyingKey;

MerkleTree.MerkleTreeData public merkleTree;

UIntSet.Set private merkleRoots;
Expand Down Expand Up @@ -139,12 +124,6 @@ contract Shielder is

function initialize(
address initialOwner,
address _newAccountVerifier,
address _depositVerifier,
address _withdrawVerifier,
address _newAccountVerifyingKey,
address _depositVerifyingKey,
address _withdrawVerifyingKey,
uint256 _depositLimit
) public initializer {
__Ownable_init(initialOwner);
Expand All @@ -155,14 +134,6 @@ contract Shielder is

arbSysPrecompile = IArbSys(ARB_SYS_ADDRESS);

newAccountVerifier = _newAccountVerifier;
depositVerifier = _depositVerifier;
withdrawVerifier = _withdrawVerifier;

newAccountVerifyingKey = _newAccountVerifyingKey;
depositVerifyingKey = _depositVerifyingKey;
withdrawVerifyingKey = _withdrawVerifyingKey;

depositLimit = _depositLimit;
}

Expand Down Expand Up @@ -206,12 +177,7 @@ contract Shielder is
publicInputs[1] = idHash;
publicInputs[2] = amount;

IVerifier _verifier = IVerifier(newAccountVerifier);
bool success = _verifier.verifyProof(
newAccountVerifyingKey,
proof,
publicInputs
);
bool success = NewAccountVerifier.verifyProof(proof, publicInputs);

if (!success) revert NewAccountVerificationFailed();

Expand Down Expand Up @@ -255,12 +221,7 @@ contract Shielder is
publicInputs[3] = newNote;
publicInputs[4] = amount;

IVerifier _verifier = IVerifier(depositVerifier);
bool success = _verifier.verifyProof(
depositVerifyingKey,
proof,
publicInputs
);
bool success = DepositVerifier.verifyProof(proof, publicInputs);

if (!success) revert DepositVerificationFailed();

Expand Down Expand Up @@ -316,12 +277,7 @@ contract Shielder is
// @dev shifting right by 4 bits so the commitment is smaller from r
publicInputs[5] = uint256(keccak256(commitment)) >> 4;

IVerifier _verifier = IVerifier(withdrawVerifier);
bool success = _verifier.verifyProof(
withdrawVerifyingKey,
proof,
publicInputs
);
bool success = WithdrawVerifier.verifyProof(proof, publicInputs);

if (!success) revert WithdrawVerificationFailed();

Expand Down
19 changes: 8 additions & 11 deletions crates/halo2-verifier/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,22 +35,21 @@ pub fn main() {
);
}

/// Generate verification key and proving key contracts for the given circuit type.
/// Generate verifier contract for the given circuit type.
fn handle_relation<PK: ProverKnowledge<Fr>>(full_params: Params, relation: &str) {
println!("Generating {relation} relation contracts...");
let (verifier_solidity, vk_solidity) = generate_solidity_verification_bundle::<PK>(full_params);
let verifier_solidity = generate_solidity_verification_bundle::<PK>(full_params);
save_contract_source(&format!("{relation}Verifier.sol"), &verifier_solidity);
save_contract_source(&format!("{relation}VerifyingKey.sol"), &vk_solidity);
}

/// Given trusted setup, generate Solidity code for the verification key and the verifier.
/// Given trusted setup, generate Solidity code for the verifier with embedded verification key.
fn generate_solidity_verification_bundle<PK: ProverKnowledge<Fr>>(
full_parameters: ParamsKZG<Bn256>,
) -> (String, String) {
) -> String {
let (parameters, _, _, vk) =
generate_keys_with_min_k::<PK::Circuit>(full_parameters).expect("Failed to generate keys");
SolidityGenerator::new(&parameters, &vk, Bdfg21, PK::PublicInput::COUNT)
.render_separately()
.render()
.expect("Failed to generate separate contracts")
}

Expand Down Expand Up @@ -99,18 +98,16 @@ mod test {
/// Return an error if verifier fails on-chain.
fn verify_with_contract(
verifier_solidity: &str,
vk_solidity: &str,
proof: &[u8],
public_input: &[Fr],
) -> Result<u64, EvmRunnerError> {
let mut evm = EvmRunner::aleph_evm();

// Deploy verifier and vk contracts
let verifier_address = deploy_source_code(verifier_solidity, "Halo2Verifier", &mut evm);
let vk_address = deploy_source_code(vk_solidity, "Halo2VerifyingKey", &mut evm);

// Call verifier contract
let calldata = verifier_contract::encode_calldata(vk_address, proof, public_input);
let calldata = verifier_contract::encode_calldata(proof, public_input);
match evm.call(verifier_address, calldata, None, None) {
Ok(SuccessResult {
gas_used, output, ..
Expand All @@ -130,15 +127,15 @@ mod test {
let prover_knowledge = PK::random_correct_example(&mut rng);
let public_input = prover_knowledge.serialize_public_input();

let (verifier_solidity, vk_solidity) =
let verifier_solidity =
generate_solidity_verification_bundle::<PK>(full_parameters.clone());

let (parameters, _, pk, _) =
generate_keys_with_min_k::<PK::Circuit>(full_parameters).unwrap();
let circuit = prover_knowledge.create_circuit();
let proof = generate_proof(&parameters, &pk, circuit, &public_input, &mut rng);

let result = verify_with_contract(&verifier_solidity, &vk_solidity, &proof, &public_input);
let result = verify_with_contract(&verifier_solidity, &proof, &public_input);
assert!(result.is_ok());
assert!(result.unwrap() <= cost_upper_bound);
}
Expand Down
7 changes: 2 additions & 5 deletions crates/halo2-verifier/src/lib/verifier_contract.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
#![allow(missing_docs)]
use alloy_primitives::Address;
use alloy_sol_types::{private::Bytes, sol, SolCall};
use halo2_proofs::halo2curves::bn256::Fr;
use shielder_rust_sdk::conversion::field_to_u256;

sol! {
function verifyProof(
address vk,
bytes calldata proof,
uint256[] calldata instances
) public returns (bool);
) public view returns (bool);
}

/// Encode proof into calldata to invoke `Halo2Verifier.verifyProof`.
pub fn encode_calldata(vk: Address, proof: &[u8], instances: &[Fr]) -> Vec<u8> {
pub fn encode_calldata(proof: &[u8], instances: &[Fr]) -> Vec<u8> {
verifyProofCall {
vk,
proof: Bytes::from(proof.to_vec()),
instances: instances.iter().map(field_to_u256::<Fr, 32>).collect(),
}
Expand Down
4 changes: 2 additions & 2 deletions crates/halo2-verifier/templates/Halo2Verifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.26;

/* @dev: linter does not understand inline assembly */
/* solhint-disable no-unused-vars */
contract Halo2Verifier {
library Halo2Verifier {
uint256 internal constant PROOF_LEN_CPTR = {{ proof_cptr - 1 }};
uint256 internal constant PROOF_CPTR = {{ proof_cptr }};
uint256 internal constant NUM_INSTANCE_CPTR = {{ proof_cptr + (proof_len / 32) }};
Expand Down Expand Up @@ -80,7 +80,7 @@ contract Halo2Verifier {
{%- endmatch %}
bytes calldata proof,
uint256[] calldata instances
) public returns (bool) {
) public view returns (bool) {
assembly ("memory-safe") {
// Read EC point (x, y) at (proof_cptr, proof_cptr + 0x20),
// and check if the point is on affine plane,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::shielder::{
CallResult,
};

const GAS_CONSUMPTION: u64 = 1833173;
const GAS_CONSUMPTION: u64 = 1826239;

pub fn prepare_call(
deployment: &mut Deployment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::shielder::{
CallResult, Deployment,
};

const GAS_CONSUMPTION: u64 = 2005642;
const GAS_CONSUMPTION: u64 = 1998654;

pub fn prepare_call(
deployment: &mut Deployment,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::shielder::{
recipient_balance_increased_by, relayer_balance_increased_by, CallResult,
};

const GAS_CONSUMPTION: u64 = 1903378;
const GAS_CONSUMPTION: u64 = 1901269;

struct PrepareCallArgs {
amount: U256,
Expand Down
57 changes: 38 additions & 19 deletions crates/integration-tests/src/shielder/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ use crate::{
unpause_shielder,
},
token,
verifier::{deploy_verifiers_and_keys, VerificationContracts},
verifier::deploy_verifiers,
};

/// The address of the deployer account.
Expand Down Expand Up @@ -83,6 +83,9 @@ pub fn prepare_account(

/// Solc leaves this placeholder for a Poseidon2 contract address.
const POSEIDON2_LIB_PLACEHOLDER: &str = "__$fa7e1b6d9a16949b5fb8159594c1e0b34c$__";
const NEW_ACCOUNT_VERIFIER_LIB_PLACEHOLDER: &str = "__$96275be2429eed9b26a54836ed89b224a2$__";
const DEPOSIT_VERIFIER_LIB_PLACEHOLDER: &str = "__$d586e7da5a0e0b714a5d44ed4e0f6a624d$__";
const WITHDRAW_VERIFIER_LIB_PLACEHOLDER: &str = "__$06bb88608c3ade14b496e12c6067f182f6$__";

pub struct Deployment {
pub evm: EvmRunner,
Expand Down Expand Up @@ -112,10 +115,9 @@ pub fn deployment(
Some(reverting_bytecode),
);

let verification_contracts = deploy_verifiers_and_keys(&mut evm);
let token = deploy_token(&mut evm, owner);
let permit2 = deploy_permit2(&mut evm, owner);
let shielder_address = deploy_shielder_contract(verification_contracts, &mut evm, owner);
let shielder_address = deploy_shielder_contract(&mut evm, owner);
unpause_shielder(shielder_address, &mut evm);
instrument_token(&mut evm, owner, actor, token, permit2);

Expand Down Expand Up @@ -191,35 +193,52 @@ fn deploy_shielder_implementation(evm: &mut EvmRunner) -> Address {
let poseidon2_address =
deploy_contract("Poseidon2T8Assembly.sol", "Poseidon2T8Assembly", evm).to_string();

let verifiers = deploy_verifiers(evm);

// 3. Manipulate the Shielder implementation bytecode to replace the placeholders with the
// corresponding contract addresses.
let implementation_bytecode = String::from_utf8(implementation_bytecode).unwrap();
let with_poseidon2 = implementation_bytecode.replace(
POSEIDON2_LIB_PLACEHOLDER,
poseidon2_address.strip_prefix("0x").unwrap(),
);
let ready_bytecode = hex::decode(with_poseidon2).unwrap();
let with_linked_libs = implementation_bytecode
.replace(
POSEIDON2_LIB_PLACEHOLDER,
poseidon2_address.strip_prefix("0x").unwrap(),
)
.replace(
NEW_ACCOUNT_VERIFIER_LIB_PLACEHOLDER,
verifiers
.new_account_verifier
.to_string()
.strip_prefix("0x")
.unwrap(),
)
.replace(
DEPOSIT_VERIFIER_LIB_PLACEHOLDER,
verifiers
.deposit_verifier
.to_string()
.strip_prefix("0x")
.unwrap(),
)
.replace(
WITHDRAW_VERIFIER_LIB_PLACEHOLDER,
verifiers
.withdraw_verifier
.to_string()
.strip_prefix("0x")
.unwrap(),
);
let ready_bytecode = hex::decode(with_linked_libs).unwrap();

// 4. Finally, deploy the Shielder implementation contract.
evm.create(ready_bytecode, None)
.expect("Failed to deploy Shielder implementation contract")
}

/// Deploy Shielder contract using ERC 1967 proxy.
pub fn deploy_shielder_contract(
verification_contracts: VerificationContracts,
evm: &mut EvmRunner,
owner: Address,
) -> Address {
pub fn deploy_shielder_contract(evm: &mut EvmRunner, owner: Address) -> Address {
let implementation_address = deploy_shielder_implementation(evm);
let initialization_data = initializeCall {
initialOwner: owner,
_newAccountVerifier: verification_contracts.new_account_verifier,
_depositVerifier: verification_contracts.deposit_verifier,
_withdrawVerifier: verification_contracts.withdraw_verifier,
_newAccountVerifyingKey: verification_contracts.new_account_vk,
_depositVerifyingKey: verification_contracts.deposit_vk,
_withdrawVerifyingKey: verification_contracts.withdraw_vk,
_depositLimit: INITIAL_DEPOSIT_LIMIT,
}
.abi_encode();
Expand Down
Loading

0 comments on commit 244c5f4

Please sign in to comment.