diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e8ff498f..712cb4ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -73,15 +73,15 @@ jobs: strategy: fail-fast: false matrix: - os: [Linux, macOS] - feature: [default] - device: [cpu] include: + - os: Linux + feature: default + device: cpu - os: Linux feature: cuda device: nvidia_rtx_a5000 - os: macOS - feature: metal + feature: default device: apple_m2_pro env: FEATURE: ${{ matrix.feature }} diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index e2555e46..e008f0af 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -11,14 +11,14 @@ repository = { workspace = true } anyhow = "1.0" [dependencies] -alloy-sol-types = { workspace = true } +alloy = { workspace = true, features = ["sol-types", "contract"] } anyhow = { workspace = true } -ethers = { version = "2.0", features = ["rustls", "ws"] } risc0-zkvm = { workspace = true } [dev-dependencies] +hex = "0.4" regex = "1.10" -tokio = { version = "1", features = ["macros", "rt"] } +tokio = { workspace = true, features = ["macros", "rt"] } [lib] doctest = false diff --git a/contracts/build.rs b/contracts/build.rs deleted file mode 100644 index ced35e6c..00000000 --- a/contracts/build.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2024 RISC Zero, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Build Solidity contracts with Foundry when `cargo build` is invoked. - -use std::process::Command; - -use anyhow::Context; - -fn main() -> anyhow::Result<()> { - let out_dir = std::env::var("OUT_DIR").unwrap(); - let out_dir = std::path::Path::new(&out_dir); - let cache_dir = out_dir.join(".cache"); - - let mut forge_build = Command::new("forge") - .arg("build") - .arg("--cache-path") - .arg(cache_dir) - .arg("--out") - .arg(out_dir) - .spawn() - .context("failed to start `forge build`")?; - - let status = forge_build.wait().context("failed to run `forge build`")?; - if !status.success() { - anyhow::bail!("`forge build` exited with failed status: {}", status); - } - - Ok(()) -} diff --git a/contracts/src/groth16.rs b/contracts/src/groth16.rs index 832f9423..b1e30a6f 100644 --- a/contracts/src/groth16.rs +++ b/contracts/src/groth16.rs @@ -12,23 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -use alloy_sol_types::SolValue; +use alloy::sol_types::SolValue; use anyhow::Result; use risc0_zkvm::{sha::Digestible, Groth16ReceiptVerifierParameters}; /// ABI encoding of the seal. -pub fn abi_encode(seal: Vec) -> Result> { +pub fn abi_encode(seal: impl AsRef<[u8]>) -> Result> { Ok(encode(seal)?.abi_encode()) } -/// encoding of the seal with selector. -pub fn encode(seal: Vec) -> Result> { +/// Encoding of a Groth16 seal by prefixing it with the verifier selector. +/// +/// The verifier selector is determined from the first 4 bytes of the hash of the verifier +/// parameters including the Groth16 verification key and the control IDs that commit to the RISC +/// Zero circuits. +/// +/// NOTE: Selector value of the current zkVM version is used. If you need to use a selector from a +/// different version of the zkVM, use the [encode_seal] method instead. +pub fn encode(seal: impl AsRef<[u8]>) -> Result> { let verifier_parameters_digest = Groth16ReceiptVerifierParameters::default().digest(); let selector = &verifier_parameters_digest.as_bytes()[..4]; // Create a new vector with the capacity to hold both selector and seal - let mut selector_seal = Vec::with_capacity(selector.len() + seal.len()); + let mut selector_seal = Vec::with_capacity(selector.len() + seal.as_ref().len()); selector_seal.extend_from_slice(selector); - selector_seal.extend_from_slice(&seal); + selector_seal.extend_from_slice(seal.as_ref()); Ok(selector_seal) } @@ -36,7 +43,6 @@ pub fn encode(seal: Vec) -> Result> { #[cfg(test)] mod tests { use anyhow::anyhow; - use ethers::utils::hex; use regex::Regex; use super::*; diff --git a/contracts/src/lib.rs b/contracts/src/lib.rs index 112c39c8..7670d634 100644 --- a/contracts/src/lib.rs +++ b/contracts/src/lib.rs @@ -12,15 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ethers::prelude::*; - -abigen!( - IRiscZeroVerifier, - "$OUT_DIR/IRiscZeroVerifier.sol/IRiscZeroVerifier.json" -); -abigen!( - RiscZeroGroth16Verifier, - "$OUT_DIR/RiscZeroGroth16Verifier.sol/RiscZeroGroth16Verifier.json" +alloy::sol!( + #![sol(rpc, all_derives)] + "src/IRiscZeroVerifier.sol" ); pub mod groth16; + +use anyhow::{bail, Result}; +use risc0_zkvm::{sha::Digestible, InnerReceipt}; + +/// Encode the seal of the given receipt for use with EVM smart contract verifiers. +/// +/// Appends the verifier selector, determined from the first 4 bytes of the verifier parameters +/// including the Groth16 verification key and the control IDs that commit to the RISC Zero +/// circuits. +pub fn encode_seal(receipt: &risc0_zkvm::Receipt) -> Result> { + let seal = match receipt.inner.clone() { + InnerReceipt::Fake(receipt) => { + let seal = receipt.claim.digest().as_bytes().to_vec(); + let selector = &[0u8; 4]; + // Create a new vector with the capacity to hold both selector and seal + let mut selector_seal = Vec::with_capacity(selector.len() + seal.len()); + selector_seal.extend_from_slice(selector); + selector_seal.extend_from_slice(&seal); + selector_seal + } + InnerReceipt::Groth16(receipt) => { + let selector = &receipt.verifier_parameters.as_bytes()[..4]; + // Create a new vector with the capacity to hold both selector and seal + let mut selector_seal = Vec::with_capacity(selector.len() + receipt.seal.len()); + selector_seal.extend_from_slice(selector); + selector_seal.extend_from_slice(receipt.seal.as_ref()); + selector_seal + } + _ => bail!("Unsupported receipt type"), + }; + Ok(seal) +} diff --git a/examples/erc20-counter/apps/src/bin/publisher.rs b/examples/erc20-counter/apps/src/bin/publisher.rs index 25298452..47bcb6ab 100644 --- a/examples/erc20-counter/apps/src/bin/publisher.rs +++ b/examples/erc20-counter/apps/src/bin/publisher.rs @@ -26,7 +26,7 @@ use alloy_primitives::Address; use anyhow::{ensure, Context, Result}; use clap::Parser; use erc20_counter_methods::BALANCE_OF_ELF; -use risc0_ethereum_contracts::groth16::encode; +use risc0_ethereum_contracts::encode_seal; use risc0_steel::{ ethereum::{EthEvmEnv, ETH_SEPOLIA_CHAIN_SPEC}, host::BlockNumberOrTag, @@ -134,13 +134,11 @@ async fn main() -> Result<()> { .await? .context("failed to create proof")?; let receipt = prove_info.receipt; + let seal = encode_seal(&receipt)?; // Create an alloy instance of the Counter contract. let contract = ICounter::new(args.contract, provider); - // Encode the groth16 seal with the selector and call the increment function of the contract. - let seal = encode(receipt.inner.groth16()?.seal.clone())?; - // Call the increment function of the contract and wait for confirmation. println!( "Sending Tx calling {} Function of {:#}...", diff --git a/examples/erc20-counter/script/DeployCounter.s.sol b/examples/erc20-counter/script/DeployCounter.s.sol index 266fc1bf..b4159688 100644 --- a/examples/erc20-counter/script/DeployCounter.s.sol +++ b/examples/erc20-counter/script/DeployCounter.s.sol @@ -19,7 +19,7 @@ pragma solidity ^0.8.20; import {Script} from "forge-std/Script.sol"; import {console2} from "forge-std/console2.sol"; import {IRiscZeroVerifier} from "risc0/IRiscZeroVerifier.sol"; -import {ControlID, RiscZeroGroth16Verifier} from "risc0/groth16/RiscZeroGroth16Verifier.sol"; +import {RiscZeroCheats} from "risc0/test/RiscZeroCheats.sol"; import {Counter} from "../contracts/Counter.sol"; import {ERC20} from "../contracts/ERC20.sol"; @@ -30,7 +30,7 @@ import {ERC20} from "../contracts/ERC20.sol"; /// /// See the Foundry documentation for more information about Solidity scripts. /// https://book.getfoundry.sh/tutorials/solidity-scripting -contract CounterrDeploy is Script { +contract CounterDeploy is Script, RiscZeroCheats { function run() external { uint256 deployerKey = uint256(vm.envBytes32("ETH_WALLET_PRIVATE_KEY")); @@ -39,8 +39,7 @@ contract CounterrDeploy is Script { ERC20 toyken = new ERC20("TOYKEN", "TOY", 0); console2.log("Deployed ERC20 TOYKEN to", address(toyken)); - IRiscZeroVerifier verifier = new RiscZeroGroth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); - console2.log("Deployed RiscZeroGroth16Verifier to", address(verifier)); + IRiscZeroVerifier verifier = deployRiscZeroVerifier(); Counter counter = new Counter(verifier, address(toyken)); console2.log("Deployed Counter to", address(counter)); diff --git a/ffi/Cargo.toml b/ffi/Cargo.toml index 6785d59a..3813399b 100644 --- a/ffi/Cargo.toml +++ b/ffi/Cargo.toml @@ -8,9 +8,9 @@ homepage = { workspace = true } repository = { workspace = true } [dependencies] +alloy = { workspace = true } anyhow = { workspace = true } clap = { version = "4.5", features = ["derive", "env"] } -ethers = { version = "2.0" } hex = { version = "0.4" } risc0-ethereum-contracts = { workspace = true } risc0-zkvm = { workspace = true, features = ["client"] } diff --git a/ffi/src/main.rs b/ffi/src/main.rs index db88376d..86b5802f 100644 --- a/ffi/src/main.rs +++ b/ffi/src/main.rs @@ -14,13 +14,11 @@ use std::io::Write; +use alloy::{primitives::Bytes, sol_types::SolValue}; use anyhow::{Context, Result}; use clap::Parser; -use ethers::abi::Token; -use risc0_ethereum_contracts::groth16::encode; -use risc0_zkvm::{ - default_prover, is_dev_mode, sha::Digestible, ExecutorEnv, ProverOpts, VerifierContext, -}; +use risc0_ethereum_contracts::encode_seal; +use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext}; #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] @@ -54,8 +52,8 @@ pub fn main() -> Result<()> { fn prove_ffi(elf_path: String, input: Vec) -> Result<()> { let elf = std::fs::read(elf_path).unwrap(); let (journal, seal) = prove(&elf, &input)?; - let calldata = vec![Token::Bytes(journal), Token::Bytes(seal)]; - let output = hex::encode(ethers::abi::encode(&calldata)); + let calldata = vec![Bytes(journal.into()), Bytes(seal.into())]; + let output = hex::encode(calldata.abi_encode()); // Forge test FFI calls expect hex encoded bytes sent to stdout print!("{output}"); @@ -77,15 +75,6 @@ fn prove(elf: &[u8], input: &[u8]) -> Result<(Vec, Vec)> { .receipt; let journal = receipt.clone().journal.bytes; - - let seal = match is_dev_mode() { - true => { - let mut seal = Vec::new(); - seal.extend(vec![0u8; 4]); - seal.extend(receipt.claim()?.digest().as_bytes()); - seal - } - false => encode(receipt.inner.groth16()?.seal.clone())?, - }; + let seal = encode_seal(&receipt)?; Ok((journal, seal)) }