Skip to content

Commit

Permalink
Initial implementation of proof composition (risc0#992)
Browse files Browse the repository at this point in the history
Proof composition is a new feature of the zkVM allowing verification of
receipts produced by other zkVM guest programs inside of a guest
program. This allow for composition of guest programs, in which the
journal, or other output state, of one guest can be consumed as input to
another.

This PR provides a basic implementation of proof composition. In
particular, it implements composition over the `CompositeReceipt` struct
(formerly `SegmentSeceipts` and `InnerReceipt::Flat`). It does not
implement the recursion predicates needed to produce a single
`SuccintReceipt` for a receipt of execution for a proof composition
(risc0#991).

Other changes:
* This PR clarifies and makes more consistent the semantics of the
`verify` method on `Receipt` and related structs. Semantics are now to
require an exit code of `Halted(0)` or `Paused(0)`, which aligns with
how receipts are using in all examples, and what seems most likely to be
intended by users. It adds the `verify_integrity_with_context` method as
the core method for verifying cryptographic integrity and internal
consistency, allowing use cases that do indeed want to verify receipts
for non-success executions.
* Relatedly, the `prove_guest_errors` options is added to `ProverOpts`
to continue and prove sessions that end with an error status (e.g.
`ExitCode::Fault` or `ExitCode::Halted(1)`). When not set, the prover
will stop after executor on guest error, such that the default is to
only return receipts that of successful execution.

Co-authored-by: Erik Kaneda <[email protected]>
Co-authored-by: Frank Laub <[email protected]>
Co-authored-by: Matheus Cardoso <[email protected]>
  • Loading branch information
4 people authored Oct 30, 2023
1 parent e48f779 commit f6e74e0
Show file tree
Hide file tree
Showing 56 changed files with 2,908 additions and 776 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
.dockerignore
.DS_Store
.idea/
.vim/
bazel-*
Cargo.lock
dist/
Expand Down
19 changes: 17 additions & 2 deletions benchmarks/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,17 @@
*.csv
!Cargo.lock
*~
*.swp
*.swo
.bazelrc.local
.cache
.devcontainer/
.dockerignore
.DS_Store
.idea/
.vim/
bazel-*
Cargo.lock
dist/
Dockerfile
rust-project.json
target/
tmp/
2 changes: 2 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ $ RUST_LOG=info cargo run --release --bin risc0-benchmark -F cuda -- --out metri
```

## Running specific benchmark

To run a specific benchmark replace the `all` option used in the previous command with one of the following:
e.g.,

```console
$ RUST_LOG=info cargo run --release -F metal -- --out metrics.csv big-sha2
```
Expand Down
1 change: 0 additions & 1 deletion bonsai/ethereum-relay/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ Then, the remaining steps will flow as above. The following example explains how
The following example assumes that the Bonsai Relay is up and running with the server API enabled,
and that the memory image of your `ELF` is already registered against Bonsai with a given `IMAGE_ID` as its identifier.

<!-- TODO(victor): Rework this example to make it actually build and run -->
```rust,ignore
// initialize a relay client
let relay_client = Client::from_parts(
Expand Down
11 changes: 10 additions & 1 deletion bonsai/ethereum-relay/src/tests/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ pub(crate) mod tests {
SessionId,
};
use ethers::types::{Address, Bytes, H256};
use risc0_zkvm::{receipt_metadata::MaybePruned, sha::Digest, ExitCode, ReceiptMetadata};
use risc0_zkvm::{InnerReceipt, Journal, Receipt};
use uuid::Uuid;
use wiremock::{
Expand All @@ -45,7 +46,15 @@ pub(crate) mod tests {

let receipt_data_response = Receipt {
journal: Journal::new(vec![]),
inner: InnerReceipt::Fake,
inner: InnerReceipt::Fake {
metadata: ReceiptMetadata {
pre: MaybePruned::Pruned(Digest::ZERO),
post: MaybePruned::Pruned(Digest::ZERO),
exit_code: ExitCode::Halted(0),
input: Digest::ZERO,
output: None.into(),
},
},
};

let create_snark_res = CreateSessRes {
Expand Down
2 changes: 1 addition & 1 deletion bonsai/examples/governance/methods/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ risc0-build = { workspace = true, features = ["guest-list"] }

[dependencies]
risc0-build = { workspace = true, features = ["guest-list"] }
risc0-zkvm = { workspace = true }

[dev-dependencies]
hex-literal = "0.4"
risc0-zkvm = { workspace = true }

[features]
default = []
Expand Down
6 changes: 4 additions & 2 deletions bonsai/rest-api-mock/src/prover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,10 @@ impl Prover {
.context("Executor failed to generate a successful session")?;

let receipt = Receipt {
inner: InnerReceipt::Fake,
journal: session.journal,
inner: InnerReceipt::Fake {
metadata: session.get_metadata()?,
},
journal: session.journal.unwrap_or_default(),
};
let receipt_bytes = bincode::serialize(&receipt)?;
self.storage
Expand Down
128 changes: 128 additions & 0 deletions risc0/binfmt/src/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// Copyright 2023 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.

extern crate alloc;

use alloc::vec::Vec;
use core::borrow::Borrow;

use risc0_zkp::core::{digest::Digest, hash::sha::Sha256};

/// Defines a collision resistant hash for the typed and structured data.
pub trait Digestible {
/// Calculate a collision resistant hash for the typed and structured data.
fn digest<S: Sha256>(&self) -> Digest;
}

impl Digestible for [u8] {
fn digest<S: Sha256>(&self) -> Digest {
*S::hash_bytes(&self)
}
}

impl Digestible for Vec<u8> {
fn digest<S: Sha256>(&self) -> Digest {
*S::hash_bytes(&self)
}
}

impl<T: Digestible> Digestible for Option<T> {
fn digest<S: Sha256>(&self) -> Digest {
match self {
Some(val) => val.digest::<S>(),
None => Digest::ZERO,
}
}
}

/// A struct hashing routine, permiting tree-like opening of fields.
///
/// Used for hashing of receipt metadata, and in the recursion predicates.
pub fn tagged_struct<S: Sha256>(tag: &str, down: &[impl Borrow<Digest>], data: &[u32]) -> Digest {
let tag_digest: Digest = *S::hash_bytes(tag.as_bytes());
let mut all = Vec::<u8>::new();
all.extend_from_slice(tag_digest.as_bytes());
for digest in down {
all.extend_from_slice(digest.borrow().as_ref());
}
for word in data.iter().copied() {
all.extend_from_slice(&word.to_le_bytes());
}
let down_count: u16 = down
.len()
.try_into()
.expect("struct defined with more than 2^16 fields");
all.extend_from_slice(&down_count.to_le_bytes());
*S::hash_bytes(&all)
}

/// A list hashing routine, permiting iterative opening over elements.
///
/// Used for hashing of receipt metadata assumptions list, and in the recursion
/// predicates.
pub fn tagged_list<S: Sha256>(tag: &str, list: &[impl Borrow<Digest>]) -> Digest {
list.into_iter()
.rev()
.fold(Digest::ZERO, |list_digest, elem| {
tagged_list_cons::<S>(tag, elem.borrow(), &list_digest)
})
}

/// Calculate the hash resulting from adding one element to a [tagged_list]
/// digest.
///
/// This function logically pushes the element `head` onto the front of the
/// list.
///
/// ```rust
/// use risc0_zkp::core::hash::sha::{cpu::Impl, Sha256};
/// use risc0_binfmt::{tagged_list, tagged_list_cons};
///
/// let [a, b, c] = [
/// *Impl::hash_bytes(b"a".as_slice()),
/// *Impl::hash_bytes(b"b".as_slice()),
/// *Impl::hash_bytes(b"c".as_slice()),
/// ];
/// assert_eq!(
/// tagged_list::<Impl>("tag", &[a, b, c]),
/// tagged_list_cons::<Impl>("tag", &a, &tagged_list::<Impl>("tag", &[b, c])),
/// );
/// ```
pub fn tagged_list_cons<S: Sha256>(tag: &str, head: &Digest, rest: &Digest) -> Digest {
tagged_struct::<S>(tag, &[head, rest], &[])
}

#[cfg(test)]
mod tests {
use risc0_zkp::core::hash::sha::cpu;

use super::{tagged_struct, Digest};

#[test]
fn test_tagged_struct() {
let digest1 = tagged_struct::<cpu::Impl>("foo", &Vec::<Digest>::new(), &[1, 2013265920, 3]);
let digest2 = tagged_struct::<cpu::Impl>("bar", &[digest1, digest1], &[2013265920, 5]);
let digest3 = tagged_struct::<cpu::Impl>(
"baz",
&[digest1, digest2, digest1],
&[6, 7, 2013265920, 9, 10],
);

println!("digest = {:?}", digest3);
assert_eq!(
digest3.to_string(),
"9ff20cc6d365efa2af09181772f49013d05cdee6da896851614cae23aa5dd442"
);
}
}
4 changes: 2 additions & 2 deletions risc0/binfmt/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use risc0_zkvm_platform::{
};
use serde::{Deserialize, Serialize};

use crate::{elf::Program, SystemState};
use crate::{elf::Program, Digestible, SystemState};

/// An image of a zkVM guest's memory
///
Expand Down Expand Up @@ -90,7 +90,7 @@ pub fn compute_image_id(merkle_root: &Digest, pc: u32) -> Digest {
merkle_root: *merkle_root,
pc,
}
.digest()
.digest::<Impl>()
}

/// Compute `ceil(a / b)` via truncated integer division.
Expand Down
8 changes: 6 additions & 2 deletions risc0/binfmt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
#![cfg_attr(all(not(feature = "std"), not(test)), no_std)]

mod elf;
mod hash;
#[cfg(not(target_os = "zkvm"))]
mod image;
mod sys_state;

#[cfg(not(target_os = "zkvm"))]
pub use crate::image::{compute_image_id, MemoryImage, PageTableInfo};
pub use crate::{
elf::Program,
image::{compute_image_id, MemoryImage, PageTableInfo},
sys_state::{read_sha_halfs, tagged_struct, write_sha_halfs, SystemState},
hash::{tagged_list, tagged_list_cons, tagged_struct, Digestible},
sys_state::{read_sha_halfs, write_sha_halfs, SystemState},
};
57 changes: 16 additions & 41 deletions risc0/binfmt/src/sys_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ extern crate alloc;

use alloc::{collections::VecDeque, vec::Vec};

use risc0_zkp::core::{
digest::Digest,
hash::sha::{cpu::Impl, Sha256},
};
use risc0_zkp::core::{digest::Digest, hash::sha::Sha256};
use serde::{Deserialize, Serialize};

#[cfg(not(target_os = "zkvm"))]
use crate::MemoryImage;
use crate::{tagged_struct, Digestible};

/// Represents the public state of a segment, needed for continuations and
/// receipt verification.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
Expand All @@ -46,27 +47,23 @@ impl SystemState {
write_u32_bytes(flat, self.pc);
write_sha_halfs(flat, &self.merkle_root);
}
}

impl Digestible for SystemState {
/// Hash the [crate::SystemState] to get a digest of the struct.
pub fn digest(&self) -> Digest {
tagged_struct("risc0.SystemState", &[self.merkle_root], &[self.pc])
fn digest<S: Sha256>(&self) -> Digest {
tagged_struct::<S>("risc0.SystemState", &[self.merkle_root], &[self.pc])
}
}

/// Implementation of the struct hash described in the recursion predicates RFC.
pub fn tagged_struct(tag: &str, down: &[Digest], data: &[u32]) -> Digest {
let tag_digest: Digest = *Impl::hash_bytes(tag.as_bytes());
let mut all = Vec::<u8>::new();
all.extend_from_slice(tag_digest.as_bytes());
for digest in down {
all.extend_from_slice(digest.as_ref());
}
for word in data.iter().copied() {
all.extend_from_slice(&word.to_le_bytes());
#[cfg(not(target_os = "zkvm"))]
impl From<&MemoryImage> for SystemState {
fn from(image: &MemoryImage) -> Self {
Self {
pc: image.pc,
merkle_root: image.compute_root_hash(),
}
}
let down_count: u16 = down.len().try_into().unwrap();
all.extend_from_slice(&down_count.to_le_bytes());
*Impl::hash_bytes(&all)
}

pub fn read_sha_halfs(flat: &mut VecDeque<u32>) -> Digest {
Expand Down Expand Up @@ -100,25 +97,3 @@ fn write_u32_bytes(flat: &mut Vec<u32>, word: u32) {
flat.push(x as u32);
}
}

#[cfg(test)]
mod tests {
use super::tagged_struct;

#[test]
fn test_tagged_struct() {
let digest1 = tagged_struct("foo", &[], &[1, 2013265920, 3]);
let digest2 = tagged_struct("bar", &[digest1, digest1], &[2013265920, 5]);
let digest3 = tagged_struct(
"baz",
&[digest1, digest2, digest1],
&[6, 7, 2013265920, 9, 10],
);

println!("digest = {:?}", digest3);
assert_eq!(
digest3.to_string(),
"9ff20cc6d365efa2af09181772f49013d05cdee6da896851614cae23aa5dd442"
);
}
}
6 changes: 3 additions & 3 deletions risc0/build/src/docker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,15 @@ mod test {
build("../../risc0/zkvm/methods/guest/Cargo.toml");
compare_image_id(
"risc0_zkvm_methods_guest/multi_test",
"de9e5429782718e0160b172f92a3a1eda72474f5fe89413678bbbf10dc2c99bd",
"dd99009768ad375deada497d7b7375f7a2085d2c70e74d9224d3942204c91206",
);
compare_image_id(
"risc0_zkvm_methods_guest/hello_commit",
"d2259fe3f16fab0e24575331290e9f4a6011c736c47588f3d7570a394b83f99d",
"07066347634d592979f2a5e034d7a2cf5e75ad5b204bee434a40554734cd01bf",
);
compare_image_id(
"risc0_zkvm_methods_guest/slice_io",
"5e2662efeb66987668097cffea45adc5aa200dd694f79f686ae9a6194b113963",
"4e207a32cf4325699af43da1c5cab5bd431f533f46d6d96b085e23d6626d93d6",
);
}
}
4 changes: 1 addition & 3 deletions risc0/cargo-risczero/src/commands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ impl AsRef<str> for BuildSubcommand {
}
}

// TODO(victor): Provide some way to pass features.

/// `cargo risczero build`
#[derive(Parser)]
pub struct BuildCommand {
Expand Down Expand Up @@ -128,7 +126,7 @@ impl BuildCommand {
.ok_or_else(|| anyhow!("invalid path string for target_dir"))?,
]);

// TODO(victor): Give the user a way to request a release build.
// TODO: Give the user a way to request a release build.
// if !is_debug() {
// cmd.args(&["--release"]);
//}
Expand Down
5 changes: 3 additions & 2 deletions risc0/cargo-risczero/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ mod commands;
mod toolchain;
mod utils;

#[cfg(feature = "experimental")]
pub use self::commands::build::BuildSubcommand;

use clap::{Parser, Subcommand};

#[cfg(feature = "experimental")]
use self::commands::build::BuildCommand;
#[cfg(feature = "experimental")]
pub use self::commands::build::BuildSubcommand;
use self::commands::{
build_guest::BuildGuest, build_toolchain::BuildToolchain, install::Install, new::NewCommand,
};
Expand Down
Loading

0 comments on commit f6e74e0

Please sign in to comment.