#![deny(rustdoc::broken_intra_doc_links)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
extern crate alloc;
use alloc::vec::Vec;
use core::borrow::Borrow;
use alloy_primitives::{Keccak256, B256};
use alloy_sol_types::SolValue;
use risc0_binfmt::Digestible;
use risc0_zkvm::{sha, sha::Digest, ReceiptClaim};
use serde::{Deserialize, Serialize};
#[cfg(feature = "verify")]
mod receipt;
#[cfg(feature = "verify")]
pub use receipt::{
EncodingError, RecursionVerifierParamters, SetInclusionReceipt,
SetInclusionReceiptVerifierParameters,
VerificationError,
};
alloy_sol_types::sol! {
#[sol(all_derives)]
struct Seal {
bytes32[] path;
bytes root_seal;
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub enum GuestInput {
Singleton {
self_image_id: Digest,
claim: ReceiptClaim,
},
Join {
self_image_id: Digest,
left_set_root: Digest,
right_set_root: Digest,
},
}
impl GuestInput {
#[inline]
pub fn image_id(&self) -> Digest {
match self {
GuestInput::Singleton { self_image_id, .. } => *self_image_id,
GuestInput::Join { self_image_id, .. } => *self_image_id,
}
}
#[inline]
pub fn root(&self) -> Digest {
match self {
GuestInput::Singleton { claim, .. } => claim.digest::<sha::Impl>(),
GuestInput::Join {
left_set_root,
right_set_root,
..
} => commutative_keccak256(left_set_root, right_set_root),
}
}
#[inline]
pub fn to_output(&self) -> GuestOutput {
GuestOutput::new(self.image_id(), self.root())
}
}
pub fn merkle_root(leaves: &[Digest]) -> Digest {
match leaves {
[] => panic!("digest list is empty, cannot compute Merkle root"),
[digest] => *digest, _ => {
let (left, right) = leaves.split_at(leaves.len().next_power_of_two() / 2);
let left_root = merkle_root(left);
let right_root = merkle_root(right);
commutative_keccak256(&left_root, &right_root)
}
}
}
pub fn merkle_path(leaves: &[Digest], index: usize) -> Vec<Digest> {
assert!(
index < leaves.len(),
"no leaf with index {index} in tree of size {}",
leaves.len()
);
if leaves.len() == 1 {
return Vec::new(); }
let mut path = Vec::new();
let mut current_leaves = leaves;
let mut current_index = index;
while current_leaves.len() > 1 {
let mid = current_leaves.len().next_power_of_two() / 2;
let (left, right) = current_leaves.split_at(mid);
if current_index < mid {
path.push(merkle_root(right));
current_leaves = left;
} else {
path.push(merkle_root(left));
current_leaves = right;
current_index -= mid;
}
}
path.reverse();
path
}
pub fn merkle_path_root(
leaf: &Digest,
path: impl IntoIterator<Item = impl Borrow<Digest>>,
) -> Digest {
path.into_iter()
.fold(*leaf, |a, b| commutative_keccak256(a.borrow(), b.borrow()))
}
fn commutative_keccak256(a: &Digest, b: &Digest) -> Digest {
let mut hasher = Keccak256::new();
if a.as_bytes() < b.as_bytes() {
hasher.update(a.as_bytes());
hasher.update(b.as_bytes());
} else {
hasher.update(b.as_bytes());
hasher.update(a.as_bytes());
}
hasher.finalize().0.into()
}
alloy_sol_types::sol! {
#[sol(all_derives)]
struct GuestOutput {
bytes32 id;
bytes32 root;
}
}
impl GuestOutput {
pub fn new(image_id: impl Into<Digest>, root: Digest) -> Self {
Self {
id: to_b256(image_id.into()),
root: to_b256(root),
}
}
#[inline]
pub fn abi_encode(&self) -> Vec<u8> {
SolValue::abi_encode(self)
}
#[inline]
pub fn image_id(&self) -> Digest {
self.id.0.into()
}
#[inline]
pub fn root(&self) -> Digest {
self.root.0.into()
}
}
fn to_b256(digest: Digest) -> B256 {
<[u8; 32]>::from(digest).into()
}
#[cfg(test)]
mod tests {
use super::*;
use hex::FromHex;
fn assert_merkle_root(digests: &[Digest], expected_root: Digest) {
let root = merkle_root(digests);
assert_eq!(root, expected_root);
}
#[test]
fn test_root_manual() {
let digests = vec![
Digest::from_hex("6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9")
.unwrap(),
Digest::from_hex("6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9")
.unwrap(),
Digest::from_hex("6a428060b5d51f04583182f2ff1b565f9db661da12ee7bdc003e9ab6d5d91ba9")
.unwrap(),
];
assert_merkle_root(
&digests,
Digest::from_hex("e004c72e4cb697fa97669508df099edbc053309343772a25e56412fc7db8ebef")
.unwrap(),
);
}
#[test]
fn test_merkle_root() {
let digests = vec![Digest::from([0u8; 32])];
assert_merkle_root(&digests, digests[0]);
let digests = vec![
Digest::from([0u8; 32]),
Digest::from([1u8; 32]),
Digest::from([2u8; 32]),
];
assert_merkle_root(
&digests,
commutative_keccak256(
&commutative_keccak256(&digests[0], &digests[1]),
&digests[2],
),
);
let digests = vec![
Digest::from([0u8; 32]),
Digest::from([1u8; 32]),
Digest::from([2u8; 32]),
Digest::from([3u8; 32]),
];
assert_merkle_root(
&digests,
commutative_keccak256(
&commutative_keccak256(&digests[0], &digests[1]),
&commutative_keccak256(&digests[2], &digests[3]),
),
);
}
#[test]
fn test_consistency() {
for length in 1..=128 {
let digests: Vec<Digest> = (0..length)
.map(|_| rand::random::<[u8; 32]>().into())
.collect();
let root = merkle_root(&digests);
for i in 0..length {
let path = merkle_path(&digests, i);
assert_eq!(merkle_path_root(&digests[i], &path), root);
}
}
}
}