Skip to content

Commit

Permalink
Merge pull request #1 from tolak/local-prover
Browse files Browse the repository at this point in the history
Local prover
  • Loading branch information
tolak authored Mar 26, 2024
2 parents 35bf034 + 10d3045 commit 160d92f
Show file tree
Hide file tree
Showing 9 changed files with 682 additions and 210 deletions.
460 changes: 416 additions & 44 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ methods = { path = "./methods" }
risc0-build = { git = "https://github.com/risc0/risc0", rev = "7f731662", features = ["docker"] }
risc0-build-ethereum = { git = "https://github.com/risc0/risc0-ethereum", branch = "release-0.7" }
risc0-ethereum-contracts = { git = "https://github.com/risc0/risc0-ethereum", branch = "release-0.7" }
risc0-zkvm = { version = "0.20", default-features = false }
risc0-zkp = { version = "0.20", default-features = false }
risc0-zkvm = { version = "0.21.0", default-features = false }
risc0-zkp = { version = "0.21.0", default-features = false }
risc0-groth16 = { version = "0.21.0", default-features = false, features = ["prove"] }
serde = { version = "1.0", features = ["derive", "std"] }

[profile.release]
Expand Down
3 changes: 2 additions & 1 deletion apps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ hex = { workspace = true }
log = { workspace = true }
methods = { workspace = true }
risc0-ethereum-contracts = { workspace = true }
risc0-zkvm = { workspace = true }
risc0-zkvm = { workspace = true, features = ["prove"] }
risc0-groth16 = { workspace = true, features = ["prove"] }
serde = { workspace = true }
tokio = { version = "1.35", features = ["full"] }
48 changes: 46 additions & 2 deletions apps/src/bin/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,52 @@
use alloy_primitives::U256;
use alloy_sol_types::{sol, SolInterface, SolValue};
use anyhow::{Context, Result};
use apps::{BonsaiProver, TxSender};
use apps::local_prover::LocalProver;
use clap::Parser;
use ethers::prelude::*;
use methods::IS_EVEN_ELF;

/// Wrapper of a `SignerMiddleware` client to send transactions to the given
/// contract's `Address`.
pub struct TxSender {
chain_id: u64,
client: SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>,
contract: Address,
}

impl TxSender {
/// Creates a new `TxSender`.
pub fn new(chain_id: u64, rpc_url: &str, private_key: &str, contract: &str) -> Result<Self> {
let provider = Provider::<Http>::try_from(rpc_url)?;
let wallet: LocalWallet = private_key.parse::<LocalWallet>()?.with_chain_id(chain_id);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
let contract = contract.parse::<Address>()?;

Ok(TxSender {
chain_id,
client,
contract,
})
}

/// Send a transaction with the given calldata.
pub async fn send(&self, calldata: Vec<u8>) -> Result<Option<TransactionReceipt>> {
let tx = TransactionRequest::new()
.chain_id(self.chain_id)
.to(self.contract)
.from(self.client.address())
.data(calldata);

log::info!("Transaction request: {:?}", &tx);

let tx = self.client.send_transaction(tx, None).await?.await?;

log::info!("Transaction receipt: {:?}", &tx);

Ok(tx)
}
}

// `IEvenNumber` interface automatically generated via the alloy `sol!` macro.
sol! {
interface IEvenNumber {
Expand Down Expand Up @@ -71,8 +113,10 @@ fn main() -> Result<()> {
// code expects.
let input = args.input.abi_encode();

log::info!("Start to generate proof for intput: {:?}", &input);

// Send an off-chain proof request to the Bonsai proving service.
let (journal, post_state_digest, seal) = BonsaiProver::prove(IS_EVEN_ELF, &input)?;
let (journal, post_state_digest, seal) = LocalProver::prove(IS_EVEN_ELF, &input)?;

// Decode the journal. Must match what was written in the guest with
// `env::commit_slice`.
Expand Down
118 changes: 118 additions & 0 deletions apps/src/bonsai_prover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// 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.

// The following library provides utility functions to help with sending
// off-chain proof requests to the Bonsai proving service and publish the
// received proofs directly to a deployed app contract on Ethereum.
//
// Please note that both `risc0_zkvm` and `bonsai_sdk` crates are still
// under active development. As such, this library might change to adapt to
// the upstream changes.

use std::time::Duration;

use alloy_primitives::FixedBytes;
use anyhow::{Context, Result};
use bonsai_sdk::alpha as bonsai_sdk;
use risc0_ethereum_contracts::groth16::Seal;
use risc0_zkvm::{compute_image_id, Receipt};

/// An implementation of a Prover that runs on Bonsai.
pub struct BonsaiProver {}
impl BonsaiProver {
/// Generates a snark proof as a triplet (`Vec<u8>`, `FixedBytes<32>`,
/// `Vec<u8>) for the given elf and input.
pub fn prove(elf: &[u8], input: &[u8]) -> Result<(Vec<u8>, FixedBytes<32>, Vec<u8>)> {
let client = bonsai_sdk::Client::from_env(risc0_zkvm::VERSION)?;

// Compute the image_id, then upload the ELF with the image_id as its key.
let image_id = compute_image_id(elf)?;
let image_id_hex = image_id.to_string();
client.upload_img(&image_id_hex, elf.to_vec())?;
log::info!("Image ID: 0x{}", image_id_hex);

// Prepare input data and upload it.
let input_id = client.upload_input(input.to_vec())?;

// Start a session running the prover.
let session = client.create_session(image_id_hex, input_id, vec![])?;
log::info!("Created session: {}", session.uuid);
let _receipt = loop {
let res = session.status(&client)?;
if res.status == "RUNNING" {
log::info!(
"Current status: {} - state: {} - continue polling...",
res.status,
res.state.unwrap_or_default()
);
std::thread::sleep(Duration::from_secs(15));
continue;
}
if res.status == "SUCCEEDED" {
// Download the receipt, containing the output.
let receipt_url = res
.receipt_url
.context("API error, missing receipt on completed session")?;

let receipt_buf = client.download(&receipt_url)?;
let receipt: Receipt = bincode::deserialize(&receipt_buf)?;

break receipt;
}

panic!(
"Workflow exited: {} - | err: {}",
res.status,
res.error_msg.unwrap_or_default()
);
};

// Fetch the snark.
let snark_session = client.create_snark(session.uuid)?;
log::info!("Created snark session: {}", snark_session.uuid);
let snark_receipt = loop {
let res = snark_session.status(&client)?;
match res.status.as_str() {
"RUNNING" => {
log::info!("Current status: {} - continue polling...", res.status,);
std::thread::sleep(Duration::from_secs(15));
continue;
}
"SUCCEEDED" => {
break res.output.context("No snark generated :(")?;
}
_ => {
panic!(
"Workflow exited: {} err: {}",
res.status,
res.error_msg.unwrap_or_default()
);
}
}
};

let snark = snark_receipt.snark;
log::debug!("Snark proof!: {snark:?}");

let seal = Seal::abi_encode(snark).context("Read seal")?;
let post_state_digest: FixedBytes<32> = snark_receipt
.post_state_digest
.as_slice()
.try_into()
.context("Read post_state_digest")?;
let journal = snark_receipt.journal;

Ok((journal, post_state_digest, seal))
}
}
162 changes: 2 additions & 160 deletions apps/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,160 +1,2 @@
// 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.

// The following library provides utility functions to help with sending
// off-chain proof requests to the Bonsai proving service and publish the
// received proofs directly to a deployed app contract on Ethereum.
//
// Please note that both `risc0_zkvm` and `bonsai_sdk` crates are still
// under active development. As such, this library might change to adapt to
// the upstream changes.

use std::time::Duration;

use alloy_primitives::FixedBytes;
use anyhow::{Context, Result};
use bonsai_sdk::alpha as bonsai_sdk;
use ethers::prelude::*;
use risc0_ethereum_contracts::groth16::Seal;
use risc0_zkvm::{compute_image_id, Receipt};

/// Wrapper of a `SignerMiddleware` client to send transactions to the given
/// contract's `Address`.
pub struct TxSender {
chain_id: u64,
client: SignerMiddleware<Provider<Http>, Wallet<k256::ecdsa::SigningKey>>,
contract: Address,
}

impl TxSender {
/// Creates a new `TxSender`.
pub fn new(chain_id: u64, rpc_url: &str, private_key: &str, contract: &str) -> Result<Self> {
let provider = Provider::<Http>::try_from(rpc_url)?;
let wallet: LocalWallet = private_key.parse::<LocalWallet>()?.with_chain_id(chain_id);
let client = SignerMiddleware::new(provider.clone(), wallet.clone());
let contract = contract.parse::<Address>()?;

Ok(TxSender {
chain_id,
client,
contract,
})
}

/// Send a transaction with the given calldata.
pub async fn send(&self, calldata: Vec<u8>) -> Result<Option<TransactionReceipt>> {
let tx = TransactionRequest::new()
.chain_id(self.chain_id)
.to(self.contract)
.from(self.client.address())
.data(calldata);

log::info!("Transaction request: {:?}", &tx);

let tx = self.client.send_transaction(tx, None).await?.await?;

log::info!("Transaction receipt: {:?}", &tx);

Ok(tx)
}
}

/// An implementation of a Prover that runs on Bonsai.
pub struct BonsaiProver {}
impl BonsaiProver {
/// Generates a snark proof as a triplet (`Vec<u8>`, `FixedBytes<32>`,
/// `Vec<u8>) for the given elf and input.
pub fn prove(elf: &[u8], input: &[u8]) -> Result<(Vec<u8>, FixedBytes<32>, Vec<u8>)> {
let client = bonsai_sdk::Client::from_env(risc0_zkvm::VERSION)?;

// Compute the image_id, then upload the ELF with the image_id as its key.
let image_id = compute_image_id(elf)?;
let image_id_hex = image_id.to_string();
client.upload_img(&image_id_hex, elf.to_vec())?;
log::info!("Image ID: 0x{}", image_id_hex);

// Prepare input data and upload it.
let input_id = client.upload_input(input.to_vec())?;

// Start a session running the prover.
let session = client.create_session(image_id_hex, input_id, vec![])?;
log::info!("Created session: {}", session.uuid);
let _receipt = loop {
let res = session.status(&client)?;
if res.status == "RUNNING" {
log::info!(
"Current status: {} - state: {} - continue polling...",
res.status,
res.state.unwrap_or_default()
);
std::thread::sleep(Duration::from_secs(15));
continue;
}
if res.status == "SUCCEEDED" {
// Download the receipt, containing the output.
let receipt_url = res
.receipt_url
.context("API error, missing receipt on completed session")?;

let receipt_buf = client.download(&receipt_url)?;
let receipt: Receipt = bincode::deserialize(&receipt_buf)?;

break receipt;
}

panic!(
"Workflow exited: {} - | err: {}",
res.status,
res.error_msg.unwrap_or_default()
);
};

// Fetch the snark.
let snark_session = client.create_snark(session.uuid)?;
log::info!("Created snark session: {}", snark_session.uuid);
let snark_receipt = loop {
let res = snark_session.status(&client)?;
match res.status.as_str() {
"RUNNING" => {
log::info!("Current status: {} - continue polling...", res.status,);
std::thread::sleep(Duration::from_secs(15));
continue;
}
"SUCCEEDED" => {
break res.output.context("No snark generated :(")?;
}
_ => {
panic!(
"Workflow exited: {} err: {}",
res.status,
res.error_msg.unwrap_or_default()
);
}
}
};

let snark = snark_receipt.snark;
log::debug!("Snark proof!: {snark:?}");

let seal = Seal::abi_encode(snark).context("Read seal")?;
let post_state_digest: FixedBytes<32> = snark_receipt
.post_state_digest
.as_slice()
.try_into()
.context("Read post_state_digest")?;
let journal = snark_receipt.journal;

Ok((journal, post_state_digest, seal))
}
}
pub mod bonsai_prover;
pub mod local_prover;
Loading

0 comments on commit 160d92f

Please sign in to comment.