1use alloc::{
2 string::{String, ToString},
3 vec::Vec,
4};
5
6#[cfg(feature = "arbitrary")]
7use proptest::prelude::*;
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11use crate::{
12 crypto::hash::{Blake3_256, Poseidon2, Rpo256, Rpx256},
13 precompile::PrecompileRequest,
14 serde::{
15 BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
16 SliceReader,
17 },
18};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub struct ExecutionProof {
31 pub proof: Vec<u8>,
32 pub hash_fn: HashFunction,
33 pub pc_requests: Vec<PrecompileRequest>,
34}
35
36impl ExecutionProof {
37 pub const fn new(
43 proof: Vec<u8>,
44 hash_fn: HashFunction,
45 pc_requests: Vec<PrecompileRequest>,
46 ) -> Self {
47 Self { proof, hash_fn, pc_requests }
48 }
49
50 pub fn stark_proof(&self) -> &[u8] {
55 &self.proof
56 }
57
58 pub const fn hash_fn(&self) -> HashFunction {
60 self.hash_fn
61 }
62
63 pub fn precompile_requests(&self) -> &[PrecompileRequest] {
65 &self.pc_requests
66 }
67
68 pub fn security_level(&self) -> u32 {
76 96
77 }
78
79 pub fn to_bytes(&self) -> Vec<u8> {
84 let mut bytes = Vec::new();
85 self.write_into(&mut bytes);
86 bytes
87 }
88
89 pub fn from_bytes(source: &[u8]) -> Result<Self, DeserializationError> {
93 <Self as Deserializable>::read_from_bytes(source)
94 }
95
96 pub fn into_parts(self) -> (HashFunction, Vec<u8>, Vec<PrecompileRequest>) {
101 (self.hash_fn, self.proof, self.pc_requests)
102 }
103}
104
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
110#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
111#[cfg_attr(
112 all(feature = "arbitrary", test),
113 miden_test_serde_macros::serde_test(binary_serde(true))
114)]
115#[repr(u8)]
116pub enum HashFunction {
117 Blake3_256 = 0x01,
119 Rpo256 = 0x02,
121 Rpx256 = 0x03,
123 Poseidon2 = 0x04,
125 Keccak = 0x05,
127}
128
129impl HashFunction {
130 pub const fn collision_resistance(&self) -> u32 {
132 match self {
133 HashFunction::Blake3_256 => Blake3_256::COLLISION_RESISTANCE,
134 HashFunction::Rpo256 => Rpo256::COLLISION_RESISTANCE,
135 HashFunction::Rpx256 => Rpx256::COLLISION_RESISTANCE,
136 HashFunction::Poseidon2 => Poseidon2::COLLISION_RESISTANCE,
137 HashFunction::Keccak => 128,
138 }
139 }
140}
141
142impl TryFrom<u8> for HashFunction {
143 type Error = DeserializationError;
144
145 fn try_from(repr: u8) -> Result<Self, Self::Error> {
146 match repr {
147 0x01 => Ok(Self::Blake3_256),
148 0x02 => Ok(Self::Rpo256),
149 0x03 => Ok(Self::Rpx256),
150 0x04 => Ok(Self::Poseidon2),
151 0x05 => Ok(Self::Keccak),
152 _ => Err(DeserializationError::InvalidValue(format!(
153 "the hash function representation {repr} is not valid!"
154 ))),
155 }
156 }
157}
158
159impl TryFrom<&str> for HashFunction {
160 type Error = InvalidHashFunctionError;
161
162 fn try_from(hash_fn_str: &str) -> Result<Self, Self::Error> {
163 match hash_fn_str {
164 "blake3-256" => Ok(Self::Blake3_256),
165 "rpo" => Ok(Self::Rpo256),
166 "rpx" => Ok(Self::Rpx256),
167 "poseidon2" => Ok(Self::Poseidon2),
168 "keccak" => Ok(Self::Keccak),
169 _ => Err(InvalidHashFunctionError { hash_function: hash_fn_str.to_string() }),
170 }
171 }
172}
173
174#[cfg(feature = "arbitrary")]
175impl Arbitrary for HashFunction {
176 type Parameters = ();
177 type Strategy = BoxedStrategy<Self>;
178
179 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
180 any::<u8>()
181 .prop_map(|tag| match tag % 5 {
182 0 => Self::Blake3_256,
183 1 => Self::Rpo256,
184 2 => Self::Rpx256,
185 3 => Self::Poseidon2,
186 _ => Self::Keccak,
187 })
188 .boxed()
189 }
190}
191
192impl Serializable for HashFunction {
196 fn write_into<W: ByteWriter>(&self, target: &mut W) {
197 target.write_u8(*self as u8);
198 }
199}
200
201impl Deserializable for HashFunction {
202 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
203 source.read_u8()?.try_into()
204 }
205}
206
207impl Serializable for ExecutionProof {
208 fn write_into<W: ByteWriter>(&self, target: &mut W) {
209 self.proof.write_into(target);
210 self.hash_fn.write_into(target);
211 self.pc_requests.write_into(target);
212 }
213}
214
215impl Deserializable for ExecutionProof {
216 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
217 let proof = Vec::<u8>::read_from(source)?;
218 let hash_fn = HashFunction::read_from(source)?;
219 let pc_requests = Vec::<PrecompileRequest>::read_from(source)?;
220
221 Ok(ExecutionProof { proof, hash_fn, pc_requests })
222 }
223
224 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
225 let mut reader = BudgetedReader::new(SliceReader::new(bytes), bytes.len());
226 Self::read_from(&mut reader)
227 }
228}
229
230#[derive(Debug, thiserror::Error)]
235#[error(
236 "invalid hash function '{hash_function}'. Valid options are: blake3-256, rpo, rpx, poseidon2, keccak"
237)]
238pub struct InvalidHashFunctionError {
239 pub hash_function: String,
240}
241
242#[cfg(any(test, feature = "testing"))]
246impl ExecutionProof {
247 pub fn new_dummy() -> Self {
251 ExecutionProof {
252 proof: Vec::new(),
253 hash_fn: HashFunction::Blake3_256,
254 pc_requests: Vec::new(),
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262 use crate::{
263 events::EventId,
264 serde::{BudgetedReader, ByteWriter, DeserializationError, SliceReader},
265 };
266
267 #[test]
268 fn execution_proof_from_bytes_rejects_unbounded_proof_len() {
269 let mut bytes = Vec::new();
270 bytes.write_usize(usize::MAX);
271
272 let err = ExecutionProof::from_bytes(&bytes).unwrap_err();
273 let DeserializationError::InvalidValue(message) = err else {
274 panic!("expected InvalidValue error");
275 };
276 assert!(message.contains("requested"));
277 assert!(message.contains("reader can provide at most"));
278 }
279
280 #[test]
281 fn execution_proof_read_from_bytes_rejects_unbounded_proof_len() {
282 let mut bytes = Vec::new();
283 bytes.write_usize(usize::MAX);
284
285 let err = ExecutionProof::read_from_bytes(&bytes).unwrap_err();
286 let DeserializationError::InvalidValue(message) = err else {
287 panic!("expected InvalidValue error");
288 };
289 assert!(message.contains("requested"));
290 assert!(message.contains("reader can provide at most"));
291 }
292
293 #[test]
294 fn execution_proof_from_bytes_accepts_many_minimal_precompile_requests() {
295 let pc_requests = (0..64)
296 .map(|event_id| PrecompileRequest::new(EventId::from_u64(event_id), Vec::new()))
297 .collect();
298 let proof = ExecutionProof::new(vec![1, 2, 3], HashFunction::Blake3_256, pc_requests);
299
300 let decoded = ExecutionProof::from_bytes(&proof.to_bytes()).unwrap();
301
302 assert_eq!(decoded, proof);
303 }
304
305 #[test]
306 fn execution_proof_rejects_over_budget_proof_len() {
307 let mut bytes = Vec::new();
308 bytes.write_usize(5);
309
310 let budget = bytes.len() + 4;
311 let mut reader = BudgetedReader::new(SliceReader::new(&bytes), budget);
312 let err = ExecutionProof::read_from(&mut reader).unwrap_err();
313 let DeserializationError::InvalidValue(message) = err else {
314 panic!("expected InvalidValue error");
315 };
316 assert!(message.contains("requested 5 elements"));
317 }
318
319 #[test]
320 fn execution_proof_rejects_over_budget_pc_requests_len() {
321 let mut bytes = Vec::new();
322 bytes.write_usize(0);
323 bytes.write_u8(HashFunction::Blake3_256 as u8);
324 bytes.write_usize(2);
325
326 let budget = bytes.len() + 1;
327 let mut reader = BudgetedReader::new(SliceReader::new(&bytes), budget);
328 let err = ExecutionProof::read_from(&mut reader).unwrap_err();
329 let DeserializationError::InvalidValue(message) = err else {
330 panic!("expected InvalidValue error");
331 };
332 assert!(message.contains("requested 2 elements"));
333 }
334}