Skip to main content

csv_adapter_core/
seal.rs

1//! Seal and Anchor reference types
2//!
3//! Seals represent single-use rights to authorize state transitions.
4//! Anchors represent on-chain references containing commitments.
5
6use alloc::vec::Vec;
7use serde::{Deserialize, Serialize};
8
9/// Maximum allowed size for seal identifiers (1KB)
10pub const MAX_SEAL_ID_SIZE: usize = 1024;
11
12/// Maximum allowed size for anchor identifiers (1KB)
13pub const MAX_ANCHOR_ID_SIZE: usize = 1024;
14
15/// Maximum allowed size for anchor metadata (4KB)
16pub const MAX_ANCHOR_METADATA_SIZE: usize = 4096;
17
18/// A reference to a single-use seal
19///
20/// The concrete meaning is chain-specific:
21/// - Bitcoin: UTXO OutPoint
22/// - Ethereum: Contract address + storage slot
23/// - Sui: Object ID
24/// - Aptos: Resource address + key
25#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct SealRef {
27    /// Chain-specific seal identifier
28    pub seal_id: Vec<u8>,
29    /// Optional nonce for replay resistance
30    pub nonce: Option<u64>,
31}
32
33impl SealRef {
34    /// Create a new SealRef from raw bytes
35    ///
36    /// # Arguments
37    /// * `seal_id` - Chain-specific seal identifier (max 1KB)
38    /// * `nonce` - Optional nonce for replay resistance
39    ///
40    /// # Errors
41    /// Returns an error if the seal_id exceeds the maximum allowed size
42    pub fn new(seal_id: Vec<u8>, nonce: Option<u64>) -> Result<Self, &'static str> {
43        if seal_id.len() > MAX_SEAL_ID_SIZE {
44            return Err("seal_id exceeds maximum allowed size (1KB)");
45        }
46        if seal_id.is_empty() {
47            return Err("seal_id cannot be empty");
48        }
49        Ok(Self { seal_id, nonce })
50    }
51
52    /// Create a new SealRef without validation.
53    ///
54    /// # Safety
55    /// This bypasses size validation. Use only for internal protocol conversions
56    /// where the input is already known to be valid.
57    pub fn new_unchecked(seal_id: Vec<u8>, nonce: Option<u64>) -> Self {
58        Self { seal_id, nonce }
59    }
60
61    /// Serialize to bytes
62    ///
63    /// Format: `[nonce_flag(1) | nonce_bytes(8 if flag=1) | seal_id_len(varuint) | seal_id]`
64    /// The nonce_flag is 1 for `Some(nonce)`, 0 for `None`.
65    pub fn to_vec(&self) -> Vec<u8> {
66        let mut out = Vec::with_capacity(9 + self.seal_id.len());
67        if let Some(nonce) = self.nonce {
68            out.push(1);
69            out.extend_from_slice(&nonce.to_le_bytes());
70        } else {
71            out.push(0);
72        }
73        out.extend_from_slice(&(self.seal_id.len() as u32).to_le_bytes());
74        out.extend_from_slice(&self.seal_id);
75        out
76    }
77
78    /// Deserialize from bytes
79    ///
80    /// # Errors
81    /// Returns an error if the bytes are malformed or seal_id exceeds the maximum allowed size.
82    pub fn from_bytes(bytes: &[u8]) -> Result<Self, &'static str> {
83        if bytes.is_empty() {
84            return Err("empty bytes");
85        }
86        let mut pos = 0;
87
88        let nonce = match bytes[pos] {
89            0 => {
90                pos += 1;
91                None
92            }
93            1 => {
94                pos += 1;
95                if bytes.len() < pos + 8 {
96                    return Err("truncated nonce");
97                }
98                let nonce_bytes: [u8; 8] = bytes[pos..pos + 8]
99                    .try_into()
100                    .map_err(|_| "truncated nonce")?;
101                pos += 8;
102                Some(u64::from_le_bytes(nonce_bytes))
103            }
104            _ => return Err("invalid nonce flag"),
105        };
106
107        if bytes.len() < pos + 4 {
108            return Err("truncated seal_id length");
109        }
110        let seal_id_len = u32::from_le_bytes(
111            bytes[pos..pos + 4]
112                .try_into()
113                .map_err(|_| "truncated seal_id length")?,
114        ) as usize;
115        pos += 4;
116
117        if seal_id_len > MAX_SEAL_ID_SIZE {
118            return Err("seal_id exceeds maximum allowed size (1KB)");
119        }
120        if seal_id_len == 0 {
121            return Err("seal_id cannot be empty");
122        }
123        if bytes.len() < pos + seal_id_len {
124            return Err("truncated seal_id");
125        }
126        let seal_id = bytes[pos..pos + seal_id_len].to_vec();
127
128        Ok(Self { seal_id, nonce })
129    }
130}
131
132/// A reference to an on-chain anchor containing a commitment
133///
134/// The concrete meaning is chain-specific:
135/// - Bitcoin: Transaction ID + output index
136/// - Ethereum: Transaction hash + log index
137/// - Sui: Object ID + version
138#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
139pub struct AnchorRef {
140    /// Chain-specific anchor identifier
141    pub anchor_id: Vec<u8>,
142    /// Block height or equivalent ordering
143    pub block_height: u64,
144    /// Optional chain-specific metadata
145    pub metadata: Vec<u8>,
146}
147
148impl AnchorRef {
149    /// Create a new AnchorRef
150    ///
151    /// # Arguments
152    /// * `anchor_id` - Chain-specific anchor identifier (max 1KB)
153    /// * `block_height` - Block height or equivalent ordering
154    /// * `metadata` - Optional chain-specific metadata (max 4KB)
155    ///
156    /// # Errors
157    /// Returns an error if anchor_id or metadata exceeds the maximum allowed size
158    pub fn new(
159        anchor_id: Vec<u8>,
160        block_height: u64,
161        metadata: Vec<u8>,
162    ) -> Result<Self, &'static str> {
163        if anchor_id.len() > MAX_ANCHOR_ID_SIZE {
164            return Err("anchor_id exceeds maximum allowed size (1KB)");
165        }
166        if anchor_id.is_empty() {
167            return Err("anchor_id cannot be empty");
168        }
169        if metadata.len() > MAX_ANCHOR_METADATA_SIZE {
170            return Err("metadata exceeds maximum allowed size (4KB)");
171        }
172        Ok(Self {
173            anchor_id,
174            block_height,
175            metadata,
176        })
177    }
178
179    /// Create a new $1 without validation.
180    ///
181    /// # Safety
182    /// This bypasses validation. Use only for internal protocol conversions.
183    ///
184    /// # Safety
185    /// This bypasses size validation and should only be used when
186    /// the input is already known to be valid.
187    pub fn new_unchecked(anchor_id: Vec<u8>, block_height: u64, metadata: Vec<u8>) -> Self {
188        Self {
189            anchor_id,
190            block_height,
191            metadata,
192        }
193    }
194
195    /// Serialize to bytes
196    pub fn to_vec(&self) -> Vec<u8> {
197        let mut out = Vec::with_capacity(8 + self.anchor_id.len() + self.metadata.len());
198        out.extend_from_slice(&self.block_height.to_le_bytes());
199        out.extend_from_slice(&self.anchor_id);
200        out.extend_from_slice(&self.metadata);
201        out
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    #[test]
210    fn test_seal_ref_creation() {
211        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
212        assert_eq!(seal.seal_id, vec![1, 2, 3]);
213        assert_eq!(seal.nonce, Some(42));
214    }
215
216    #[test]
217    fn test_anchor_ref_creation() {
218        let anchor = AnchorRef::new(vec![4, 5, 6], 100, vec![7, 8]).unwrap();
219        assert_eq!(anchor.anchor_id, vec![4, 5, 6]);
220        assert_eq!(anchor.block_height, 100);
221        assert_eq!(anchor.metadata, vec![7, 8]);
222    }
223
224    #[test]
225    fn test_seal_ref_serialization() {
226        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
227        let bytes = seal.to_vec();
228        assert!(!bytes.is_empty());
229    }
230
231    #[test]
232    fn test_seal_ref_roundtrip() {
233        // Test with Some(nonce)
234        let seal1 = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
235        let bytes1 = seal1.to_vec();
236        let restored1 = SealRef::from_bytes(&bytes1).unwrap();
237        assert_eq!(restored1.seal_id, vec![1, 2, 3]);
238        assert_eq!(restored1.nonce, Some(42));
239
240        // Test with None nonce
241        let seal2 = SealRef::new(vec![4, 5, 6], None).unwrap();
242        let bytes2 = seal2.to_vec();
243        let restored2 = SealRef::from_bytes(&bytes2).unwrap();
244        assert_eq!(restored2.seal_id, vec![4, 5, 6]);
245        assert_eq!(restored2.nonce, None);
246
247        // Verify that None and Some(0) produce different bytes
248        let seal_none = SealRef::new(vec![1, 2, 3], None).unwrap();
249        let seal_zero = SealRef::new(vec![1, 2, 3], Some(0)).unwrap();
250        assert_ne!(seal_none.to_vec(), seal_zero.to_vec());
251    }
252
253    #[test]
254    fn test_seal_ref_from_bytes_errors() {
255        // Empty bytes
256        assert_eq!(SealRef::from_bytes(&[]), Err("empty bytes"));
257
258        // Invalid nonce flag
259        assert_eq!(SealRef::from_bytes(&[5]), Err("invalid nonce flag"));
260
261        // Truncated nonce
262        assert_eq!(SealRef::from_bytes(&[1, 0, 0]), Err("truncated nonce"));
263
264        // Truncated seal_id length
265        assert_eq!(SealRef::from_bytes(&[0]), Err("truncated seal_id length"));
266
267        // Truncated seal_id data
268        assert_eq!(
269            SealRef::from_bytes(&[0, 3, 0, 0, 0, 1]),
270            Err("truncated seal_id")
271        );
272
273        // Seal_id too large
274        let mut large = vec![0, 0x01, 0x04, 0x00, 0x00]; // length 1025
275        large.extend(vec![0u8; 1025]);
276        assert_eq!(
277            SealRef::from_bytes(&large),
278            Err("seal_id exceeds maximum allowed size (1KB)")
279        );
280
281        // Empty seal_id
282        assert_eq!(
283            SealRef::from_bytes(&[0, 0, 0, 0, 0]),
284            Err("seal_id cannot be empty")
285        );
286    }
287
288    #[test]
289    fn test_anchor_ref_serialization() {
290        let anchor = AnchorRef::new(vec![4, 5, 6], 100, vec![7, 8]).unwrap();
291        let bytes = anchor.to_vec();
292        assert!(!bytes.is_empty());
293    }
294
295    #[test]
296    fn test_seal_ref_empty_id() {
297        let result = SealRef::new(vec![], Some(42));
298        assert!(result.is_err());
299        assert_eq!(result.unwrap_err(), "seal_id cannot be empty");
300    }
301
302    #[test]
303    fn test_seal_ref_too_large() {
304        let large_id = vec![0u8; MAX_SEAL_ID_SIZE + 1];
305        let result = SealRef::new(large_id, Some(42));
306        assert!(result.is_err());
307        assert_eq!(
308            result.unwrap_err(),
309            "seal_id exceeds maximum allowed size (1KB)"
310        );
311    }
312
313    #[test]
314    fn test_seal_ref_at_max_size() {
315        let max_id = vec![0u8; MAX_SEAL_ID_SIZE];
316        let result = SealRef::new(max_id, Some(42));
317        assert!(result.is_ok());
318    }
319
320    #[test]
321    fn test_anchor_ref_empty_id() {
322        let result = AnchorRef::new(vec![], 100, vec![7, 8]);
323        assert!(result.is_err());
324        assert_eq!(result.unwrap_err(), "anchor_id cannot be empty");
325    }
326
327    #[test]
328    fn test_anchor_ref_id_too_large() {
329        let large_id = vec![0u8; MAX_ANCHOR_ID_SIZE + 1];
330        let result = AnchorRef::new(large_id, 100, vec![7, 8]);
331        assert!(result.is_err());
332        assert_eq!(
333            result.unwrap_err(),
334            "anchor_id exceeds maximum allowed size (1KB)"
335        );
336    }
337
338    #[test]
339    fn test_anchor_ref_metadata_too_large() {
340        let large_metadata = vec![0u8; MAX_ANCHOR_METADATA_SIZE + 1];
341        let result = AnchorRef::new(vec![1, 2, 3], 100, large_metadata);
342        assert!(result.is_err());
343        assert_eq!(
344            result.unwrap_err(),
345            "metadata exceeds maximum allowed size (4KB)"
346        );
347    }
348
349    #[test]
350    fn test_anchor_ref_at_max_sizes() {
351        let max_id = vec![0u8; MAX_ANCHOR_ID_SIZE];
352        let max_metadata = vec![0u8; MAX_ANCHOR_METADATA_SIZE];
353        let result = AnchorRef::new(max_id, 100, max_metadata);
354        assert!(result.is_ok());
355    }
356
357    #[test]
358    fn test_seal_ref_new_unchecked() {
359        let seal = SealRef::new_unchecked(vec![1, 2, 3], Some(42));
360        assert_eq!(seal.seal_id, vec![1, 2, 3]);
361        assert_eq!(seal.nonce, Some(42));
362    }
363
364    #[test]
365    fn test_anchor_ref_new_unchecked() {
366        let anchor = AnchorRef::new_unchecked(vec![4, 5, 6], 100, vec![7, 8]);
367        assert_eq!(anchor.anchor_id, vec![4, 5, 6]);
368        assert_eq!(anchor.block_height, 100);
369        assert_eq!(anchor.metadata, vec![7, 8]);
370    }
371}