use alloc::vec::Vec;
use alloy_primitives::B256;
use alloy_sol_types::SolValue;
use risc0_binfmt::{tagged_struct, Digestible};
use risc0_zkvm::{
sha,
sha::{Digest, Sha256, DIGEST_BYTES},
InnerReceipt, MaybePruned, Receipt, ReceiptClaim, VerifierContext,
};
use serde::{Deserialize, Serialize};
use crate::{merkle_path_root, GuestState, MerkleMountainRange, Seal};
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct SetInclusionReceipt<Claim>
where
Claim: Digestible + Clone + Serialize,
{
pub claim: MaybePruned<Claim>,
pub root: Option<Receipt>,
pub merkle_path: Vec<Digest>,
pub verifier_parameters: Digest,
}
#[derive(thiserror::Error, Debug)]
pub enum VerificationError {
#[error("{0}")]
Base(risc0_zkp::verify::VerificationError),
#[error("root receipt claim does not match expected set builder claim: {claim_digest} != {expected}")]
ClaimDigestDoesNotMatch {
claim_digest: Digest,
expected: Digest,
},
#[error("failed to confirm the validity of the set root: {path_root}")]
RootNotVerified { path_root: Digest },
}
impl From<core::convert::Infallible> for VerificationError {
fn from(_: core::convert::Infallible) -> Self {
unreachable!()
}
}
impl From<risc0_zkp::verify::VerificationError> for VerificationError {
fn from(err: risc0_zkp::verify::VerificationError) -> Self {
VerificationError::Base(err)
}
}
#[derive(thiserror::Error, Debug)]
pub enum SetInclusionEncodingError {
#[error("unsupported receipt type")]
UnsupportedReceipt,
}
#[derive(thiserror::Error, Debug)]
pub enum SetInclusionDecodingError {
#[error("unsupported receipt type")]
UnsupportedReceipt,
#[error("Digest decoding error")]
Digest,
#[error("failed to decode aggregation seal from bytes")]
SolType(#[from] alloy_sol_types::Error),
}
impl<Claim> SetInclusionReceipt<Claim>
where
Claim: Digestible + Clone + Serialize,
{
pub fn from_path_with_verifier_params(
claim: impl Into<MaybePruned<Claim>>,
merkle_path: Vec<Digest>,
verifier_parameters: impl Into<Digest>,
) -> Self {
Self {
claim: claim.into(),
root: None,
merkle_path,
verifier_parameters: verifier_parameters.into(),
}
}
pub fn with_root(self, root_receipt: Receipt) -> Self {
Self {
root: Some(root_receipt),
..self
}
}
pub fn without_root(self) -> Self {
Self { root: None, ..self }
}
pub fn verify_integrity_with_context(
&self,
ctx: &VerifierContext,
set_verifier_params: SetInclusionReceiptVerifierParameters,
_recursion_verifier_params: Option<RecursionVerifierParameters>,
) -> Result<(), VerificationError> {
let path_root = merkle_path_root(self.claim.digest::<sha::Impl>(), &self.merkle_path);
let expected_root_claim = ReceiptClaim::ok(
set_verifier_params.image_id,
GuestState {
self_image_id: set_verifier_params.image_id,
mmr: MerkleMountainRange::new_finalized(path_root),
}
.encode(),
);
if let Some(ref root_receipt) = self.root {
root_receipt.verify_integrity_with_context(ctx)?;
if root_receipt.claim()?.digest::<sha::Impl>()
!= expected_root_claim.digest::<sha::Impl>()
{
return Err(VerificationError::ClaimDigestDoesNotMatch {
claim_digest: root_receipt.claim()?.digest::<sha::Impl>(),
expected: expected_root_claim.digest::<sha::Impl>(),
});
}
return Ok(());
}
#[cfg(target_os = "zkvm")]
if let Some(params) = _recursion_verifier_params {
risc0_zkvm::guest::env::verify_assumption(
expected_root_claim.digest::<sha::Impl>(),
params.control_root.unwrap_or(Digest::ZERO),
)?;
return Ok(());
}
Err(VerificationError::RootNotVerified { path_root })
}
pub fn abi_encode_seal(&self) -> Result<Vec<u8>, SetInclusionEncodingError> {
let selector = &self.verifier_parameters.as_bytes()[..4];
let merkle_path: Vec<B256> = self
.merkle_path
.iter()
.map(|x| <[u8; DIGEST_BYTES]>::from(*x).into())
.collect();
let root_seal: Vec<u8> = self.root.as_ref().map(encode_seal).unwrap_or(Ok(vec![]))?;
let seal = Seal {
path: merkle_path,
root_seal: root_seal.into(),
};
let mut encoded_seal = Vec::<u8>::with_capacity(selector.len() + seal.abi_encoded_size());
encoded_seal.extend_from_slice(selector);
encoded_seal.extend_from_slice(&seal.abi_encode());
Ok(encoded_seal)
}
}
fn extract_path(seal: &[u8]) -> Result<Vec<Digest>, SetInclusionDecodingError> {
if seal.len() <= 4 {
return Ok(Vec::new());
}
let aggregation_seal = <Seal>::abi_decode(&seal[4..])?;
aggregation_seal
.path
.iter()
.map(|x| Digest::try_from(x.as_slice()).map_err(|_| SetInclusionDecodingError::Digest))
.collect()
}
pub fn decode_set_inclusion_seal(
seal: &[u8],
claim: ReceiptClaim,
verifier_parameters: Digest,
) -> Result<SetInclusionReceipt<ReceiptClaim>, SetInclusionDecodingError> {
let receipt = SetInclusionReceipt::from_path_with_verifier_params(
claim.clone(),
extract_path(seal)?,
verifier_parameters,
);
Ok(receipt)
}
fn encode_seal(receipt: &risc0_zkvm::Receipt) -> Result<Vec<u8>, SetInclusionEncodingError> {
match receipt.inner.clone() {
InnerReceipt::Fake(receipt) => {
let seal = receipt.claim.digest::<sha::Impl>().as_bytes().to_vec();
let selector = &[0u8; 4];
let mut selector_seal = Vec::with_capacity(selector.len() + seal.len());
selector_seal.extend_from_slice(selector);
selector_seal.extend_from_slice(&seal);
Ok(selector_seal)
}
InnerReceipt::Groth16(receipt) => {
let selector = &receipt.verifier_parameters.as_bytes()[..4];
let mut selector_seal = Vec::with_capacity(selector.len() + receipt.seal.len());
selector_seal.extend_from_slice(selector);
selector_seal.extend_from_slice(receipt.seal.as_ref());
Ok(selector_seal)
}
_ => Err(SetInclusionEncodingError::UnsupportedReceipt),
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SetInclusionReceiptVerifierParameters {
pub image_id: Digest,
}
impl Digestible for SetInclusionReceiptVerifierParameters {
fn digest<S: Sha256>(&self) -> Digest {
tagged_struct::<S>(
"risc0.SetInclusionReceiptVerifierParameters",
&[self.image_id],
&[],
)
}
}
#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct RecursionVerifierParameters {
pub control_root: Option<Digest>,
}