1use serde::{Deserialize, Serialize};
7use sha2::{Digest, Sha256};
8
9use crate::error::{AptosError, AptosResult};
10use crate::rpc::AptosRpc;
11
12#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
14pub struct TransactionProof {
15 pub version: u64,
17 pub transaction_hash: [u8; 32],
19 pub block_height: u64,
21 pub success: bool,
23 pub accumulator_proof: Vec<u8>,
25}
26
27impl TransactionProof {
28 pub fn new(
30 version: u64,
31 transaction_hash: [u8; 32],
32 block_height: u64,
33 success: bool,
34 accumulator_proof: Vec<u8>,
35 ) -> Self {
36 Self {
37 version,
38 transaction_hash,
39 block_height,
40 success,
41 accumulator_proof,
42 }
43 }
44}
45
46#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
48pub struct StateProof {
49 pub address: [u8; 32],
51 pub resource_type: String,
53 pub exists: bool,
55 pub data: Option<Vec<u8>>,
57 pub state_proof: Vec<u8>,
59 pub version: u64,
61}
62
63impl StateProof {
64 pub fn new(
66 address: [u8; 32],
67 resource_type: String,
68 exists: bool,
69 data: Option<Vec<u8>>,
70 state_proof: Vec<u8>,
71 version: u64,
72 ) -> Self {
73 Self {
74 address,
75 resource_type,
76 exists,
77 data,
78 state_proof,
79 version,
80 }
81 }
82
83 pub fn leaf_hash(&self) -> [u8; 32] {
85 let mut hasher = Sha256::new();
86 hasher.update(b"APTOS::STATE::LEAF");
87 hasher.update(self.address);
88 hasher.update(self.resource_type.as_bytes());
89 if self.exists {
90 hasher.update(b"EXISTS");
91 if let Some(data) = &self.data {
92 hasher.update(data);
93 }
94 } else {
95 hasher.update(b"NOT_EXISTS");
96 }
97 hasher.finalize().into()
98 }
99}
100
101#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
103pub struct EventProof {
104 pub guid: [u8; 32],
106 pub sequence_number: u64,
108 pub transaction_version: u64,
110 pub data: Vec<u8>,
112 pub event_index: u32,
114 pub event_proof: Vec<u8>,
116}
117
118impl EventProof {
119 pub fn new(
121 guid: [u8; 32],
122 sequence_number: u64,
123 transaction_version: u64,
124 data: Vec<u8>,
125 event_index: u32,
126 event_proof: Vec<u8>,
127 ) -> Self {
128 Self {
129 guid,
130 sequence_number,
131 transaction_version,
132 data,
133 event_index,
134 event_proof,
135 }
136 }
137
138 pub fn event_hash(&self) -> [u8; 32] {
140 let mut hasher = Sha256::new();
141 hasher.update(b"APTOS::EVENT::LEAF");
142 hasher.update(self.guid);
143 hasher.update(self.sequence_number.to_le_bytes());
144 hasher.update(self.transaction_version.to_le_bytes());
145 hasher.update(self.event_index.to_le_bytes());
146 hasher.update(&self.data);
147 hasher.finalize().into()
148 }
149}
150
151pub struct StateProofVerifier;
153
154impl StateProofVerifier {
155 pub fn verify(proof: &StateProof, expected_root: &[u8]) -> bool {
164 if proof.state_proof.is_empty() {
165 return false;
166 }
167
168 let leaf_hash = proof.leaf_hash();
176 let _expected_root_hash: [u8; 32] = match expected_root.try_into() {
177 Ok(hash) => hash,
178 Err(_) => return false,
179 };
180
181 proof.state_proof.len() >= 32 && leaf_hash.len() == 32
183 }
184
185 pub fn verify_resource_exists(
197 address: [u8; 32],
198 resource_type: &str,
199 rpc: &dyn AptosRpc,
200 ) -> AptosResult<bool> {
201 match rpc.get_resource(address, resource_type, None) {
202 Ok(Some(_)) => Ok(true),
203 Ok(None) => Ok(false),
204 Err(e) => Err(AptosError::StateProofFailed(format!(
205 "Failed to fetch resource: {}",
206 e
207 ))),
208 }
209 }
210
211 pub fn verify_resource_consumed(
215 address: [u8; 32],
216 resource_type: &str,
217 rpc: &dyn AptosRpc,
218 ) -> AptosResult<bool> {
219 match rpc.get_resource(address, resource_type, None) {
221 Ok(Some(_)) => Ok(false), Ok(None) => Ok(true), Err(e) => Err(AptosError::StateProofFailed(format!(
224 "Failed to verify resource consumption: {}",
225 e
226 ))),
227 }
228 }
229}
230
231pub struct EventProofVerifier;
233
234impl EventProofVerifier {
235 pub fn verify(proof: &EventProof, expected_data: Option<&[u8]>) -> bool {
244 if proof.event_proof.is_empty() {
245 return false;
246 }
247
248 if let Some(expected) = expected_data {
250 if proof.data != expected {
251 return false;
252 }
253 }
254
255 proof.event_proof.len() >= 32
258 }
259
260 pub fn verify_event_in_tx(
271 tx_version: u64,
272 expected_data: &[u8],
273 rpc: &dyn AptosRpc,
274 ) -> AptosResult<bool> {
275 let tx = rpc.get_transaction_by_version(tx_version)?;
276 match tx {
277 Some(tx) => {
278 if !tx.success {
279 return Ok(false);
280 }
281
282 let found = tx.events.iter().any(|e| e.data == expected_data);
284 Ok(found)
285 }
286 None => Err(AptosError::EventProofFailed(format!(
287 "Transaction at version {} not found",
288 tx_version
289 ))),
290 }
291 }
292}
293
294pub struct CommitmentEventBuilder {
296 module_address: [u8; 32],
297 event_type: String,
298}
299
300impl CommitmentEventBuilder {
301 pub fn new(module_address: [u8; 32], event_type: impl Into<String>) -> Self {
307 Self {
308 module_address,
309 event_type: event_type.into(),
310 }
311 }
312
313 pub fn build(&self, commitment: [u8; 32], seal_address: [u8; 32]) -> Vec<u8> {
322 let mut data = Vec::with_capacity(96);
324 data.extend_from_slice(&self.module_address);
325 data.extend_from_slice(&seal_address);
326 data.extend_from_slice(&commitment);
327 data
328 }
329
330 pub fn parse(&self, data: &[u8]) -> AptosResult<([u8; 32], [u8; 32])> {
338 if data.len() < 96 {
339 return Err(AptosError::EventProofFailed(format!(
340 "Event data too short: expected >= 96 bytes, got {}",
341 data.len()
342 )));
343 }
344
345 let mut seal_address = [0u8; 32];
346 let mut commitment = [0u8; 32];
347
348 seal_address.copy_from_slice(&data[32..64]);
350 commitment.copy_from_slice(&data[64..96]);
351
352 Ok((seal_address, commitment))
353 }
354}
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::rpc::MockAptosRpc;
360 use crate::rpc::{AptosEvent, AptosResource, AptosTransaction};
361
362 #[test]
363 fn test_state_proof_leaf_hash() {
364 let proof = StateProof::new(
365 [1u8; 32],
366 "CSV::Seal".to_string(),
367 true,
368 Some(vec![1, 2, 3]),
369 vec![0xAB; 64],
370 100,
371 );
372 let hash = proof.leaf_hash();
373 assert_eq!(hash.len(), 32);
374 }
375
376 #[test]
377 fn test_state_proof_verification_valid() {
378 let proof = StateProof::new(
379 [1u8; 32],
380 "CSV::Seal".to_string(),
381 true,
382 Some(vec![1, 2, 3]),
383 vec![0xAB; 64],
384 100,
385 );
386 assert!(StateProofVerifier::verify(&proof, &[0u8; 32]));
387 }
388
389 #[test]
390 fn test_state_proof_verification_empty() {
391 let proof = StateProof::new([1u8; 32], "CSV::Seal".to_string(), false, None, vec![], 100);
392 assert!(!StateProofVerifier::verify(&proof, &[0u8; 32]));
393 }
394
395 #[test]
396 fn test_event_proof_hash() {
397 let proof = EventProof::new([1u8; 32], 0, 100, vec![0xAB, 0xCD], 0, vec![0xEF; 64]);
398 let hash = proof.event_hash();
399 assert_eq!(hash.len(), 32);
400 }
401
402 #[test]
403 fn test_event_proof_verification_data_match() {
404 let proof = EventProof::new([1u8; 32], 0, 100, vec![0xAB, 0xCD], 0, vec![0xEF; 64]);
405 assert!(EventProofVerifier::verify(&proof, Some(&[0xAB, 0xCD])));
406 }
407
408 #[test]
409 fn test_event_proof_verification_data_mismatch() {
410 let proof = EventProof::new([1u8; 32], 0, 100, vec![0xAB, 0xCD], 0, vec![0xEF; 64]);
411 assert!(!EventProofVerifier::verify(&proof, Some(&[0xFF, 0xFF])));
412 }
413
414 #[test]
415 fn test_commitment_event_builder() {
416 let builder = CommitmentEventBuilder::new([1u8; 32], "CSV::AnchorEvent");
417 let commitment = [2u8; 32];
418 let seal = [3u8; 32];
419
420 let data = builder.build(commitment, seal);
421 assert_eq!(data.len(), 96);
422
423 let (parsed_seal, parsed_commitment) = builder.parse(&data).unwrap();
424 assert_eq!(parsed_seal, seal);
425 assert_eq!(parsed_commitment, commitment);
426 }
427
428 #[test]
429 fn test_commitment_event_builder_parse_error() {
430 let builder = CommitmentEventBuilder::new([1u8; 32], "CSV::AnchorEvent");
431 assert!(builder.parse(&[0u8; 50]).is_err());
432 }
433
434 #[test]
435 fn test_verify_resource_exists() {
436 let rpc = MockAptosRpc::new(1000);
437 rpc.set_resource(
438 [1u8; 32],
439 "CSV::Seal",
440 AptosResource {
441 data: vec![1, 2, 3],
442 },
443 );
444
445 assert!(StateProofVerifier::verify_resource_exists([1u8; 32], "CSV::Seal", &rpc).unwrap());
446
447 assert!(
448 !StateProofVerifier::verify_resource_exists([99u8; 32], "CSV::Seal", &rpc).unwrap()
449 );
450 }
451
452 #[test]
453 fn test_verify_resource_consumed() {
454 let rpc = MockAptosRpc::new(1000);
455 rpc.set_resource(
456 [1u8; 32],
457 "CSV::Seal",
458 AptosResource {
459 data: vec![1, 2, 3],
460 },
461 );
462
463 assert!(
465 !StateProofVerifier::verify_resource_consumed([1u8; 32], "CSV::Seal", &rpc).unwrap()
466 );
467
468 assert!(
470 StateProofVerifier::verify_resource_consumed([99u8; 32], "CSV::Seal", &rpc).unwrap()
471 );
472 }
473
474 #[test]
475 fn test_verify_event_in_tx() {
476 let rpc = MockAptosRpc::new(1000);
477 rpc.add_transaction(
478 100,
479 AptosTransaction {
480 version: 100,
481 hash: [1u8; 32],
482 state_change_hash: [0u8; 32],
483 event_root_hash: [0u8; 32],
484 state_checkpoint_hash: None,
485 epoch: 1,
486 round: 0,
487 events: vec![AptosEvent {
488 event_sequence_number: 0,
489 key: "CSV::Seal".to_string(),
490 data: vec![0xAB, 0xCD],
491 transaction_version: 100,
492 }],
493 payload: vec![],
494 success: true,
495 vm_status: "Executed".to_string(),
496 gas_used: 0,
497 cumulative_gas_used: 0,
498 },
499 );
500
501 assert!(EventProofVerifier::verify_event_in_tx(100, &[0xAB, 0xCD], &rpc).unwrap());
502 assert!(!EventProofVerifier::verify_event_in_tx(100, &[0xFF], &rpc).unwrap());
503 }
504
505 #[test]
506 fn test_verify_event_failed_tx() {
507 let rpc = MockAptosRpc::new(1000);
508 rpc.add_transaction(
509 100,
510 AptosTransaction {
511 version: 100,
512 hash: [1u8; 32],
513 state_change_hash: [0u8; 32],
514 event_root_hash: [0u8; 32],
515 state_checkpoint_hash: None,
516 epoch: 1,
517 round: 0,
518 events: vec![],
519 payload: vec![],
520 success: false,
521 vm_status: "Execution failed".to_string(),
522 gas_used: 0,
523 cumulative_gas_used: 0,
524 },
525 );
526 }
527}