Skip to content

Commit

Permalink
Add cargo risczero verify (risc0#1775)
Browse files Browse the repository at this point in the history
This PR adds the command `verify` to `cargo risczero`. 

The user should provide the `image_id` of the guest as a positional
parameter, and specify as an argument either the path to a local receipt
saved in binary format using `bincode` or specify the Bonsai ID of the
receipt to download and verify.

The usage of the new command looks like the following: 

```
$ cargo-risczero risczero verify --help

Verifies if a receipt is valid

Usage: cargo risczero verify [OPTIONS] <--path <PATH>|--id <ID>> <IMAGE_ID>

Arguments:
  <IMAGE_ID>  The image ID to verify the receipt against

Options:
      --path <PATH>        The path to the receipt file
      --id <ID>            The Bonsai ID of the receipt to download and verify
      --api-url <API_URL>  API URL for Bonsai
      --api-key <API_KEY>  API key for Bonsai
  -h, --help               Print help
```

--- 

Closes risc0#1619
  • Loading branch information
Cardosaum authored May 7, 2024
1 parent d51ff37 commit 40e7d47
Show file tree
Hide file tree
Showing 9 changed files with 268 additions and 2 deletions.
65 changes: 65 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions risc0/cargo-risczero/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ doc = false

[dependencies]
anyhow = { version = "1.0", features = ["backtrace"] }
bincode = "1.3"
bonsai-sdk = { workspace = true }
cargo-generate = { version = "0.18", features = ["vendored-openssl"] }
cargo_metadata = { version = "0.18" }
Expand Down Expand Up @@ -44,6 +45,7 @@ tar = "0.4"
tempfile = "3"
text_io = "0.1.12"
tokio = { version = "1", features = ["rt"] }
tracing = { version = "0.1", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
xz = "0.1.0"
zip = { version = "0.6", optional = true }
Expand All @@ -67,3 +69,8 @@ experimental = [
]
metal = ["risc0-zkvm/metal"]
r0vm = ["dep:risc0-r0vm"]

[dev-dependencies]
assert_cmd = { version = "2.0", features = ["color", "color-auto"] }
predicates = { version = "3.1", features = ["regex"] }
rstest = "0.19"
2 changes: 2 additions & 0 deletions risc0/cargo-risczero/src/bin/cargo-risczero.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ fn main() -> Result<()> {
RisczeroCmd::Install(cmd) => cmd.run(),
RisczeroCmd::New(cmd) => cmd.run(),
RisczeroCmd::Deploy(cmd) => cmd.run(),
RisczeroCmd::Verify(cmd) => cmd.run(),
#[cfg(feature = "experimental")]
RisczeroCmd::BuildCrate(build) => build.run(BuildSubcommand::Build),
#[cfg(feature = "experimental")]
RisczeroCmd::Test(build) => build.run(BuildSubcommand::Test),
_ => unimplemented!("Command not implemented"),
}
}
1 change: 1 addition & 0 deletions risc0/cargo-risczero/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ pub mod build_toolchain;
pub mod deploy;
pub mod install;
pub mod new;
pub mod verify;
119 changes: 119 additions & 0 deletions risc0/cargo-risczero/src/commands/verify.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// 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.

use crate::utils::{self, ClientEnvs};
use anyhow::Result;
use bonsai_sdk::alpha::SessionId;
use clap::Parser;
use hex::FromHex;
use risc0_zkvm::{sha::Digest, Receipt};
use tracing::{debug, error, info, instrument, trace};

/// `cargo risczero verify`
#[derive(Parser, Debug, Clone, PartialEq, Eq)]
pub struct VerifyCommand {
/// Receipt source
#[command(flatten)]
source: Source,

/// The hex-encoded image ID to verify the receipt against.
image_id: String,

/// The client environment variables.
#[command(flatten)]
client: ClientEnvs,
}

#[derive(Parser, Debug, Clone, PartialEq, Eq)]
#[group(required = true, multiple = false)]
struct Source {
/// The path to the receipt file.
#[arg(group = "source", long)]
path: Option<String>,

/// The Bonsai ID of the receipt to download and verify.
#[arg(groups = ["source", "bonsai"], long)]
id: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
enum SourceType<'a> {
Path(&'a str),
Id(&'a str),
}

impl VerifyCommand {
#[instrument(skip(self))]
pub fn run(&self) -> Result<()> {
debug!("Running verify command");
let receipt = self.get_receipt()?;
let image_id = self.get_image_id()?;
let result = receipt.verify(image_id);
match result {
Ok(_) => {
info!("Receipt verified successfully");
println!("✅ Receipt is valid!");
}
Err(ref error) => {
error!(?error, "Receipt verification failed");
eprintln!("❌ Receipt is not valid");
}
}

Ok(result?)
}

fn get_image_id(&self) -> Result<Digest> {
trace!("Parsing image ID");
let image_id = Digest::from_hex(&self.image_id)?;
debug!(?image_id, "Parsed image ID");
Ok(image_id)
}

fn get_receipt(&self) -> Result<Receipt> {
trace!("Getting receipt");
let source = self.source();
let receipt = match source {
SourceType::Path(path) => {
let receipt_raw = std::fs::read(path)?;
parse_receipt(receipt_raw)?
}
SourceType::Id(id) => {
let id = SessionId::new(id.into());
let client = utils::get_client(&self.client)?;
let receipt_raw = client.receipt_download(&id)?;
parse_receipt(receipt_raw)?
}
};
debug!("Got receipt");

Ok(receipt)
}

fn source(&self) -> SourceType {
match (&self.source.path, &self.source.id) {
(Some(path), None) => SourceType::Path(path),
(None, Some(id)) => SourceType::Id(id),
(Some(_), Some(_)) => {
unreachable!("Both path and ID provided. Please provide only one.")
}
_ => unreachable!("No path or ID provided. Please provide one."),
}
}
}

fn parse_receipt(receipt_raw: Vec<u8>) -> Result<Receipt> {
let receipt: Receipt = bincode::deserialize(&receipt_raw)?;
Ok(receipt)
}
5 changes: 4 additions & 1 deletion risc0/cargo-risczero/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use clap::{Parser, Subcommand};
use self::commands::build::BuildCommand;
use self::commands::{
build_guest::BuildGuest, build_toolchain::BuildToolchain, deploy::DeployCommand,
install::Install, new::NewCommand,
install::Install, new::NewCommand, verify::VerifyCommand,
};

#[derive(Parser)]
Expand All @@ -48,6 +48,7 @@ pub struct Risczero {
pub command: RisczeroCmd,
}

#[non_exhaustive]
#[derive(Subcommand)]
/// Primary commands of `cargo risczero`.
pub enum RisczeroCmd {
Expand All @@ -61,6 +62,8 @@ pub enum RisczeroCmd {
New(NewCommand),
/// Uploads the guest code to Bonsai.
Deploy(DeployCommand),
/// Verifies if a receipt is valid.
Verify(VerifyCommand),
/// Build a crate for RISC Zero.
#[cfg(feature = "experimental")]
BuildCrate(BuildCommand),
Expand Down
3 changes: 2 additions & 1 deletion risc0/cargo-risczero/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ impl fmt::Display for ProcessError {

impl std::error::Error for ProcessError {}

#[derive(Debug, Default, Args)]
#[derive(Debug, Clone, Default, Args, PartialEq, Eq)]
#[group(multiple = false)]
pub struct ClientEnvs {
/// API URL for Bonsai.
#[arg(long)]
Expand Down
Binary file added risc0/cargo-risczero/tests/data/receipt.bin
Binary file not shown.
Loading

0 comments on commit 40e7d47

Please sign in to comment.