use crate::{
data_stack::Stack,
runtime_resource_meter::RuntimeResourceMeter,
zk_precompiles::{
ZkPrecompile,
risc0::{
merkle::MerkleProof,
rcpt::{HashFnId, SuccinctReceipt},
receipt_claim::compute_assert_claim,
},
},
};
use risc0_zkp::core::digest::DIGEST_BYTES;
pub use risc0_zkp::core::digest::Digest;
mod error;
pub mod merkle;
pub mod rcpt;
pub mod receipt_claim;
pub struct R0SuccinctPrecompile;
pub use error::R0Error;
const POSEIDON2_CONTROL_MERKLE_DEPTH: usize = 8;
fn control_merkle_depth_for(hashfn: HashFnId) -> usize {
match hashfn {
HashFnId::Poseidon2 => POSEIDON2_CONTROL_MERKLE_DEPTH,
HashFnId::Blake2b | HashFnId::Sha256 => unreachable!("unsupported hashfn was checked above"),
}
}
fn parse_digest(bytes: impl AsRef<[u8]>) -> Result<Digest, R0Error> {
let bytes = bytes.as_ref();
Digest::try_from(bytes).map_err(|_| R0Error::InvalidDigestLength(bytes.len()))
}
fn parse_seal(bytes: impl AsRef<[u8]>) -> Result<Vec<u32>, R0Error> {
let bytes = bytes.as_ref();
let (chunks, remaining) = bytes.as_chunks::<4>();
if !remaining.is_empty() {
Err(R0Error::InvalidSealLength(bytes.len()))
} else {
Ok(chunks.iter().copied().map(u32::from_le_bytes).collect())
}
}
fn parse_hashfn(bytes: impl AsRef<[u8]>) -> Result<HashFnId, R0Error> {
let bytes = bytes.as_ref();
if bytes.len() != 1 {
return Err(R0Error::InvalidHashFnEncoding(bytes.len()));
}
HashFnId::try_from(bytes[0])
}
fn parse_merkle_index(bytes: impl AsRef<[u8]>) -> Result<u32, R0Error> {
let bytes = bytes.as_ref();
if bytes.len() != 4 {
return Err(R0Error::InvalidMerkleIndexLength(bytes.len()));
}
Ok(u32::from_le_bytes(bytes.try_into().expect("index is 4 bytes")))
}
fn parse_digest_list(bytes: impl AsRef<[u8]>) -> Result<Vec<Digest>, R0Error> {
let bytes = bytes.as_ref();
let (chunks, remaining) = bytes.as_chunks::<DIGEST_BYTES>();
if !remaining.is_empty() {
Err(R0Error::InvalidDigestListLength(bytes.len()))
} else {
Ok(chunks.iter().copied().map(Digest::from).collect())
}
}
impl ZkPrecompile for R0SuccinctPrecompile {
type Error = R0Error;
fn verify_zk(dstack: &mut Stack, _meter: &mut RuntimeResourceMeter) -> Result<(), Self::Error> {
let [claim, control_index, control_digests, seal, journal, image_id, control_id, hashfn] = dstack.pop_raw()?;
let control_id = parse_digest(control_id)?;
let seal = parse_seal(seal)?;
let claim = parse_digest(claim)?;
let hashfn = parse_hashfn(hashfn)?;
if hashfn != HashFnId::Poseidon2 {
return Err(R0Error::UnsupportedHashFn(hashfn));
}
let control_index = parse_merkle_index(control_index)?;
let control_digests = parse_digest_list(control_digests)?;
let max_control_proof_len = control_merkle_depth_for(hashfn);
if control_digests.len() > max_control_proof_len {
return Err(R0Error::ControlInclusionProofTooLong { actual: control_digests.len(), max: max_control_proof_len });
}
let control_inclusion_proof = MerkleProof { index: control_index, digests: control_digests };
let rcpt = SuccinctReceipt::new(seal, control_id, claim, hashfn, control_inclusion_proof);
let image_id: Digest = parse_digest(image_id)?;
let journal: Digest = parse_digest(journal)?;
rcpt.verify_integrity()?;
compute_assert_claim(rcpt.claim(), image_id, journal)?;
Ok(())
}
}