diff --git a/Cargo.lock b/Cargo.lock index b53f7577..b9c4a06e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2463,6 +2463,7 @@ dependencies = [ name = "canoe-sp1-cc-host" version = "0.1.0" dependencies = [ + "alloy-genesis", "alloy-primitives 1.4.1", "alloy-rpc-types", "alloy-sol-types", @@ -4406,6 +4407,7 @@ name = "hokulea-example-preloader" version = "0.1.0" dependencies = [ "alloy-evm 0.22.6", + "alloy-genesis", "alloy-op-evm", "anyhow", "canoe-provider", diff --git a/Cargo.toml b/Cargo.toml index d1d7828b..9ed72889 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,12 +3,12 @@ resolver = "2" members = [ "bin/*", "crates/*", - "example/preloader", + "example/preloader", "canoe/bindings", "canoe/provider", "canoe/steel/apps", "canoe/steel/methods", - "canoe/steel/verifier", + "canoe/steel/verifier", "canoe/sp1-cc/host", "canoe/sp1-cc/client", "canoe/sp1-cc/vkey-bin", @@ -55,12 +55,12 @@ hokulea-zkvm-verification = { path = "crates/zkvm-verification", version = "0.1. eigenda-cert = { path = "crates/eigenda-cert" } # Alloy (Network) -alloy-rlp = { version = "0.3.12", default-features = false } -alloy-provider = { version = "1.0.38", default-features = false } alloy-consensus = { version = "1.0.38", default-features = false } -alloy-rpc-types = { version = "1.0.38", default-features = false } alloy-genesis = { version = "1.0.38", default-features = false } alloy-primitives = { version = "1.3.1", default-features = false } +alloy-provider = { version = "1.0.38", default-features = false } +alloy-rlp = { version = "0.3.12", default-features = false } +alloy-rpc-types = { version = "1.0.38", default-features = false } alloy-sol-types = { version = "1.3.1", default-features = false } # Execution @@ -137,7 +137,7 @@ sp1-core-executor = "5.2.1" sp1-prover = "5.2.1" sp1-cc-client-executor = { git = "https://github.com/succinctlabs/sp1-contract-call.git", tag = "reth-1.9.1" } sp1-cc-host-executor = { git = "https://github.com/succinctlabs/sp1-contract-call.git", tag = "reth-1.9.1" } -rsp-primitives = { git = "https://github.com/succinctlabs/rsp", tag = "reth-1.9.1" } +rsp-primitives = { git = "https://github.com/succinctlabs/rsp", tag = "reth-1.9.1" } # ZKVM deps hex = "0.4" diff --git a/canoe/sp1-cc/host/Cargo.toml b/canoe/sp1-cc/host/Cargo.toml index f5ecfc8b..0c325ffd 100644 --- a/canoe/sp1-cc/host/Cargo.toml +++ b/canoe/sp1-cc/host/Cargo.toml @@ -8,8 +8,9 @@ sp1-cc-host-executor = { workspace = true } sp1-cc-client-executor = { workspace = true } rsp-primitives = { workspace = true } +alloy-genesis.workspace = true alloy-primitives.workspace = true -alloy-sol-types = { workspace = true } +alloy-sol-types.workspace = true bincode.workspace = true anyhow = { workspace = true } @@ -25,4 +26,4 @@ tracing.workspace = true # sp1 sp1-sdk.workspace = true sp1-core-executor.workspace = true -sp1-prover.workspace = true \ No newline at end of file +sp1-prover.workspace = true diff --git a/canoe/sp1-cc/host/src/lib.rs b/canoe/sp1-cc/host/src/lib.rs index ff4104cc..f4dcdf82 100644 --- a/canoe/sp1-cc/host/src/lib.rs +++ b/canoe/sp1-cc/host/src/lib.rs @@ -1,3 +1,4 @@ +use alloy_genesis::ChainConfig; use alloy_primitives::Address; use alloy_rpc_types::BlockNumberOrTag; use alloy_sol_types::SolType; @@ -5,6 +6,7 @@ use anyhow::Result; use async_trait::async_trait; use canoe_bindings::{Journal, StatusCode}; use canoe_provider::{CanoeInput, CanoeProvider, CertVerifierCall}; +use rsp_primitives::genesis::genesis_from_json; use sp1_cc_client_executor::ContractInput; use sp1_cc_host_executor::{EvmSketch, Genesis}; use sp1_sdk::{ @@ -19,9 +21,7 @@ use std::{ use tracing::{debug, info, warn}; use url::Url; -use rsp_primitives::genesis::genesis_from_json; - -/// The ELF we want to execute inside the zkVM. +/// The default ELF we want to execute inside the zkVM. pub const ELF: &[u8] = include_bytes!("../../elf/canoe-sp1-cc-client"); const DEFAULT_NETWORK_PRIVATE_KEY: &str = @@ -62,6 +62,10 @@ pub struct CanoeSp1CCProvider { pub eth_rpc_url: String, /// if true, execute and return a mock proof pub mock_mode: bool, + /// optional custom chain configuration for genesis block + pub custom_chain_config: Option, + /// optional custom ELF bytes for the SP1 zkVM client. If None, uses default ELF + pub custom_canoe_client_elf: Option>, } #[async_trait] @@ -77,7 +81,16 @@ impl CanoeProvider for CanoeSp1CCProvider { return None; } - Some(get_sp1_cc_proof(canoe_inputs, &self.eth_rpc_url, self.mock_mode).await) + Some( + get_sp1_cc_proof( + canoe_inputs, + &self.eth_rpc_url, + self.mock_mode, + self.custom_chain_config.clone(), + self.custom_canoe_client_elf.as_deref(), + ) + .await, + ) } } @@ -92,6 +105,10 @@ pub struct CanoeSp1CCReducedProofProvider { pub eth_rpc_url: String, /// if true, execute and return a mock proof pub mock_mode: bool, + /// optional custom chain configuration for genesis block + pub custom_chain_config: Option, + /// optional custom ELF bytes for the SP1 zkVM client. If None, uses default ELF + pub custom_canoe_client_elf: Option>, } #[async_trait] @@ -107,7 +124,15 @@ impl CanoeProvider for CanoeSp1CCReducedProofProvider { return None; } - match get_sp1_cc_proof(canoe_inputs, &self.eth_rpc_url, self.mock_mode).await { + match get_sp1_cc_proof( + canoe_inputs, + &self.eth_rpc_url, + self.mock_mode, + self.custom_chain_config.clone(), + self.custom_canoe_client_elf.as_deref(), + ) + .await + { Ok(proof) => { let SP1Proof::Compressed(proof) = proof.proof else { panic!("cannot get Sp1ReducedProof") @@ -123,6 +148,8 @@ async fn get_sp1_cc_proof( canoe_inputs: Vec, eth_rpc_url: &str, mock_mode: bool, + custom_chain_config: Option, + custom_canoe_client_elf: Option<&[u8]>, ) -> Result { // ensure chain id and l1 block number across all DAcerts are identical let l1_chain_id = canoe_inputs[0].l1_chain_id; @@ -147,35 +174,26 @@ async fn get_sp1_cc_proof( let rpc_url = Url::from_str(eth_rpc_url).unwrap(); - let sketch = match Genesis::try_from(l1_chain_id) { - Ok(genesis) => { - EvmSketch::builder() - .at_block(block_number) - .with_genesis(genesis) - .el_rpc_url(rpc_url) - .build() - .await? - } - // if genesis is not available in the sp1-cc library, the code uses custom genesis config - Err(_) => { - let chain_config = match l1_chain_id { - 17000 => genesis_from_json(HOLESKY_GENESIS).expect("genesis from json"), - 3151908 => genesis_from_json(KURTOSIS_DEVNET_GENESIS).expect("genesis from json"), - _ => panic!("chain id {l1_chain_id} is not supported by canoe sp1 cc"), - }; - - let genesis = Genesis::Custom(chain_config.config); - - EvmSketch::builder() - .at_block(block_number) - .with_genesis(genesis) - .el_rpc_url(rpc_url) - .build() - .await - .expect("evm sketch builder") - } + let genesis = if let Some(chain_config) = custom_chain_config { + Genesis::Custom(chain_config) + } else if let Ok(genesis) = Genesis::try_from(l1_chain_id) { + genesis + } else { + let chain_config = match l1_chain_id { + 17000 => genesis_from_json(HOLESKY_GENESIS).expect("genesis from json"), + 3151908 => genesis_from_json(KURTOSIS_DEVNET_GENESIS).expect("genesis from json"), + _ => panic!("chain id {l1_chain_id} is not supported by canoe sp1 cc"), + }; + Genesis::Custom(chain_config.config) }; + let sketch = EvmSketch::builder() + .at_block(block_number) + .with_genesis(genesis) + .el_rpc_url(rpc_url) + .build() + .await?; + let derived_l1_header_hash = sketch.anchor.header().hash_slow(); assert!(l1_head_block_hash == derived_l1_header_hash); @@ -222,12 +240,13 @@ async fn get_sp1_cc_proof( .network() .private_key(&network_private_key) .build(); - let (pk, _vk) = client.setup(ELF); + let elf_bytes = custom_canoe_client_elf.unwrap_or(ELF); + let (pk, _vk) = client.setup(elf_bytes); let proof = if mock_mode { // Execute the program using the `ProverClient.execute` method, without generating a proof. let (public_values, report) = client - .execute(ELF, &stdin) + .execute(elf_bytes, &stdin) .run() .expect("sp1-cc should have executed the ELF"); info!( diff --git a/canoe/sp1-cc/verifier/src/lib.rs b/canoe/sp1-cc/verifier/src/lib.rs index 24a1a1d6..488984e1 100644 --- a/canoe/sp1-cc/verifier/src/lib.rs +++ b/canoe/sp1-cc/verifier/src/lib.rs @@ -2,6 +2,8 @@ #![no_std] extern crate alloc; +use core::str::FromStr; + use alloc::{ string::{String, ToString}, vec::Vec, @@ -39,7 +41,21 @@ pub const V_KEY: [u32; 8] = [ ]; #[derive(Clone)] -pub struct CanoeSp1CCVerifier {} +pub struct CanoeSp1CCVerifier { + v_key: [u32; 8], +} + +impl CanoeSp1CCVerifier { + pub fn new(v_key: [u32; 8]) -> Self { + Self { v_key } + } +} + +impl Default for CanoeSp1CCVerifier { + fn default() -> Self { + Self::new(V_KEY) + } +} impl CanoeVerifier for CanoeSp1CCVerifier { // some variable is unused, because when sp1-cc verifier is not configured in zkVM mode, all tests @@ -50,7 +66,7 @@ impl CanoeVerifier for CanoeSp1CCVerifier { cert_validity_pair: Vec<(AltDACommitment, CertValidity)>, canoe_proof_bytes: Option>, ) -> Result<(), HokuleaCanoeVerificationError> { - info!("using CanoeSp1CCVerifier with v_key {:?}", V_KEY); + info!("using CanoeSp1CCVerifier with v_key {:?}", &self.v_key); assert!(!cert_validity_pair.is_empty()); @@ -72,7 +88,7 @@ impl CanoeVerifier for CanoeSp1CCVerifier { let public_values_digest = Sha256::digest(journals_bytes); // the function will panic if the proof is incorrect // https://github.com/succinctlabs/sp1/blob/011d2c64808301878e6f0375c3596b3e22e53949/crates/zkvm/lib/src/verify.rs#L3 - verify_sp1_proof(&V_KEY, &public_values_digest.into()); + verify_sp1_proof(&self.v_key, &public_values_digest.into()); Ok(()) } else { panic!("CanoeSp1CCVerifier should only be used for secure integration whose validation happens in zkVM"); @@ -154,6 +170,10 @@ fn hash_chain_config(chain_id: u64, active_fork_name: String) -> B256 { // Resulting different genesis hash. // https://github.com/succinctlabs/rsp/blob/c14b4005ea9257e4d434a080b6900411c17f781b/crates/primitives/src/genesis.rs#L19 fn rsp_genesis_hash(chain_id: u64) -> B256 { + if let Some(s) = option_env!("CUSTOM_RSP_GENESIS_HASH") { + return B256::from_str(s).expect("CUSTOM_RSP_GENESIS_HASH should be a valid hex string"); + } + match Genesis::try_from(chain_id) { Ok(genesis) => { let rsp_genesis_bytes = diff --git a/canoe/verifier-address-fetcher/src/lib.rs b/canoe/verifier-address-fetcher/src/lib.rs index 7893ee74..cd7a2984 100644 --- a/canoe/verifier-address-fetcher/src/lib.rs +++ b/canoe/verifier-address-fetcher/src/lib.rs @@ -95,3 +95,13 @@ fn cert_verifier_address_abi_encode_interface( } } } + +impl CanoeVerifierAddressFetcher for Address { + fn fetch_address( + &self, + _chain_id: u64, + _versioned_cert: &EigenDAVersionedCert, + ) -> Result { + Ok(*self) + } +} diff --git a/canoe/verifier/src/chain_spec.rs b/canoe/verifier/src/chain_spec.rs index 981d466d..deee89d5 100644 --- a/canoe/verifier/src/chain_spec.rs +++ b/canoe/verifier/src/chain_spec.rs @@ -1,3 +1,5 @@ +use core::str::FromStr; + use alloy_genesis::Genesis; use alloy_primitives::B256; use reth_chainspec::{Chain, ChainSpec, ChainSpecBuilder, HOLESKY, MAINNET, SEPOLIA}; @@ -10,6 +12,10 @@ pub fn derive_chain_spec_id( l1_head_block_timestamp: u64, l1_head_block_number: u64, ) -> SpecId { + if let Some(s) = option_env!("CUSTOM_CHAIN_SPEC") { + return SpecId::from_str(s).expect("CUSTOM_CHAIN_SPEC should be a valid hard fork name"); + } + match l1_chain_id { // mainnet 1 => spec_by_timestamp_and_block_number( diff --git a/example/preloader/Cargo.toml b/example/preloader/Cargo.toml index 5fcfb153..36edf5d5 100644 --- a/example/preloader/Cargo.toml +++ b/example/preloader/Cargo.toml @@ -13,12 +13,13 @@ hokulea-client.workspace = true hokulea-compute-proof.workspace = true # General +alloy-genesis.workspace = true anyhow.workspace = true -tokio = { workspace = true, features = ["full"] } -clap = { workspace = true, features = ["derive", "env"] } cfg-if = { workspace = true } -tracing = { workspace = true } +clap = { workspace = true, features = ["derive", "env"] } serde_json.workspace = true +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true } kona-client.workspace = true kona-preimage.workspace = true diff --git a/example/preloader/src/main.rs b/example/preloader/src/main.rs index eecd18f3..153fc007 100644 --- a/example/preloader/src/main.rs +++ b/example/preloader/src/main.rs @@ -87,9 +87,20 @@ async fn main() -> anyhow::Result<()> { .and_then(|v| v.parse::().ok()) .unwrap_or(false); - let canoe_provider = CanoeSp1CCReducedProofProvider{ + let custom_chain_config = if let Some(path) = cfg.kona_cfg.l1_config_path { + let json = std::fs::read_to_string(&path) + .with_context(|| format!("failed to read genesis file at {}", path.display()))?; + let genesis = serde_json::from_str::(&json) + .with_context(|| format!("failed to parse L1 genesis from {}", path.display()))?; + Some(genesis.config) + } else { + None + }; + let canoe_provider = CanoeSp1CCReducedProofProvider { eth_rpc_url: cfg.kona_cfg.l1_node_address.clone().unwrap(), mock_mode, + custom_chain_config, + custom_canoe_client_elf: None, }; let canoe_verifier = CanoeNoOpVerifier{}; } else {