Skip to content

Commit

Permalink
modify groth verifier contracts for better test-ability and extendabi…
Browse files Browse the repository at this point in the history
…lity (#414)

This PR does the following:
* adds the `virtual` tag to several functions in
`Groth16VerifierExtension.sol` - this allows us to override the
functions in a parent contract, which makes it much easier to test. With
these changes, we can test as much of the response codepath as possible
without actually having to generate proofs. This will also make it
easier to tweak or extend verification logic for specific chains. Ex on
polygon (and in the future on scroll) we want to skip blockhash
verification. This will be much easier to do once we have this setup.
* moves `verifyBlockhash()` to it's own function. This is specifically
for the `isCDK()` case where we want to override this function.
* renames `verifier.sol` => `Verifier.sol` and
`Groth16VerifierExtensions.sol` => `Groth16VerifierExtension.sol` for
consistency with the contracts codebase
* Changes the name of the `Query` contract to `Groth16VerifierExtension`
for consistency
  • Loading branch information
RyanRHall authored Nov 28, 2024
1 parent fb739e2 commit 1067757
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 22 deletions.
6 changes: 3 additions & 3 deletions gnark-utils/lib/lib.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,16 +308,16 @@ func SaveVerifierSolidity(assetDir string, vk groth16.VerifyingKey) error {
}
content := buf.String()

contractFile, err := os.Create(assetDir + "/verifier.sol")
contractFile, err := os.Create(assetDir + "/Verifier.sol")
if err != nil {
return errors.Wrap(err, "create verifier.sol file")
return errors.Wrap(err, "create Verifier.sol file")
}
defer contractFile.Close()

w := bufio.NewWriter(contractFile)
// write the new content to the writer
if _, err = w.Write([]byte(content)); err != nil {
return errors.Wrap(err, "write to verifier.sol")
return errors.Wrap(err, "write to Verifier.sol")
}

return nil
Expand Down
2 changes: 1 addition & 1 deletion gnark-utils/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use anyhow::Result;
use std::ffi::CString;

/// Compile the circuit data and generate the asset files of `r1cs.bin`,
/// `pk.bin`, `vk.bin` and `verifier.sol`.
/// `pk.bin`, `vk.bin` and `Verifier.sol`.
pub fn compile_and_generate_assets(
common_circuit_data: &str,
verifier_only_circuit_data: &str,
Expand Down
2 changes: 1 addition & 1 deletion gnark-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ mod go {
extern "C" {
/// Compile and generate the asset files from the circuit data to the
/// specified dir. The generated files are `r1cs.bin`, `pk.bin`,
/// `vk.bin` and `verifier.sol`.
/// `vk.bin` and `Verifier.sol`.
pub fn CompileAndGenerateAssets(
common_circuit_data: *const c_char,
verifier_only_circuit_data: *const c_char,
Expand Down
2 changes: 1 addition & 1 deletion groth16-framework/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use std::{
type WrapCircuit = WrappedCircuit<DefaultParameters, Groth16WrapperParameters, D>;

/// Compile the circuit data and generate the asset files of `r1cs.bin`,
/// `pk.bin`, `vk.bin` and `verifier.sol`.
/// `pk.bin`, `vk.bin` and `Verifier.sol`.
/// This function returns the full file path of the Solidity verifier contract.
pub fn compile_and_generate_assets(
circuit_data: CircuitData<F, C, D>,
Expand Down
4 changes: 2 additions & 2 deletions groth16-framework/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! 1. Generate the asset files.
//!
//! The asset files are `circuit.bin`, `r1cs.bin`, `pk.bin`, `vk.bin` and
//! `verifier.sol`. User could call the `compile_and_generate_assets`
//! `Verifier.sol`. User could call the `compile_and_generate_assets`
//! function to generate these files as below.
//!
//! ``
Expand Down Expand Up @@ -76,7 +76,7 @@ pub mod utils;
mod verifier;

// The function is used to generate the asset files of `circuit.bin`,
// `r1cs.bin`, `pk.bin`, `vk.bin` and `verifier.sol`. It's only necessary to be
// `r1cs.bin`, `pk.bin`, `vk.bin` and `Verifier.sol`. It's only necessary to be
// called for re-generating these asset files when the circuit code changes.
pub use compiler::compile_and_generate_assets;

Expand Down
2 changes: 1 addition & 1 deletion groth16-framework/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ fn groth16_verify(asset_dir: &str, proof: &Groth16Proof) {
/// Test the Solidity verification.
fn evm_verify(asset_dir: &str, proof: &Groth16Proof) {
let solidity_file_path = Path::new(asset_dir)
.join("verifier.sol")
.join("Verifier.sol")
.to_string_lossy()
.to_string();

Expand Down
2 changes: 1 addition & 1 deletion groth16-framework/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use std::{
pub const CIRCUIT_DATA_FILENAME: &str = "circuit.bin";

/// The filename of the exported Solidity verifier contract.
pub const SOLIDITY_VERIFIER_FILENAME: &str = "verifier.sol";
pub const SOLIDITY_VERIFIER_FILENAME: &str = "Verifier.sol";

/// Convert a string with `0x` prefix to an U256.
pub fn hex_to_u256(s: &str) -> Result<U256> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

pragma solidity ^0.8.0;

import {Verifier} from "./verifier.sol";
import {Verifier} from "./Verifier.sol";

// The query input struct passed into the processQuery function
struct QueryInput {
Expand Down Expand Up @@ -40,7 +40,7 @@ enum QueryErrorCode {
ComputationOverflow
}

contract Query is Verifier {
contract Groth16VerifierExtension is Verifier {
// Top 3 bits mask.
uint256 constant TOP_THREE_BIT_MASK = ~(uint256(7) << 253);

Expand Down Expand Up @@ -94,7 +94,12 @@ contract Query is Verifier {
// Then ensure this hash value equals to the last Groth16 input (groth16_inputs[2]).
// 3. Parse the items from public inputs, and check as expected for query.
// 4. Parse and return the query output from public inputs.
function processQuery(bytes32[] calldata data, QueryInput memory query) public view returns (QueryOutput memory) {
function processQuery(bytes32[] calldata data, QueryInput memory query)
public
view
virtual
returns (QueryOutput memory)
{
// 1. Groth16 verification
uint256[3] memory groth16Inputs = verifyGroth16Proof(data);

Expand All @@ -109,7 +114,7 @@ contract Query is Verifier {
}

// Parse the Groth16 proofs and inputs, do verification, and returns the Groth16 inputs.
function verifyGroth16Proof(bytes32[] calldata data) internal view returns (uint256[3] memory) {
function verifyGroth16Proof(bytes32[] calldata data) internal view virtual returns (uint256[3] memory) {
uint256[8] memory proofs;
uint256[3] memory inputs;

Expand All @@ -130,7 +135,7 @@ contract Query is Verifier {
}

// Compute sha256 on the public inputs, and ensure it equals to the last Groth16 input.
function verifyPublicInputs(bytes32[] calldata data, uint256[3] memory groth16Inputs) internal pure {
function verifyPublicInputs(bytes32[] calldata data, uint256[3] memory groth16Inputs) internal pure virtual {
// Parse the public inputs from calldata.
bytes memory pi = parsePublicInputs(data);

Expand Down Expand Up @@ -166,13 +171,18 @@ contract Query is Verifier {
}

// Verify the public inputs with the expected query.
function verifyQuery(bytes32[] calldata data, QueryInput memory query) internal pure returns (QueryErrorCode) {
function verifyQuery(bytes32[] calldata data, QueryInput memory query)
internal
view
virtual
returns (QueryErrorCode)
{
// Retrieve the last Uint256 of public inputs.
bytes32 rem = data[PI_REM_OFFSET];

// Check the block hash and computational hash.
bytes32 blockHash = convertToBlockHash(data[PI_OFFSET + BLOCK_HASH_POS]);
require(blockHash == query.blockHash, "Block hash must equal as expected.");
verifyBlockHash(blockHash, query.blockHash);
bytes32 computationalHash = data[PI_OFFSET + COMPUTATIONAL_HASH_POS];
require(computationalHash == query.computationalHash, "Computational hash must equal as expected.");

Expand Down Expand Up @@ -215,8 +225,21 @@ contract Query is Verifier {
return QueryErrorCode.ComputationOverflow;
}

/// @notice verifies two blockhashed are equal
/// @param blockHash the blockhash computed from the proof
/// @param expectedBlockHash the expected blockhash, retrieved from the query
/// @dev this function is virtual to allow for different implementations in different environments
function verifyBlockHash(bytes32 blockHash, bytes32 expectedBlockHash) internal view virtual {
require(blockHash == expectedBlockHash, "Block hash must equal as expected.");
}

// Parse the query output from the public inputs.
function parseOutput(bytes32[] calldata data, QueryErrorCode error) internal pure returns (QueryOutput memory) {
function parseOutput(bytes32[] calldata data, QueryErrorCode error)
internal
pure
virtual
returns (QueryOutput memory)
{
bytes32 rem = data[PI_REM_OFFSET];

// Retrieve total number of the matched rows.
Expand Down
8 changes: 4 additions & 4 deletions groth16-framework/tests/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ fn test_local_groth16_proof() {

// Verify the query in the Solidity function.
// The editing Solidity code is saved in `test_data/TestGroth16Verifier.sol`.
// TODO: In practice, the separate `Groth16VerifierExtensions.sol` and
// `verifier.sol` should be used, but the `revm` (Rust EVM) cannot support
// TODO: In practice, the separate `Groth16VerifierExtension.sol` and
// `Verifier.sol` should be used, but the `revm` (Rust EVM) cannot support
// compilated contract deployment (as inheritance) for now.
verify_query_in_solidity(ASSET_DIR);
}
Expand Down Expand Up @@ -64,8 +64,8 @@ fn test_groth16_proving_for_query() {

// Verify the query in the Solidity function.
// The editing Solidity code is saved in `test_data/TestGroth16Verifier.sol`.
// TODO: In practice, the separate `Groth16VerifierExtensions.sol` and
// `verifier.sol` should be used, but the `revm` (Rust EVM) cannot support
// TODO: In practice, the separate `Groth16VerifierExtension.sol` and
// `Verifier.sol` should be used, but the `revm` (Rust EVM) cannot support
// compilated contract deployment (as inheritance) for now.
verify_query_in_solidity(ASSET_DIR);
}
Expand Down

0 comments on commit 1067757

Please sign in to comment.