use alloc::{
string::{String, ToString},
vec::Vec,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{
crypto::hash::{Blake3_256, Poseidon2, Rpo256, Rpx256},
precompile::PrecompileRequest,
serde::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable, SliceReader,
},
};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ExecutionProof {
pub proof: Vec<u8>,
pub hash_fn: HashFunction,
pub pc_requests: Vec<PrecompileRequest>,
}
impl ExecutionProof {
pub const fn new(
proof: Vec<u8>,
hash_fn: HashFunction,
pc_requests: Vec<PrecompileRequest>,
) -> Self {
Self { proof, hash_fn, pc_requests }
}
pub fn stark_proof(&self) -> &[u8] {
&self.proof
}
pub const fn hash_fn(&self) -> HashFunction {
self.hash_fn
}
pub fn precompile_requests(&self) -> &[PrecompileRequest] {
&self.pc_requests
}
pub fn security_level(&self) -> u32 {
96
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut bytes = Vec::new();
self.write_into(&mut bytes);
bytes
}
pub fn from_bytes(source: &[u8]) -> Result<Self, DeserializationError> {
let mut reader = SliceReader::new(source);
Self::read_from(&mut reader)
}
pub fn into_parts(self) -> (HashFunction, Vec<u8>, Vec<PrecompileRequest>) {
(self.hash_fn, self.proof, self.pc_requests)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(u8)]
pub enum HashFunction {
Blake3_256 = 0x01,
Rpo256 = 0x02,
Rpx256 = 0x03,
Poseidon2 = 0x04,
Keccak = 0x05,
}
impl HashFunction {
pub const fn collision_resistance(&self) -> u32 {
match self {
HashFunction::Blake3_256 => Blake3_256::COLLISION_RESISTANCE,
HashFunction::Rpo256 => Rpo256::COLLISION_RESISTANCE,
HashFunction::Rpx256 => Rpx256::COLLISION_RESISTANCE,
HashFunction::Poseidon2 => Poseidon2::COLLISION_RESISTANCE,
HashFunction::Keccak => 128,
}
}
}
impl TryFrom<u8> for HashFunction {
type Error = DeserializationError;
fn try_from(repr: u8) -> Result<Self, Self::Error> {
match repr {
0x01 => Ok(Self::Blake3_256),
0x02 => Ok(Self::Rpo256),
0x03 => Ok(Self::Rpx256),
0x04 => Ok(Self::Poseidon2),
0x05 => Ok(Self::Keccak),
_ => Err(DeserializationError::InvalidValue(format!(
"the hash function representation {repr} is not valid!"
))),
}
}
}
impl TryFrom<&str> for HashFunction {
type Error = InvalidHashFunctionError;
fn try_from(hash_fn_str: &str) -> Result<Self, Self::Error> {
match hash_fn_str {
"blake3-256" => Ok(Self::Blake3_256),
"rpo" => Ok(Self::Rpo256),
"rpx" => Ok(Self::Rpx256),
"poseidon2" => Ok(Self::Poseidon2),
"keccak" => Ok(Self::Keccak),
_ => Err(InvalidHashFunctionError { hash_function: hash_fn_str.to_string() }),
}
}
}
impl Serializable for HashFunction {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u8(*self as u8);
}
}
impl Deserializable for HashFunction {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
source.read_u8()?.try_into()
}
}
impl Serializable for ExecutionProof {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
self.proof.write_into(target);
self.hash_fn.write_into(target);
self.pc_requests.write_into(target);
}
}
impl Deserializable for ExecutionProof {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let proof = Vec::<u8>::read_from(source)?;
let hash_fn = HashFunction::read_from(source)?;
let pc_requests = Vec::<PrecompileRequest>::read_from(source)?;
Ok(ExecutionProof { proof, hash_fn, pc_requests })
}
}
#[derive(Debug, thiserror::Error)]
#[error(
"invalid hash function '{hash_function}'. Valid options are: blake3-256, rpo, rpx, poseidon2, keccak"
)]
pub struct InvalidHashFunctionError {
pub hash_function: String,
}
#[cfg(any(test, feature = "testing"))]
impl ExecutionProof {
pub fn new_dummy() -> Self {
ExecutionProof {
proof: Vec::new(),
hash_fn: HashFunction::Blake3_256,
pc_requests: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::serde::{BudgetedReader, ByteWriter, DeserializationError, SliceReader};
#[test]
fn execution_proof_rejects_over_budget_proof_len() {
let mut bytes = Vec::new();
bytes.write_usize(5);
let budget = bytes.len() + 4;
let mut reader = BudgetedReader::new(SliceReader::new(&bytes), budget);
let err = ExecutionProof::read_from(&mut reader).unwrap_err();
let DeserializationError::InvalidValue(message) = err else {
panic!("expected InvalidValue error");
};
assert!(message.contains("requested 5 elements"));
}
#[test]
fn execution_proof_rejects_over_budget_pc_requests_len() {
let mut bytes = Vec::new();
bytes.write_usize(0);
bytes.write_u8(HashFunction::Blake3_256 as u8);
bytes.write_usize(2);
let budget = bytes.len() + 1;
let mut reader = BudgetedReader::new(SliceReader::new(&bytes), budget);
let err = ExecutionProof::read_from(&mut reader).unwrap_err();
let DeserializationError::InvalidValue(message) = err else {
panic!("expected InvalidValue error");
};
assert!(message.contains("requested 2 elements"));
}
}