Skip to content

Commit

Permalink
Expand examples in documentation (risc0#393)
Browse files Browse the repository at this point in the history
* WIP

* Format

* Re-add semantic newline

* Fix mismatched initial hash data

* WIP Receipt

* Finish Receipt module examples

* Add examples to prover Rust docs

* Manually flatten fake journal

* Revise `expect` text

Co-authored-by: Frank Laub <[email protected]>

* Format/wordsmith

Co-authored-by: Frank Laub <[email protected]>

* Document Prover::cycles

* Revise `run_with_hal` docs

* Document `default_hal`

* Clarify language documenting `Prover::run`

Co-authored-by: Frank Laub <[email protected]>

* Use more idiomatic array init

Co-authored-by: Frank Laub <[email protected]>

* Revise add_input_u8_slice docs

Co-authored-by: Frank Laub <[email protected]>

* Format & extend revisions to add_input_u8_slice

* Format

* Fully qualify to_vec in example

* Discuss Err in functions not expects

* Format

* Add info on get_output_u32_vec Err

* Add `receipt` module summary

* Document `insecure_skip_seal`

* Replace expect with ? in receipt docs

* Revise receipt module docs

* Document receipt verify functions

* Document `Receipt::new`

---------

Co-authored-by: Frank Laub <[email protected]>
  • Loading branch information
tzerrell and flaub authored Mar 2, 2023
1 parent 6bca151 commit afacb28
Show file tree
Hide file tree
Showing 3 changed files with 290 additions and 3 deletions.
117 changes: 117 additions & 0 deletions risc0/zkvm/src/prove/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@
// limitations under the License.

//! Run the zkVM guest and prove its results
//!
//! # Usage
//! The primary use of this module is to provably run a zkVM guest by use of a
//! [Prover]. See the [Prover] documentation for more detailed usage
//! information.
//!
//! ```ignore
//! // In real code, the ELF & Image ID would be generated by `risc0-build` scripts for the guest code
//! use methods::{EXAMPLE_ELF, EXAMPLE_ID};
//! use risc0_zkvm::Prover;
//!
//! let mut prover = Prover::new(&EXAMPLE_ELF, EXAMPLE_ID)?;
//! prover.add_input_u32_slice(&to_vec(&input)?);
//! let receipt = prover.run()?;
//! ```
mod exec;
pub mod io;
Expand Down Expand Up @@ -127,10 +142,49 @@ impl<'a> Default for ProverOpts<'a> {
}

/// Manages communication with and execution of a zkVM [Program]
///
/// # Usage
/// A [Prover] is constructed from the ELF code and an Image ID generated from
/// the guest code to be proven (see
/// [risc0_build](https://docs.rs/risc0-build/latest/risc0_build/) for more
/// information about how these are generated). Use [Prover::new] if you want
/// the default [ProverOpts], or [Prover::new_with_opts] to use custom options.
/// ```ignore
/// // In real code, the ELF & Image ID would be generated by risc0 build scripts from guest code
/// use methods::{EXAMPLE_ELF, EXAMPLE_ID};
/// use risc0_zkvm::Prover;
///
/// let mut prover = Prover::new(&EXAMPLE_ELF, EXAMPLE_ID)?;
/// ```
/// Provers should essentially always be mutable so that their [Prover::run]
/// method may be called.
///
/// Input data can be passed to the Prover with [Prover::add_input_u32_slice]
/// (or [Prover::add_input_u8_slice]). After all inputs have been added, call
/// [Prover::run] to execute the guest code and produce a [Receipt] proving
/// execution.
/// ```ignore
/// prover.add_input_u32_slice(&risc0_zkvm::serde::to_vec(&input)?);
/// let receipt = prover.run()?;
/// ```
/// After running the prover, publicly proven results can be accessed from the
/// [Receipt], while private outputs can be accessed using
/// [Prover::get_output_u32_vec] (or [Prover::get_output_u8_slice]):
/// ```ignore
/// let receipt = prover.run()?;
/// let slice = prover.get_output_u32_vec()?;
/// let private_output: OutputType = risc0_zkvm::serde::from_slice(&slice)?;
/// let proven_result: ResultType = risc0_zkvm::serde::from_slice(&receipt.journal)?;
/// ```
pub struct Prover<'a> {
elf: Program,
inner: ProverImpl<'a>,
image_id: Digest,
/// How many cycles executing the guest took.
///
/// Initialized to 0 by [Prover::new], then computed when [Prover::run] is
/// called. Note that this is privately shared with the host; it is not
/// present in the [Receipt].
pub cycles: usize,
}

Expand All @@ -157,6 +211,20 @@ cfg_if::cfg_if! {
use risc0_circuit_rv32im::{CircuitImpl, cpu::CpuEvalCheck};
use risc0_zkp::hal::cpu::BabyBearSha256CpuHal;

/// Returns the default HAL for the RISC Zero circuit
///
/// RISC Zero uses a
/// [HAL](https://docs.rs/risc0-zkp/latest/risc0_zkp/hal/index.html)
/// (Hardware Abstraction Layer) to interface with the zkVM circuit.
/// This function returns the default HAL for the selected `risc0-zkvm`
/// features. It also returns the associated
/// [EvalCheck](https://docs.rs/risc0-zkp/latest/risc0_zkp/hal/trait.EvalCheck.html)
/// used for computing the cryptographic check polynomial.
///
/// Note that this function will return different types when
/// `risc0-zkvm` is built with features that select different the target
/// hardware. The version documented here is used when no special
/// hardware features are selected.
pub fn default_hal() -> (Rc<BabyBearSha256CpuHal>, CpuEvalCheck<'static, CircuitImpl>) {
let hal = Rc::new(BabyBearSha256CpuHal::new());
let eval = CpuEvalCheck::new(&CIRCUIT);
Expand All @@ -166,13 +234,19 @@ cfg_if::cfg_if! {
}

impl<'a> Prover<'a> {
/// Construct a new prover using the default options
///
/// This will return an `Err` if `elf` is not a valid ELF file
pub fn new<D>(elf: &[u8], image_id: D) -> Result<Self>
where
Digest: From<D>,
{
Self::new_with_opts(elf, image_id, ProverOpts::default())
}

/// Construct a new prover using custom [ProverOpts]
///
/// This will return an `Err` if `elf` is not a valid ELF file
pub fn new_with_opts<D>(elf: &[u8], image_id: D, opts: ProverOpts<'a>) -> Result<Self>
where
Digest: From<D>,
Expand All @@ -185,20 +259,51 @@ impl<'a> Prover<'a> {
})
}

/// Provide input data to the guest. This data can be read by the guest
/// via [crate::guest::env::read].
///
/// It is possible to provide multiple inputs to the guest so long as the
/// guest reads them in the same order they are added by the [Prover].
/// However, to reduce maintenance burden and the chance of mistakes, we
/// recommend instead using a single `struct` to hold all the inputs and
/// calling [Prover::add_input_u8_slice] just once (on the serialized
/// representation of that input).
pub fn add_input_u8_slice(&mut self, slice: &[u8]) {
self.inner.input.extend_from_slice(slice);
}

/// Provide input data to the guest. This data can be read by the guest
/// via [crate::guest::env::read].
///
/// It is possible to provide multiple inputs to the guest so long as the
/// guest reads them in the same order they are added by the [Prover].
/// However, to reduce maintenance burden and the chance of mistakes, we
/// recommend instead using a single `struct` to hold all the inputs and
/// calling [Prover::add_input_u32_slice] just once (on the serialized
/// representation of that input).
pub fn add_input_u32_slice(&mut self, slice: &[u32]) {
self.inner
.input
.extend_from_slice(bytemuck::cast_slice(slice));
}

/// Read _private_ output data from the guest. This reads data written by
/// [crate::guest::env::write] or [crate::guest::env::write_slice] in the
/// guest.
///
/// The proven result data from [crate::guest::env::commit] is not accessed
/// with this function. Instead read the [Receipt::journal].
pub fn get_output_u8_slice(&self) -> &[u8] {
&self.inner.output
}

/// Read _private_ output data from the guest. This reads data written by
/// [crate::guest::env::write] or [crate::guest::env::write_slice] in the
/// guest. The data is read as 32-bit words, and an `Err` will be returned
/// if the data is not word-aligned.
///
/// The proven result data from [crate::guest::env::commit] is not accessed
/// with this function. Instead read the [Receipt::journal].
pub fn get_output_u32_vec(&self) -> Result<Vec<u32>> {
if self.inner.output.len() % WORD_SIZE != 0 {
bail!("Private output must be word-aligned");
Expand All @@ -211,6 +316,14 @@ impl<'a> Prover<'a> {
.collect())
}

/// Run the guest code. If the guest exits successfully, this returns a
/// [Receipt] that proves execution. If the execution of the guest fails for
/// any reason, this instead returns an `Err`.
///
/// This function uses the default HAL (Hardware Abstraction Layer) to
/// run the guest. If you want to use a different HAL, you can do so either
/// by changing the default using risc0_zkvm feature flags, or by using
/// [Prover::run_with_hal].
#[tracing::instrument(skip_all)]
pub fn run(&mut self) -> Result<Receipt> {
let (hal, eval) = default_hal();
Expand All @@ -227,6 +340,10 @@ impl<'a> Prover<'a> {
}
}

/// Run the guest code. Like [Prover::run], but with parameters for
/// selecting a HAL, allowing the use of HALs other than [default_hal].
/// People creating or using a third-party HAL can use this function to run
/// the Prover with that HAL.
#[tracing::instrument(skip_all)]
pub fn run_with_hal<H, E>(&mut self, hal: &H, eval: &E) -> Result<Receipt>
where
Expand Down
145 changes: 142 additions & 3 deletions risc0/zkvm/src/receipt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,69 @@
// limitations under the License.

//! Manages the output and cryptographic data for a proven computation
//!
//! The primary component of this module is the [Receipt]. A [Receipt] contains
//! the result of a zkVM guest execution and cryptographic proof of how it was
//! generated. The prover can provide a [Receipt] to an untrusting party to
//! convince them that the results contained within the [Receipt] came from
//! running specific code. Conversely, a verifier can inspect a [Receipt] to
//! confirm that its results must have been generated from the expected code,
//! even when this code was run by an untrused source.
//!
//! # Usage
//! To create a receipt, use [crate::prove::Prover::run] or
//! [crate::prove::Prover::run_with_hal]:
//! ```ignore
//! // In real code, the ELF & Image ID would be generated by risc0 build scripts from guest code
//! use methods::{EXAMPLE_ELF, EXAMPLE_ID};
//! use risc0_zkvm::Prover;
//!
//! let mut prover = Prover::new(&EXAMPLE_ELF, EXAMPLE_ID)?;
//! let receipt = prover.run()?;
//! ```
//! To confirm that a [Receipt] was honestly generated, use [Receipt::verify]
//! and supply the ImageID of the code that should have been executed as a
//! parameter. (See
//! [risc0_build](https://docs.rs/risc0-build/latest/risc0_build/) for more
//! information about how ImageIDs are generated.)
//! ```
//! use risc0_zkvm::Receipt;
//! # use risc0_zkvm::serde::Result;
//!
//! # fn main() -> Result<()> {
//! # // Need to awkwardly set up a fake Receipt since we can't use the guest in docs
//! # let journal_words = risc0_zkvm::serde::to_vec(&String::from("test"))
//! # .unwrap()
//! # .iter()
//! # .map(|&x| x.to_le_bytes())
//! # .collect::<Vec<[u8; 4]>>();
//! # let mut journal: Vec<u8> = vec!();
//! # for word in journal_words.iter() {
//! # for byte in word.iter() {
//! # journal.push(*byte);
//! # }
//! # }
//! # let receipt = Receipt {
//! # seal: vec!(),
//! # journal: journal,
//! # };
//! # use crate::risc0_zkvm::sha::Sha256;
//! # let IMAGE_ID: [u32; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
//! // Here `receipt` is a Receipt whose journal contains the String "test"
//! receipt.verify(&IMAGE_ID);
//! let committed_value: String = risc0_zkvm::serde::from_slice(&receipt.journal)?;
//! assert_eq!("test", committed_value);
//! # Ok(())
//! # }
//! ```
//! The public outputs of the [Receipt] are contained in the [Receipt::journal].
//! We provide serialization tools in the zkVM [serde](crate::serde) module,
//! which can be used to read data from the journal as the same type it was
//! written to the journal. We also have a crate
//! [zeroio](https://docs.rs/risc0-zeroio/latest/risc0_zeroio/) offering an
//! alternative serialization approach which is more restrictive but may have
//! better performance for some use cases. If you prefer, you can also directly
//! access the [Receipt::journal] as a `Vec<u8>`.
use alloc::vec::Vec;

Expand All @@ -35,6 +98,24 @@ use crate::{
ControlIdLocator, CIRCUIT,
};

/// Reports whether the zkVM is in the insecure seal skipping mode
///
/// Returns `true` when in the insecure seal skipping mode. Returns `false` when
/// in normal secure mode.
///
/// When `insecure_skip_seal` is `false`, [crate::prove::Prover::run] will
/// generate a seal when run that proves faithful execution, and
/// [Receipt::verify] will check the seal and return an `Err` if the seal is
/// missing or invalid.
///
/// When `insecure_skip_seal` is `true`, [crate::prove::Prover::run] will not
/// generate a seal and the Receipt does not contain a proof of execution, and
/// [Receipt::verify] will not check the seal and will return an `Ok` even if
/// the seal is missing or invalid.
///
/// In particular, if [Receipt::verify] is run with `insecure_skip_seal` being
/// `false`, it will always return an `Err` for any [Receipt] generated while
/// `insecure_skip_seal` was `true`.
#[cfg(all(feature = "std", not(target_os = "zkvm")))]
pub fn insecure_skip_seal() -> bool {
cfg!(feature = "insecure_skip_seal")
Expand All @@ -46,7 +127,18 @@ pub fn insecure_skip_seal() -> bool {
cfg!(feature = "insecure_skip_seal")
}

/// The receipt serves as a zero-knowledge proof of computation.
/// The Receipt is a zero-knowledge proof of computation.
///
/// A Receipt is an attestation that specific code, identified by an `ImageID`,
/// was executed and generated the results included in the Receipt's
/// [Receipt::journal].
///
/// This Receipt is zero-knowledge in the sense that no information that is not
/// deliberately published is included in the receipt. Everything in the
/// [Receipt::journal] was explicitly written to the journal by the zkVM guest,
/// and the [Receipt::seal] is cryptographically opaque.
///
/// See [the module-level documentation](crate::receipt) for usage examples.
#[derive(Deserialize, Serialize, ZeroioSerialize, ZeroioDeserialize, Clone, Debug)]
pub struct Receipt {
/// The journal contains the public outputs of the computation.
Expand All @@ -58,6 +150,20 @@ pub struct Receipt {
pub seal: Vec<u32>,
}

/// Verifies the `seal` and `journal` form a valid [Receipt]
///
/// This is the underlying function used by [Receipt::verify],
/// [Receipt::verify_with_hash], and [Receipt::verify_with_hal]. It is exposed
/// for use cases where `seal` and `journal` are not already part of a [Receipt]
/// object.
///
/// This verifies that this receipt was constructed by running code whose
/// ImageID is `image_id`. Returns `Ok(())` if this is true. If the code used to
/// generate this Receipt has a different ImageID, or if it was generated by an
/// insecure or malicious prover, this will return an `Err`.
///
/// This function allows the user to specify the Hardware Abstraction Layer
/// (HAL) to be used for verification with the `hal` parameter.
pub fn verify_with_hal<'a, H, D>(hal: &H, image_id: D, seal: &[u32], journal: &[u8]) -> Result<()>
where
H: risc0_zkp::verify::VerifyHal<Elem = BabyBearElem>,
Expand Down Expand Up @@ -133,6 +239,12 @@ where
}

impl Receipt {
/// Constructs a Receipt from a journal and a seal
///
/// A Receipt is more commonly constructed as the output of
/// [crate::prove::Prover::run], but since it has no data beyond its
/// [journal](Receipt::journal) and [seal](Receipt::seal) it can be
/// directly constructed from them.
pub fn new(journal: &[u8], seal: &[u32]) -> Self {
Self {
journal: Vec::from(journal),
Expand All @@ -141,14 +253,31 @@ impl Receipt {
}

#[cfg(not(target_os = "zkvm"))]
/// Verifies a receipt using CPU.
/// Verifies a SHA-256 receipt using CPU
///
/// Verifies that this receipt was constructed by running code whose ImageID
/// is `image_id`. Returns `Ok(())` if this is true. If the code used to
/// generate this Receipt has a different ImageID, or if it was generated by
/// an insecure or malicious prover, this will return an `Err`.
///
/// This runs the verification on the CPU and is for receipts using SHA-256
/// as their hash function.
pub fn verify<'a, D>(&self, image_id: D) -> Result<()>
where
&'a Digest: From<D>,
{
self.verify_with_hash::<HashSuiteSha256<BabyBear, crate::sha::Impl>, _>(image_id)
}

/// Verifies a receipt with a user-specified hash function using the CPU.
///
/// Verifies that this receipt was constructed by running code whose ImageID
/// is `image_id`. Returns `Ok(())` if this is true. If the code used to
/// generate this Receipt has a different ImageID, or if it was generated by
/// an insecure or malicious prover, this will return an `Err`.
///
/// This runs the verification on the CPU and is for receipts using the hash
/// function specified by `HS`.
#[cfg(not(target_os = "zkvm"))]
pub fn verify_with_hash<'a, HS, D>(&self, image_id: D) -> Result<()>
where
Expand All @@ -161,7 +290,17 @@ impl Receipt {
self.verify_with_hal(&hal, image_id)
}

/// Verifies a receipt using the hardware acceleration layer.
/// Verifies a receipt with a user-specified hash function and HAL
///
/// Verifies that this receipt was constructed by running code whose ImageID
/// is `image_id`. Returns `Ok(())` if this is true. If the code used to
/// generate this Receipt has a different ImageID, or if it was generated by
/// an insecure or malicious prover, this will return an `Err`.
///
/// This function allows the user to specify the Hardware Abstraction Layer
/// (HAL) to be used for verification with the `hal` parameter. Note that
/// the selection of a HAL also implies the selection of the hash function
/// associated with that HAL.
pub fn verify_with_hal<'a, H, D>(&self, hal: &H, image_id: D) -> Result<()>
where
H: risc0_zkp::verify::VerifyHal<Elem = BabyBearElem>,
Expand Down
Loading

0 comments on commit afacb28

Please sign in to comment.