shape_runtime/
execution_proof.rs1use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8use std::collections::HashMap;
9use std::time::{SystemTime, UNIX_EPOCH};
10
11pub fn hash_bytes(data: &[u8]) -> [u8; 32] {
13 let digest = Sha256::digest(data);
14 let mut out = [0u8; 32];
15 out.copy_from_slice(&digest);
16 out
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct ExecutionProof {
22 pub function_hash: [u8; 32],
24 pub args_hash: [u8; 32],
26 pub result_hash: [u8; 32],
28 pub timestamp: u64,
30 pub trace: Option<Vec<[u8; 32]>>,
32 pub proof_hash: [u8; 32],
34}
35
36impl ExecutionProof {
37 pub fn compute_proof_hash(
39 function_hash: &[u8; 32],
40 args_hash: &[u8; 32],
41 result_hash: &[u8; 32],
42 timestamp: u64,
43 trace: &Option<Vec<[u8; 32]>>,
44 ) -> [u8; 32] {
45 let mut hasher = Sha256::new();
46 hasher.update(function_hash);
47 hasher.update(args_hash);
48 hasher.update(result_hash);
49 hasher.update(timestamp.to_le_bytes());
50 if let Some(entries) = trace {
51 hasher.update((entries.len() as u64).to_le_bytes());
53 for entry in entries {
54 hasher.update(entry);
55 }
56 } else {
57 hasher.update([0xff; 8]);
59 }
60 let digest = hasher.finalize();
61 let mut out = [0u8; 32];
62 out.copy_from_slice(&digest);
63 out
64 }
65
66 pub fn verify_integrity(&self) -> bool {
68 let expected = Self::compute_proof_hash(
69 &self.function_hash,
70 &self.args_hash,
71 &self.result_hash,
72 self.timestamp,
73 &self.trace,
74 );
75 expected == self.proof_hash
76 }
77}
78
79pub struct ExecutionProofBuilder {
81 function_hash: [u8; 32],
82 args_hash: Option<[u8; 32]>,
83 trace: Vec<[u8; 32]>,
84 capture_trace: bool,
85}
86
87impl ExecutionProofBuilder {
88 pub fn new(function_hash: [u8; 32]) -> Self {
90 Self {
91 function_hash,
92 args_hash: None,
93 trace: Vec::new(),
94 capture_trace: false,
95 }
96 }
97
98 pub fn with_trace(mut self) -> Self {
100 self.capture_trace = true;
101 self
102 }
103
104 pub fn set_args_hash(&mut self, hash: [u8; 32]) {
106 self.args_hash = Some(hash);
107 }
108
109 pub fn record_trace_step(&mut self, state_hash: [u8; 32]) {
113 if self.capture_trace {
114 self.trace.push(state_hash);
115 }
116 }
117
118 pub fn finalize(self, result_hash: [u8; 32]) -> ExecutionProof {
121 let args_hash = self.args_hash.unwrap_or([0u8; 32]);
122 let timestamp = SystemTime::now()
123 .duration_since(UNIX_EPOCH)
124 .map(|d| d.as_secs())
125 .unwrap_or(0);
126 let trace = if self.capture_trace {
127 Some(self.trace)
128 } else {
129 None
130 };
131 let proof_hash = ExecutionProof::compute_proof_hash(
132 &self.function_hash,
133 &args_hash,
134 &result_hash,
135 timestamp,
136 &trace,
137 );
138 ExecutionProof {
139 function_hash: self.function_hash,
140 args_hash,
141 result_hash,
142 timestamp,
143 trace,
144 proof_hash,
145 }
146 }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151pub enum VerificationResult {
152 Valid,
154 InvalidProofHash,
156 ResultMismatch {
158 expected: [u8; 32],
159 actual: [u8; 32],
160 },
161 TraceMismatch { step: usize },
163}
164
165pub struct ProofRegistry {
167 proofs: Vec<ExecutionProof>,
168 by_function: HashMap<[u8; 32], Vec<usize>>,
169}
170
171impl ProofRegistry {
172 pub fn new() -> Self {
174 Self {
175 proofs: Vec::new(),
176 by_function: HashMap::new(),
177 }
178 }
179
180 pub fn register(&mut self, proof: ExecutionProof) -> usize {
182 let idx = self.proofs.len();
183 self.by_function
184 .entry(proof.function_hash)
185 .or_default()
186 .push(idx);
187 self.proofs.push(proof);
188 idx
189 }
190
191 pub fn lookup(&self, function_hash: &[u8; 32]) -> &[ExecutionProof] {
193 match self.by_function.get(function_hash) {
194 Some(indices) => {
195 if indices.is_empty() {
202 return &[];
203 }
204 let first = indices[0];
205 let last = *indices.last().unwrap();
206 if last - first + 1 == indices.len() {
208 &self.proofs[first..=last]
209 } else {
210 &[]
212 }
213 }
214 None => &[],
215 }
216 }
217
218 pub fn lookup_iter<'a>(
220 &'a self,
221 function_hash: &[u8; 32],
222 ) -> impl Iterator<Item = &'a ExecutionProof> {
223 let indices = self.by_function.get(function_hash);
224 indices
225 .into_iter()
226 .flat_map(|v| v.iter())
227 .map(move |&idx| &self.proofs[idx])
228 }
229
230 pub fn verify_all(&self) -> Vec<(usize, VerificationResult)> {
235 let mut failures = Vec::new();
236 for (i, proof) in self.proofs.iter().enumerate() {
237 if !proof.verify_integrity() {
238 failures.push((i, VerificationResult::InvalidProofHash));
239 }
240 }
241 failures
242 }
243
244 pub fn len(&self) -> usize {
246 self.proofs.len()
247 }
248
249 pub fn is_empty(&self) -> bool {
251 self.proofs.is_empty()
252 }
253}
254
255impl Default for ProofRegistry {
256 fn default() -> Self {
257 Self::new()
258 }
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 #[test]
266 fn test_hash_bytes() {
267 let h = hash_bytes(b"hello");
268 assert_eq!(
270 hex::encode(h),
271 "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824"
272 );
273 }
274
275 #[test]
276 fn test_proof_integrity_valid() {
277 let func_hash = hash_bytes(b"my_function");
278 let args_hash = hash_bytes(b"args");
279 let result_hash = hash_bytes(b"result");
280
281 let mut builder = ExecutionProofBuilder::new(func_hash);
282 builder.set_args_hash(args_hash);
283 let proof = builder.finalize(result_hash);
284
285 assert!(proof.verify_integrity());
286 }
287
288 #[test]
289 fn test_proof_integrity_tampered() {
290 let func_hash = hash_bytes(b"my_function");
291 let args_hash = hash_bytes(b"args");
292 let result_hash = hash_bytes(b"result");
293
294 let mut builder = ExecutionProofBuilder::new(func_hash);
295 builder.set_args_hash(args_hash);
296 let mut proof = builder.finalize(result_hash);
297
298 proof.result_hash = hash_bytes(b"different_result");
300 assert!(!proof.verify_integrity());
301 }
302
303 #[test]
304 fn test_proof_with_trace() {
305 let func_hash = hash_bytes(b"traced_fn");
306 let args_hash = hash_bytes(b"args");
307
308 let mut builder = ExecutionProofBuilder::new(func_hash).with_trace();
309 builder.set_args_hash(args_hash);
310 builder.record_trace_step(hash_bytes(b"state_1"));
311 builder.record_trace_step(hash_bytes(b"state_2"));
312
313 let result_hash = hash_bytes(b"final");
314 let proof = builder.finalize(result_hash);
315
316 assert!(proof.verify_integrity());
317 assert!(proof.trace.is_some());
318 assert_eq!(proof.trace.as_ref().unwrap().len(), 2);
319 }
320
321 #[test]
322 fn test_trace_disabled_by_default() {
323 let mut builder = ExecutionProofBuilder::new([0u8; 32]);
324 builder.record_trace_step(hash_bytes(b"ignored"));
325 let proof = builder.finalize([1u8; 32]);
326
327 assert!(proof.trace.is_none());
328 }
329
330 #[test]
331 fn test_registry_register_and_lookup() {
332 let mut registry = ProofRegistry::new();
333 let func_hash = hash_bytes(b"fn1");
334
335 let mut b = ExecutionProofBuilder::new(func_hash);
336 b.set_args_hash(hash_bytes(b"a1"));
337 let p1 = b.finalize(hash_bytes(b"r1"));
338
339 let mut b2 = ExecutionProofBuilder::new(func_hash);
340 b2.set_args_hash(hash_bytes(b"a2"));
341 let p2 = b2.finalize(hash_bytes(b"r2"));
342
343 registry.register(p1);
344 registry.register(p2);
345
346 assert_eq!(registry.len(), 2);
347 let results: Vec<_> = registry.lookup_iter(&func_hash).collect();
348 assert_eq!(results.len(), 2);
349 }
350
351 #[test]
352 fn test_registry_verify_all_clean() {
353 let mut registry = ProofRegistry::new();
354 let mut b = ExecutionProofBuilder::new(hash_bytes(b"f"));
355 b.set_args_hash(hash_bytes(b"a"));
356 registry.register(b.finalize(hash_bytes(b"r")));
357
358 let failures = registry.verify_all();
359 assert!(failures.is_empty());
360 }
361
362 #[test]
363 fn test_registry_lookup_missing() {
364 let registry = ProofRegistry::new();
365 let missing = hash_bytes(b"nonexistent");
366 assert!(registry.lookup(&missing).is_empty());
367 assert_eq!(registry.lookup_iter(&missing).count(), 0);
368 }
369}