Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WEB3-213: add SteelVerifier to verify Steel commitments in the guest #319

Merged
merged 29 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9c1c0a0
add commitment verifier
Wollac Nov 4, 2024
f5fbc38
remove unused imports
Wollac Nov 6, 2024
a806ab5
Merge branch 'main' into feat/verifier
Wollac Nov 6, 2024
44f6a48
cleanups
Wollac Nov 7, 2024
69526cf
Merge branch 'main' into feat/verifier
Wollac Nov 7, 2024
d0f94c5
support envs without accessed accounts
Wollac Nov 7, 2024
8c50d20
documentation
Wollac Nov 7, 2024
eb3d803
cleanup beacon roots
Wollac Nov 7, 2024
58d0098
Merge branch 'main' into feat/verifier
Wollac Nov 7, 2024
7144a7d
minor cleanups
Wollac Nov 7, 2024
ffe979a
Merge branch 'main' into feat/verifier
Wollac Nov 8, 2024
58c1fb2
Add SteelVerifier example (#327)
Wollac Nov 20, 2024
fcd494e
Merge branch 'main' into feat/verifier
Wollac Nov 20, 2024
5599b10
return commitment for Envs with BlockHeaderCommit
Wollac Dec 16, 2024
157a96d
generalize also the block input
Wollac Dec 16, 2024
effaa4d
Merge branch 'main' into feat/verifier
Wollac Dec 16, 2024
e79e83b
Revert "generalize also the block input"
Wollac Dec 16, 2024
85709fc
return commitment for Envs without commitment
Wollac Dec 16, 2024
faa1a54
various fixes
Wollac Dec 16, 2024
984ecea
mark verifier as unstable
Wollac Dec 16, 2024
f7aad34
readd alloy
Wollac Dec 16, 2024
9322214
fix warnings
Wollac Dec 16, 2024
b048895
fix feature name
Wollac Dec 16, 2024
813a097
update CHANGELOG.md
Wollac Dec 16, 2024
7e74a7b
Apply suggestions from code review
Wollac Jan 6, 2025
489096c
Merge branch 'main' into feat/verifier
Wollac Jan 8, 2025
18070b4
address review comment
Wollac Jan 8, 2025
fa53c37
update license header
Wollac Jan 8, 2025
04e03a6
Merge branch 'main' into feat/verifier
Wollac Jan 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion examples/erc20-counter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ risc0-build = { git = "https://github.com/risc0/risc0", branch = "main", feature
risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false }
risc0-zkp = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false }

alloy = { version = "0.7", features = ["full"] }
alloy-primitives = { version = "0.8", features = ["rlp", "serde", "std"] }
alloy-sol-types = { version = "0.8" }
anyhow = { version = "1.0.75" }
Expand Down
1 change: 0 additions & 1 deletion examples/erc20-counter/apps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ version = { workspace = true }
edition = { workspace = true }

[dependencies]
alloy = { workspace = true }
alloy-primitives = { workspace = true }
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] }
Expand Down
17 changes: 9 additions & 8 deletions examples/erc20-counter/apps/src/bin/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@
// to the Bonsai proving service and publish the received proofs directly
// to your deployed app contract.

use alloy::{
network::EthereumWallet,
providers::ProviderBuilder,
signers::local::PrivateKeySigner,
sol_types::{SolCall, SolValue},
};
use alloy_primitives::{Address, U256};
use anyhow::{ensure, Context, Result};
use clap::Parser;
use erc20_counter_methods::{BALANCE_OF_ELF, BALANCE_OF_ID};
use risc0_ethereum_contracts::encode_seal;
use risc0_steel::alloy::{
network::EthereumWallet,
providers::ProviderBuilder,
signers::local::PrivateKeySigner,
sol,
sol_types::{SolCall, SolValue},
};
use risc0_steel::{
ethereum::{EthEvmEnv, ETH_SEPOLIA_CHAIN_SPEC},
host::BlockNumberOrTag,
Expand All @@ -37,7 +38,7 @@ use tokio::task;
use tracing_subscriber::EnvFilter;
use url::Url;

alloy::sol! {
sol! {
/// Interface to be called by the guest.
interface IERC20 {
function balanceOf(address account) external view returns (uint);
Expand All @@ -50,7 +51,7 @@ alloy::sol! {
}
}

alloy::sol!(
sol!(
#[sol(rpc, all_derives)]
"../contracts/src/ICounter.sol"
);
Expand Down
6 changes: 4 additions & 2 deletions examples/op/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ debug = 1
lto = true

[workspace.dependencies]
risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main" }
risc0-build = { git = "https://github.com/risc0/risc0", branch = "main" }
risc0-op-steel = { path = "../../op-steel" }
risc0-steel = { path = "../../steel" }
risc0-ethereum-contracts = { path = "../../contracts" }
examples-common = { path = "common" }

risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main" }
risc0-build = { git = "https://github.com/risc0/risc0", branch = "main" }

alloy = { version = "0.7" }
3 changes: 1 addition & 2 deletions examples/token-stats/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ members = ["core", "host", "methods"]

[workspace.dependencies]
# Intra-workspace dependencies
risc0-steel = { path = "../../steel" }
risc0-steel = { path = "../../steel", features = ["unstable-verifier"] }

# risc0 monorepo dependencies.
risc0-build = { git = "https://github.com/risc0/risc0", branch = "main" }
Expand All @@ -21,7 +21,6 @@ log = "0.4"
token-stats-core = { path = "core" }
token-stats-methods = { path = "methods" }
once_cell = "1.19"
rlp = "0.5.2"
serde = "1.0"
thiserror = "2.0"
tokio = { version = "1.35", features = ["full"] }
Expand Down
69 changes: 61 additions & 8 deletions examples/token-stats/host/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,26 @@ use alloy_sol_types::{SolCall, SolValue};
use anyhow::{Context, Result};
use clap::Parser;
use risc0_steel::{
alloy::providers::{Provider, ProviderBuilder},
ethereum::{EthEvmEnv, ETH_MAINNET_CHAIN_SPEC},
Contract,
Contract, SteelVerifier,
};
use risc0_zkvm::{default_executor, ExecutorEnv};
use token_stats_core::{APRCommitment, CometMainInterface, CONTRACT};
use token_stats_methods::TOKEN_STATS_ELF;
use tracing_subscriber::EnvFilter;
use url::Url;

// Simple program to show the use of Ethereum contract data inside the guest.
#[derive(Parser, Debug)]
#[command(about, long_about = None)]
struct Args {
/// URL of the RPC endpoint
#[arg(short, long, env = "RPC_URL")]
#[arg(long, env)]
rpc_url: Url,

/// Beacon API endpoint URL
#[clap(long, env)]
beacon_api_url: Url,
}

#[tokio::main]
Expand All @@ -43,8 +47,17 @@ async fn main() -> Result<()> {
// Parse the command line arguments.
let args = Args::parse();

// Create an EVM environment from an RPC endpoint defaulting to the latest block.
let mut env = EthEvmEnv::builder().rpc(args.rpc_url).build().await?;
// Query the latest block number.
let provider = ProviderBuilder::new().on_http(args.rpc_url);
let latest = provider.get_block_number().await?;

// Create an EVM environment for that provider and about 12h (3600 blocks) ago.
let mut env = EthEvmEnv::builder()
.provider(provider.clone())
.block_number(latest - 3600)
.beacon_api(args.beacon_api_url)
.build()
.await?;
// The `with_chain_spec` method is used to specify the chain configuration.
env = env.with_chain_spec(&ETH_MAINNET_CHAIN_SPEC);

Expand Down Expand Up @@ -74,13 +87,53 @@ async fn main() -> Result<()> {
rate
);

// Finally, construct the input from the environment.
let input = env.into_input().await?;
// Construct the commitment and input from the environment representing the state 12h ago.
let commitment_input1 = env.commitment();
let input1 = env.into_input().await?;

// Create another EVM environment for that provider defaulting to the latest block.
let mut env = EthEvmEnv::builder().provider(provider).build().await?;
env = env.with_chain_spec(&ETH_MAINNET_CHAIN_SPEC);

// Preflight the verification of the commitment of the previous input.
SteelVerifier::preflight(&mut env)
.verify(&commitment_input1)
.await?;

// Preflight the actual contract calls.
let mut contract = Contract::preflight(CONTRACT, &mut env);
let utilization = contract
.call_builder(&CometMainInterface::getUtilizationCall {})
.call()
.await?
._0;
println!(
"Call {} Function on {:#} returns: {}",
CometMainInterface::getUtilizationCall::SIGNATURE,
CONTRACT,
utilization
);
let rate = contract
.call_builder(&CometMainInterface::getSupplyRateCall { utilization })
.call()
.await?
._0;
println!(
"Call {} Function on {:#} returns: {}",
CometMainInterface::getSupplyRateCall::SIGNATURE,
CONTRACT,
rate
);

// Finally, construct the second input from the environment representing the latest state.
let input2 = env.into_input().await?;

println!("Running the guest with the constructed input:");
let session_info = {
let env = ExecutorEnv::builder()
.write(&input)
.write(&input1)
.unwrap()
.write(&input2)
.unwrap()
.build()
.context("failed to build executor env")?;
Expand Down
2 changes: 1 addition & 1 deletion examples/token-stats/methods/guest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ edition = "2021"

[dependencies]
alloy-sol-types = { version = "0.8" }
risc0-steel = { path = "../../../../steel" }
risc0-steel = { path = "../../../../steel", features = ["unstable-verifier"] }
risc0-zkvm = { git = "https://github.com/risc0/risc0", branch = "main", default-features = false, features = ["std"] }
token-stats-core = { path = "../../core" }

Expand Down
42 changes: 29 additions & 13 deletions examples/token-stats/methods/guest/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,46 @@
use alloy_sol_types::SolValue;
use risc0_steel::{
ethereum::{EthEvmInput, ETH_MAINNET_CHAIN_SPEC},
Contract,
Contract, SteelVerifier,
};
use risc0_zkvm::guest::env;
use token_stats_core::{APRCommitment, CometMainInterface, CONTRACT};

const SECONDS_PER_YEAR: u64 = 60 * 60 * 24 * 365;

fn main() {
// Read the input from the guest environment.
// Read the first input from the guest environment. It corresponds to the older EVM state.
let input: EthEvmInput = env::read();

// Converts the input into a `EvmEnv` for execution. The `with_chain_spec` method is used
// to specify the chain configuration. It checks that the state matches the state root in the
// header provided in the input.
let env = input.into_env().with_chain_spec(&ETH_MAINNET_CHAIN_SPEC);
// Converts the input into a `EvmEnv` for execution.
let env_prev = input.into_env().with_chain_spec(&ETH_MAINNET_CHAIN_SPEC);

// Execute the view calls; it returns the result in the type generated by the `sol!` macro.
let contract = Contract::new(CONTRACT, &env);
// Execute the view calls on the older EVM state.
let contract = Contract::new(CONTRACT, &env_prev);
let utilization = contract
.call_builder(&CometMainInterface::getUtilizationCall {})
.call()
._0;
let supply_rate = contract
let supply_rate_prev = contract
.call_builder(&CometMainInterface::getSupplyRateCall { utilization })
.call()
._0;

// Prepare the second `EvmEnv` for execution. It corresponds to the recent EVM state.
let input: EthEvmInput = env::read();
let env_cur = input.into_env().with_chain_spec(&ETH_MAINNET_CHAIN_SPEC);

// Verify that the older EVM state is valid wrt the recent EVM state.
// We initialize the SteelVerifier with the recent state, to check the previous commitment.
SteelVerifier::new(&env_cur).verify(env_prev.commitment());

// Execute the view calls also on the recent EVM state.
let contract = Contract::new(CONTRACT, &env_cur);
let utilization = contract
.call_builder(&CometMainInterface::getUtilizationCall {})
.call()
._0;
let supply_rate_cur = contract
.call_builder(&CometMainInterface::getSupplyRateCall { utilization })
.call()
._0;
Expand All @@ -48,13 +65,12 @@ fn main() {
// Supply Rate = getSupplyRate(Utilization)
// Supply APR = Supply Rate / (10 ^ 18) * Seconds Per Year * 100
//
// And this is calculating: Supply Rate * Seconds Per Year, to avoid float calculations for
// precision.
let annual_supply_rate = supply_rate * SECONDS_PER_YEAR;
// Compute the average APR, by computing the average over both states.
let annual_supply_rate = (supply_rate_prev + supply_rate_cur) * SECONDS_PER_YEAR / 2;

// This commits the APR at current utilization rate for this given block.
let journal = APRCommitment {
commitment: env.into_commitment(),
commitment: env_cur.into_commitment(),
annualSupplyRate: annual_supply_rate,
};
env::commit_slice(&journal.abi_encode());
Expand Down
4 changes: 4 additions & 0 deletions steel/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### ⚡️ Features

- Introduce the `SteelVerifier`, which acts as a built-in Steel `Contract` to verify Steel commitments. It is used like any other `Contract`, during the preflight step and in the guest. This functionality is currently marked unstable and must be enabled using the `unstable-verifier` feature.

## [1.2.0](https://github.com/risc0/risc0-ethereum/releases/tag/v1.2.0)

### ⚡️ Features
Expand Down
5 changes: 3 additions & 2 deletions steel/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
alloy = { workspace = true, optional = true, features = ["eips", "network", "provider-http", "rpc-types"] }
alloy = { workspace = true, optional = true, features = ["full"] }
alloy-consensus = { workspace = true }
alloy-primitives = { workspace = true, features = ["rlp", "serde"] }
alloy-rlp = { workspace = true }
Expand All @@ -33,7 +33,7 @@ tokio = { workspace = true, optional = true }
url = { workspace = true, optional = true }

[dev-dependencies]
alloy = { workspace = true, features = ["contract", "node-bindings"] }
alloy = { workspace = true, features = ["node-bindings"] }
alloy-trie = { workspace = true }
bincode = { workspace = true }
risc0-steel = { path = ".", features = ["host"] }
Expand All @@ -52,3 +52,4 @@ host = [
"dep:url",
]
unstable-history = []
unstable-verifier = []
20 changes: 17 additions & 3 deletions steel/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
// limitations under the License.

use crate::{
config::ChainSpec, state::StateDb, Commitment, CommitmentVersion, EvmBlockHeader, EvmEnv,
GuestEvmEnv, MerkleTrie,
config::ChainSpec, state::StateDb, BlockHeaderCommit, Commitment, CommitmentVersion,
EvmBlockHeader, EvmEnv, GuestEvmEnv, MerkleTrie,
};
use ::serde::{Deserialize, Serialize};
use alloy_primitives::{map::HashMap, Bytes};
use alloy_primitives::{map::HashMap, Bytes, Sealed, B256};

/// Input committing to the corresponding execution block hash.
#[derive(Clone, Serialize, Deserialize)]
Expand All @@ -29,6 +29,20 @@ pub struct BlockInput<H> {
ancestors: Vec<H>,
}

/// Implement [BlockHeaderCommit] for the unit type.
/// This makes it possible to treat an `HostEvmEnv<D, H, ()>`, which is used for the [BlockInput]
/// in the same way as any other `HostEvmEnv<D, H, BlockHeaderCommit>`.
impl<H: EvmBlockHeader> BlockHeaderCommit<H> for () {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels very strange to implement a trait for an empty tuple, but here it could actually be kind of intuitive...

Comment on lines +32 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could use the following empty-type instead of unit-type. This is more clear and explicit, but I think implementing it on () also makes sense. Up to you.

Suggested change
/// Implement [BlockHeaderCommit] for the unit type.
/// This makes it possible to treat an `HostEvmEnv<D, H, ()>`, which is used for the [BlockInput]
/// in the same way as any other `HostEvmEnv<D, H, BlockHeaderCommit>`.
impl<H: EvmBlockHeader> BlockHeaderCommit<H> for () {
struct DirectCommit;
/// Implement [BlockHeaderCommit] for the unit type.
/// This makes it possible to treat an `HostEvmEnv<D, H, DirectCommit>`, which is used for the [BlockInput]
/// in the same way as any other `HostEvmEnv<D, H, BlockHeaderCommit>`.
impl<H: EvmBlockHeader> BlockHeaderCommit<H> for DirectCommit {

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would technically be a breaking change, and testing it led to quite a few required changes in op-steel. So I kept the uint type.

fn commit(self, header: &Sealed<H>, config_id: B256) -> Commitment {
Commitment::new(
CommitmentVersion::Block as u16,
header.number(),
header.seal(),
config_id,
)
}
}

impl<H: EvmBlockHeader> BlockInput<H> {
/// Converts the input into a [EvmEnv] for verifiable state access in the guest.
pub fn into_env(self) -> GuestEvmEnv<H> {
Expand Down
Loading
Loading