Skip to content

Commit 8ad5a6f

Browse files
authored
feat(raiko): use simple contract to verify sp1 proof (#381)
* use simple contract to verify sp1 proof * fix fmt
1 parent 7e61432 commit 8ad5a6f

File tree

8 files changed

+168
-85
lines changed

8 files changed

+168
-85
lines changed

Cargo.lock

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

host/tests/proof_request.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
#![cfg(feature = "integration")]
12
use crate::common::scenarios::{
23
test_v1_api_format, test_v2_api_response, test_v2_cancellation, test_v2_prune, test_v2_report,
34
};
45

56
mod common;
67

78
#[tokio::test]
8-
#[cfg(feature = "integration")]
99
async fn run_scenarios_sequentially() -> anyhow::Result<()> {
1010
test_v2_prune().await?;
1111
test_v2_report().await?;

provers/sp1/driver/Cargo.toml

+10-5
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,21 @@ required-features = ["enable"]
1717

1818
[dependencies]
1919
raiko-lib = { workspace = true, optional = true }
20+
alloy-contract = { workspace = true, optional = true }
2021
alloy-primitives = { workspace = true, optional = true }
22+
alloy-provider = { workspace = true, optional = true }
2123
alloy-sol-types = { workspace = true, optional = true }
2224
alloy-eips = { workspace = true, optional = true }
2325
serde = { workspace = true, optional = true }
2426
serde_json = { workspace = true, optional = true }
2527
sp1-sdk = { workspace = true, optional = true }
2628
anyhow = { workspace = true, optional = true }
2729
once_cell = { workspace = true, optional = true }
28-
sha3 = { workspace = true, optional = true, default-features = false}
30+
sha3 = { workspace = true, optional = true, default-features = false }
2931
serde_with = { workspace = true, optional = true }
30-
dotenv = {workspace = true, optional = true}
31-
cfg-if = {workspace = true }
32-
bincode = {workspace = true }
32+
dotenv = { workspace = true, optional = true }
33+
cfg-if = { workspace = true }
34+
bincode = { workspace = true }
3335
reth-primitives = { workspace = true }
3436
tokio = { workspace = true, optional = true }
3537
tracing = { workspace = true, optional = true }
@@ -49,13 +51,16 @@ enable = [
4951
"raiko-lib",
5052
"sp1-sdk",
5153
"anyhow",
54+
"alloy-contract",
5255
"alloy-primitives",
56+
"alloy-provider",
5357
"alloy-sol-types",
5458
"once_cell",
5559
"sha3",
5660
"dotenv",
5761
"serde_with",
5862
"tokio",
59-
"tracing"
63+
"tracing",
6064
]
6165
neon = ["sp1-sdk?/neon"]
66+
foundry-verify = []

provers/sp1/driver/src/lib.rs

+14-79
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
#![cfg(feature = "enable")]
22
#![feature(iter_advance_by)]
33

4-
use once_cell::sync::Lazy;
54
use raiko_lib::{
65
input::{GuestInput, GuestOutput},
76
prover::{IdStore, IdWrite, Proof, ProofKey, Prover, ProverConfig, ProverError, ProverResult},
@@ -16,21 +15,15 @@ use sp1_sdk::{
1615
proto::network::{ProofMode, UnclaimReason},
1716
};
1817
use sp1_sdk::{HashableKey, ProverClient, SP1Stdin};
19-
use std::{
20-
borrow::BorrowMut,
21-
env, fs,
22-
path::{Path, PathBuf},
23-
};
24-
use tracing::{debug, info};
18+
use std::{borrow::BorrowMut, env};
19+
use tracing::info;
20+
21+
mod proof_verify;
22+
use proof_verify::remote_contract_verify::verify_sol_by_contract_call;
2523

2624
pub const ELF: &[u8] = include_bytes!("../../guest/elf/sp1-guest");
2725
const SP1_PROVER_CODE: u8 = 1;
28-
static FIXTURE_PATH: Lazy<PathBuf> =
29-
Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../contracts/src/fixtures/"));
30-
static CONTRACT_PATH: Lazy<PathBuf> =
31-
Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../contracts/src/exports/"));
3226

33-
pub static VERIFIER: Lazy<Result<PathBuf, ProverError>> = Lazy::new(init_verifier);
3427
#[serde_as]
3528
#[derive(Clone, Debug, Serialize, Deserialize)]
3629
pub struct Sp1Param {
@@ -164,12 +157,12 @@ impl Prover for Sp1Prover {
164157
.public_values
165158
.read::<[u8; 32]>();
166159
let fixture = RaikoProofFixture {
167-
vkey: vk.bytes32().to_string(),
168-
public_values: B256::from_slice(&pi_hash).to_string(),
169-
proof: reth_primitives::hex::encode_prefixed(&proof_bytes),
160+
vkey: vk.bytes32(),
161+
public_values: pi_hash.into(),
162+
proof: proof_bytes.clone(),
170163
};
171164

172-
verify_sol(&fixture)?;
165+
verify_sol_by_contract_call(&fixture).await?;
173166
time.stop_with("==> Verification complete");
174167
}
175168

@@ -232,66 +225,13 @@ fn get_env_mock() -> ProverMode {
232225
}
233226
}
234227

235-
fn init_verifier() -> Result<PathBuf, ProverError> {
236-
// In cargo run, Cargo sets the working directory to the root of the workspace
237-
let contract_path = &*CONTRACT_PATH;
238-
info!("Contract dir: {:?}", contract_path);
239-
let artifacts_dir = sp1_sdk::install::try_install_circuit_artifacts();
240-
// Create the destination directory if it doesn't exist
241-
fs::create_dir_all(&contract_path)?;
242-
243-
// Read the entries in the source directory
244-
for entry in fs::read_dir(artifacts_dir)? {
245-
let entry = entry?;
246-
let src = entry.path();
247-
248-
// Check if the entry is a file and ends with .sol
249-
if src.is_file() && src.extension().map(|s| s == "sol").unwrap_or(false) {
250-
let out = contract_path.join(src.file_name().unwrap());
251-
fs::copy(&src, &out)?;
252-
println!("Copied: {:?}", src.file_name().unwrap());
253-
}
254-
}
255-
Ok(contract_path.clone())
256-
}
257-
258228
/// A fixture that can be used to test the verification of SP1 zkVM proofs inside Solidity.
259-
#[derive(Debug, Clone, Serialize, Deserialize)]
229+
#[derive(Clone, Debug, Serialize, Deserialize)]
260230
#[serde(rename_all = "camelCase")]
261231
pub(crate) struct RaikoProofFixture {
262232
vkey: String,
263-
public_values: String,
264-
proof: String,
265-
}
266-
267-
fn verify_sol(fixture: &RaikoProofFixture) -> ProverResult<()> {
268-
assert!(VERIFIER.is_ok());
269-
debug!("===> Fixture: {:#?}", fixture);
270-
271-
// Save the fixture to a file.
272-
let fixture_path = &*FIXTURE_PATH;
273-
info!("Writing fixture to: {:?}", fixture_path);
274-
275-
if !fixture_path.exists() {
276-
std::fs::create_dir_all(fixture_path).map_err(|e| {
277-
ProverError::GuestError(format!("Failed to create fixture path: {}", e))
278-
})?;
279-
}
280-
std::fs::write(
281-
fixture_path.join("fixture.json"),
282-
serde_json::to_string_pretty(&fixture).unwrap(),
283-
)
284-
.map_err(|e| ProverError::GuestError(format!("Failed to write fixture: {}", e)))?;
285-
286-
let child = std::process::Command::new("forge")
287-
.arg("test")
288-
.current_dir(&*CONTRACT_PATH)
289-
.stdout(std::process::Stdio::inherit()) // Inherit the parent process' stdout
290-
.spawn();
291-
info!("Verification started {:?}", child);
292-
child.map_err(|e| ProverError::GuestError(format!("Failed to run forge: {}", e)))?;
293-
294-
Ok(())
233+
public_values: B256,
234+
proof: Vec<u8>,
295235
}
296236

297237
#[cfg(test)]
@@ -314,16 +254,11 @@ mod test {
314254
prover: Some(ProverMode::Network),
315255
verify: true,
316256
};
317-
let serialized = serde_json::to_value(&param).unwrap();
257+
let serialized = serde_json::to_value(param).unwrap();
318258
assert_eq!(json, serialized);
319259

320260
let deserialized: Sp1Param = serde_json::from_value(serialized).unwrap();
321-
println!("{:?} {:?}", json, deserialized);
322-
}
323-
324-
#[test]
325-
fn test_init_verifier() {
326-
VERIFIER.as_ref().expect("Failed to init verifier");
261+
println!("{json:?} {deserialized:?}");
327262
}
328263

329264
#[test]
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub(crate) mod forge_proof_verify;
2+
pub(crate) mod remote_contract_verify;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#![cfg(feature = "foundry-verify")]
2+
3+
use once_cell::sync::Lazy;
4+
use raiko_lib::prover::{ProverError, ProverResult};
5+
use std::{
6+
env, fs,
7+
path::{Path, PathBuf},
8+
};
9+
use tracing::{debug, info};
10+
11+
use crate::RaikoProofFixture;
12+
13+
static FIXTURE_PATH: Lazy<PathBuf> =
14+
Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../contracts/src/fixtures/"));
15+
static CONTRACT_PATH: Lazy<PathBuf> =
16+
Lazy::new(|| Path::new(env!("CARGO_MANIFEST_DIR")).join("../contracts/src/exports/"));
17+
18+
pub static VERIFIER: Lazy<Result<PathBuf, ProverError>> = Lazy::new(init_verifier);
19+
20+
fn init_verifier() -> Result<PathBuf, ProverError> {
21+
// In cargo run, Cargo sets the working directory to the root of the workspace
22+
let contract_path = &*CONTRACT_PATH;
23+
info!("Contract dir: {contract_path:?}");
24+
let artifacts_dir = sp1_sdk::install::try_install_circuit_artifacts();
25+
// Create the destination directory if it doesn't exist
26+
fs::create_dir_all(contract_path)?;
27+
28+
// Read the entries in the source directory
29+
for entry in fs::read_dir(artifacts_dir)? {
30+
let entry = entry?;
31+
let src = entry.path();
32+
33+
// Check if the entry is a file and ends with .sol
34+
if src.is_file() && src.extension().map(|s| s == "sol").unwrap_or(false) {
35+
let out = contract_path.join(src.file_name().unwrap());
36+
fs::copy(&src, &out)?;
37+
println!("Copied: {:?}", src.file_name().unwrap());
38+
}
39+
}
40+
Ok(contract_path.clone())
41+
}
42+
43+
/// verify the proof by using forge test, which involves downloading the whole sp1 sdk &
44+
/// starting a forge environment to run test.
45+
pub(crate) fn verify_sol_by_forge_test(fixture: &RaikoProofFixture) -> ProverResult<()> {
46+
assert!(VERIFIER.is_ok());
47+
debug!("===> Fixture: {fixture:#?}");
48+
49+
// Save the fixture to a file.
50+
let fixture_path = &*FIXTURE_PATH;
51+
info!("Writing fixture to: {fixture_path:?}");
52+
53+
if !fixture_path.exists() {
54+
std::fs::create_dir_all(fixture_path.clone())
55+
.map_err(|e| ProverError::GuestError(format!("Failed to create fixture path: {e}")))?;
56+
}
57+
std::fs::write(
58+
fixture_path.join("fixture.json"),
59+
serde_json::to_string_pretty(&fixture).unwrap(),
60+
)
61+
.map_err(|e| ProverError::GuestError(format!("Failed to write fixture: {e}")))?;
62+
63+
let child = std::process::Command::new("forge")
64+
.arg("test")
65+
.current_dir(&*CONTRACT_PATH)
66+
.stdout(std::process::Stdio::inherit()) // Inherit the parent process' stdout
67+
.spawn();
68+
info!("Verification started {:?}", child);
69+
child.map_err(|e| ProverError::GuestError(format!("Failed to run forge: {e}")))?;
70+
71+
Ok(())
72+
}
73+
74+
#[cfg(test)]
75+
mod test {
76+
use super::*;
77+
78+
#[test]
79+
fn test_init_verifier() {
80+
VERIFIER.as_ref().expect("Failed to init verifier");
81+
}
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use alloy_primitives::Address;
2+
use alloy_provider::ProviderBuilder;
3+
use alloy_sol_types::sol;
4+
use raiko_lib::prover::ProverResult;
5+
use reth_primitives::B256;
6+
use sp1_sdk::proto::network::twirp::url::Url;
7+
use std::{env, str::FromStr};
8+
use tracing::{error, info};
9+
10+
use crate::RaikoProofFixture;
11+
12+
sol!(
13+
#[sol(rpc)]
14+
#[allow(dead_code)]
15+
contract ISP1Verifier {
16+
#[derive(Debug)]
17+
function verifyProof(
18+
bytes32 programVKey,
19+
bytes calldata publicValues,
20+
bytes calldata proofBytes
21+
) external view;
22+
}
23+
);
24+
25+
/// using pre-deployed contract to verify the proof, the only problem is to double check the verification version.
26+
pub(crate) async fn verify_sol_by_contract_call(fixture: &RaikoProofFixture) -> ProverResult<()> {
27+
let sp1_verifier_rpc_url = env::var("SP1_VERIFIER_RPC_URL").expect("env SP1_VERIFIER_RPC_URL");
28+
let sp1_verifier_addr = {
29+
let addr = env::var("SP1_VERIFIER_ADDRESS").expect("env SP1_VERIFIER_RPC_URL");
30+
Address::from_str(&addr).unwrap()
31+
};
32+
33+
let provider = ProviderBuilder::new().on_http(Url::parse(&sp1_verifier_rpc_url).unwrap());
34+
let program_key: B256 = B256::from_str(&fixture.vkey).unwrap();
35+
let public_value = fixture.public_values;
36+
let proof_bytes = fixture.proof.clone();
37+
38+
info!(
39+
"verify sp1 proof with program key: {:?} public value: {:?} proof: {:?}",
40+
program_key,
41+
public_value,
42+
reth_primitives::hex::encode(&proof_bytes)
43+
);
44+
45+
let sp1_verifier = ISP1Verifier::new(sp1_verifier_addr, provider);
46+
let call_builder =
47+
sp1_verifier.verifyProof(program_key, public_value.into(), proof_bytes.into());
48+
let verify_call_res = call_builder.call().await;
49+
50+
if verify_call_res.is_ok() {
51+
info!("SP1 proof verified successfully using {sp1_verifier_addr:?}!");
52+
} else {
53+
error!("SP1 proof verification failed: {:?}!", verify_call_res);
54+
}
55+
56+
Ok(())
57+
}

provers/sp1/guest/elf/sp1-guest

-310 KB
Binary file not shown.

0 commit comments

Comments
 (0)