Skip to main content

csv_adapter_core/
proof.rs

1//! Proof bundle types for off-chain verification
2//!
3//! Proof bundles are exchanged between peers for verification.
4
5use alloc::vec::Vec;
6use serde::{Deserialize, Serialize};
7
8use crate::dag::DAGSegment;
9use crate::hash::Hash;
10use crate::seal::{AnchorRef, SealRef};
11
12/// Maximum allowed size for proof bytes (64KB)
13pub const MAX_PROOF_BYTES: usize = 64 * 1024;
14
15/// Maximum allowed size for finality data (4KB)
16pub const MAX_FINALITY_DATA: usize = 4 * 1024;
17
18/// Maximum allowed size for signatures in a bundle (1MB total)
19pub const MAX_SIGNATURES_TOTAL_SIZE: usize = 1024 * 1024;
20
21/// Inclusion proof material
22#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
23pub struct InclusionProof {
24    /// Merkle proof or equivalent
25    pub proof_bytes: Vec<u8>,
26    /// Block hash containing the commitment
27    pub block_hash: Hash,
28    /// Position in block (for verification)
29    pub position: u64,
30}
31
32impl InclusionProof {
33    /// Create a new inclusion proof
34    ///
35    /// # Arguments
36    /// * `proof_bytes` - Merkle proof or equivalent (max 64KB)
37    /// * `block_hash` - Block hash containing the commitment
38    /// * `position` - Position in block (for verification)
39    ///
40    /// # Errors
41    /// Returns an error if proof_bytes exceeds the maximum allowed size
42    pub fn new(
43        proof_bytes: Vec<u8>,
44        block_hash: Hash,
45        position: u64,
46    ) -> Result<Self, &'static str> {
47        if proof_bytes.len() > MAX_PROOF_BYTES {
48            return Err("proof_bytes exceeds maximum allowed size (64KB)");
49        }
50        Ok(Self {
51            proof_bytes,
52            block_hash,
53            position,
54        })
55    }
56
57    /// Create a new inclusion proof without validation.
58    ///
59    /// # Safety
60    /// This bypasses size and structure validation. Use only for internal protocol conversions
61    /// where the input is already known to be valid.
62    pub fn new_unchecked(proof_bytes: Vec<u8>, block_hash: Hash, position: u64) -> Self {
63        Self {
64            proof_bytes,
65            block_hash,
66            position,
67        }
68    }
69
70    /// Check if confirmed with given depth
71    pub fn is_confirmed(&self, _required_depth: u32) -> bool {
72        // Placeholder - adapters implement chain-specific logic
73        true
74    }
75}
76
77/// Finality proof material
78#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
79pub struct FinalityProof {
80    /// Finality checkpoint or depth
81    pub finality_data: Vec<u8>,
82    /// Number of confirmations or equivalent
83    pub confirmations: u64,
84    /// Whether finality is deterministic (vs probabilistic)
85    pub is_deterministic: bool,
86}
87
88impl FinalityProof {
89    /// Create a new finality proof
90    ///
91    /// # Arguments
92    /// * `finality_data` - Finality checkpoint or depth (max 4KB)
93    /// * `confirmations` - Number of confirmations or equivalent
94    /// * `is_deterministic` - Whether finality is deterministic (vs probabilistic)
95    ///
96    /// # Errors
97    /// Returns an error if finality_data exceeds the maximum allowed size
98    pub fn new(
99        finality_data: Vec<u8>,
100        confirmations: u64,
101        is_deterministic: bool,
102    ) -> Result<Self, &'static str> {
103        if finality_data.len() > MAX_FINALITY_DATA {
104            return Err("finality_data exceeds maximum allowed size (4KB)");
105        }
106        Ok(Self {
107            finality_data,
108            confirmations,
109            is_deterministic,
110        })
111    }
112
113    /// Create a new $1 without validation.
114    ///
115    /// # Safety
116    /// This bypasses validation. Use only for internal protocol conversions.
117    pub fn new_unchecked(
118        finality_data: Vec<u8>,
119        confirmations: u64,
120        is_deterministic: bool,
121    ) -> Self {
122        Self {
123            finality_data,
124            confirmations,
125            is_deterministic,
126        }
127    }
128}
129
130/// Complete proof bundle for peer-to-peer verification
131#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
132pub struct ProofBundle {
133    /// State transition DAG segment
134    pub transition_dag: DAGSegment,
135    /// Authorizing signatures
136    pub signatures: Vec<Vec<u8>>,
137    /// Seal reference
138    pub seal_ref: SealRef,
139    /// Anchor reference
140    pub anchor_ref: AnchorRef,
141    /// Inclusion proof
142    pub inclusion_proof: InclusionProof,
143    /// Finality proof
144    pub finality_proof: FinalityProof,
145}
146
147impl ProofBundle {
148    /// Create a new proof bundle
149    ///
150    /// # Arguments
151    /// * `transition_dag` - State transition DAG segment
152    /// * `signatures` - Authorizing signatures (total max 1MB)
153    /// * `seal_ref` - Seal reference
154    /// * `anchor_ref` - Anchor reference
155    /// * `inclusion_proof` - Inclusion proof
156    /// * `finality_proof` - Finality proof
157    ///
158    /// # Errors
159    /// Returns an error if signatures exceed the maximum total size
160    pub fn new(
161        transition_dag: DAGSegment,
162        signatures: Vec<Vec<u8>>,
163        seal_ref: SealRef,
164        anchor_ref: AnchorRef,
165        inclusion_proof: InclusionProof,
166        finality_proof: FinalityProof,
167    ) -> Result<Self, &'static str> {
168        // Validate total signature size
169        let total_sig_size: usize = signatures.iter().map(|s| s.len()).sum();
170        if total_sig_size > MAX_SIGNATURES_TOTAL_SIZE {
171            return Err("total signatures size exceeds maximum allowed (1MB)");
172        }
173        Ok(Self {
174            transition_dag,
175            signatures,
176            seal_ref,
177            anchor_ref,
178            inclusion_proof,
179            finality_proof,
180        })
181    }
182
183    /// Create a new $1 without validation.
184    ///
185    /// # Safety
186    /// This bypasses validation. Use only for internal protocol conversions.
187    pub fn new_unchecked(
188        transition_dag: DAGSegment,
189        signatures: Vec<Vec<u8>>,
190        seal_ref: SealRef,
191        anchor_ref: AnchorRef,
192        inclusion_proof: InclusionProof,
193        finality_proof: FinalityProof,
194    ) -> Self {
195        Self {
196            transition_dag,
197            signatures,
198            seal_ref,
199            anchor_ref,
200            inclusion_proof,
201            finality_proof,
202        }
203    }
204
205    /// Serialize the proof bundle
206    pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
207        bincode::serialize(self)
208    }
209
210    /// Deserialize the proof bundle with size limit (10MB max)
211    pub fn from_bytes(bytes: &[u8]) -> Result<Self, bincode::Error> {
212        const MAX_SIZE: usize = 10 * 1024 * 1024; // 10MB
213        if bytes.len() > MAX_SIZE {
214            return Err(bincode::ErrorKind::Custom(format!(
215                "ProofBundle too large: {} bytes (max {})",
216                bytes.len(),
217                MAX_SIZE
218            ))
219            .into());
220        }
221        bincode::deserialize(bytes)
222    }
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn test_inclusion_proof_creation() {
231        let proof = InclusionProof::new(vec![0xAB; 64], Hash::new([1u8; 32]), 42).unwrap();
232        assert_eq!(proof.position, 42);
233    }
234
235    #[test]
236    fn test_finality_proof_creation() {
237        let proof = FinalityProof::new(vec![0xCD; 32], 6, false).unwrap();
238        assert_eq!(proof.confirmations, 6);
239        assert!(!proof.is_deterministic);
240    }
241
242    #[test]
243    fn test_proof_bundle_serialization() {
244        let bundle = ProofBundle::new(
245            DAGSegment::new(vec![], Hash::zero()),
246            vec![vec![0xAB; 64]],
247            SealRef::new(vec![1, 2, 3], Some(42)).unwrap(),
248            AnchorRef::new(vec![4, 5, 6], 100, vec![]).unwrap(),
249            InclusionProof::new(vec![], Hash::zero(), 0).unwrap(),
250            FinalityProof::new(vec![], 6, false).unwrap(),
251        )
252        .unwrap();
253
254        let bytes = bundle.to_bytes().unwrap();
255        let restored = ProofBundle::from_bytes(&bytes).unwrap();
256        assert_eq!(bundle, restored);
257    }
258
259    #[test]
260    fn test_inclusion_proof_too_large() {
261        let large_proof = vec![0u8; MAX_PROOF_BYTES + 1];
262        let result = InclusionProof::new(large_proof, Hash::zero(), 0);
263        assert!(result.is_err());
264    }
265
266    #[test]
267    fn test_finality_proof_too_large() {
268        let large_data = vec![0u8; MAX_FINALITY_DATA + 1];
269        let result = FinalityProof::new(large_data, 6, false);
270        assert!(result.is_err());
271    }
272
273    #[test]
274    fn test_proof_bundle_signatures_too_large() {
275        let large_sigs = vec![vec![0u8; MAX_SIGNATURES_TOTAL_SIZE / 2 + 1]; 2];
276        let result = ProofBundle::new(
277            DAGSegment::new(vec![], Hash::zero()),
278            large_sigs,
279            SealRef::new(vec![1, 2, 3], Some(42)).unwrap(),
280            AnchorRef::new(vec![4, 5, 6], 100, vec![]).unwrap(),
281            InclusionProof::new(vec![], Hash::zero(), 0).unwrap(),
282            FinalityProof::new(vec![], 6, false).unwrap(),
283        );
284        assert!(result.is_err());
285    }
286}