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(bytes[pos..pos + 4].try_into().map_err(|_| "truncated seal_id length")?) as usize;
111        pos += 4;
112
113        if seal_id_len > MAX_SEAL_ID_SIZE {
114            return Err("seal_id exceeds maximum allowed size (1KB)");
115        }
116        if seal_id_len == 0 {
117            return Err("seal_id cannot be empty");
118        }
119        if bytes.len() < pos + seal_id_len {
120            return Err("truncated seal_id");
121        }
122        let seal_id = bytes[pos..pos + seal_id_len].to_vec();
123
124        Ok(Self { seal_id, nonce })
125    }
126}
127
128/// A reference to an on-chain anchor containing a commitment
129///
130/// The concrete meaning is chain-specific:
131/// - Bitcoin: Transaction ID + output index
132/// - Ethereum: Transaction hash + log index
133/// - Sui: Object ID + version
134#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
135pub struct AnchorRef {
136    /// Chain-specific anchor identifier
137    pub anchor_id: Vec<u8>,
138    /// Block height or equivalent ordering
139    pub block_height: u64,
140    /// Optional chain-specific metadata
141    pub metadata: Vec<u8>,
142}
143
144impl AnchorRef {
145    /// Create a new AnchorRef
146    ///
147    /// # Arguments
148    /// * `anchor_id` - Chain-specific anchor identifier (max 1KB)
149    /// * `block_height` - Block height or equivalent ordering
150    /// * `metadata` - Optional chain-specific metadata (max 4KB)
151    ///
152    /// # Errors
153    /// Returns an error if anchor_id or metadata exceeds the maximum allowed size
154    pub fn new(
155        anchor_id: Vec<u8>,
156        block_height: u64,
157        metadata: Vec<u8>,
158    ) -> Result<Self, &'static str> {
159        if anchor_id.len() > MAX_ANCHOR_ID_SIZE {
160            return Err("anchor_id exceeds maximum allowed size (1KB)");
161        }
162        if anchor_id.is_empty() {
163            return Err("anchor_id cannot be empty");
164        }
165        if metadata.len() > MAX_ANCHOR_METADATA_SIZE {
166            return Err("metadata exceeds maximum allowed size (4KB)");
167        }
168        Ok(Self {
169            anchor_id,
170            block_height,
171            metadata,
172        })
173    }
174
175    /// Create a new $1 without validation.
176    ///
177    /// # Safety
178    /// This bypasses validation. Use only for internal protocol conversions.
179    ///
180    /// # Safety
181    /// This bypasses size validation and should only be used when
182    /// the input is already known to be valid.
183    pub fn new_unchecked(anchor_id: Vec<u8>, block_height: u64, metadata: Vec<u8>) -> Self {
184        Self {
185            anchor_id,
186            block_height,
187            metadata,
188        }
189    }
190
191    /// Serialize to bytes
192    pub fn to_vec(&self) -> Vec<u8> {
193        let mut out = Vec::with_capacity(8 + self.anchor_id.len() + self.metadata.len());
194        out.extend_from_slice(&self.block_height.to_le_bytes());
195        out.extend_from_slice(&self.anchor_id);
196        out.extend_from_slice(&self.metadata);
197        out
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204
205    #[test]
206    fn test_seal_ref_creation() {
207        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
208        assert_eq!(seal.seal_id, vec![1, 2, 3]);
209        assert_eq!(seal.nonce, Some(42));
210    }
211
212    #[test]
213    fn test_anchor_ref_creation() {
214        let anchor = AnchorRef::new(vec![4, 5, 6], 100, vec![7, 8]).unwrap();
215        assert_eq!(anchor.anchor_id, vec![4, 5, 6]);
216        assert_eq!(anchor.block_height, 100);
217        assert_eq!(anchor.metadata, vec![7, 8]);
218    }
219
220    #[test]
221    fn test_seal_ref_serialization() {
222        let seal = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
223        let bytes = seal.to_vec();
224        assert!(!bytes.is_empty());
225    }
226
227    #[test]
228    fn test_seal_ref_roundtrip() {
229        // Test with Some(nonce)
230        let seal1 = SealRef::new(vec![1, 2, 3], Some(42)).unwrap();
231        let bytes1 = seal1.to_vec();
232        let restored1 = SealRef::from_bytes(&bytes1).unwrap();
233        assert_eq!(restored1.seal_id, vec![1, 2, 3]);
234        assert_eq!(restored1.nonce, Some(42));
235
236        // Test with None nonce
237        let seal2 = SealRef::new(vec![4, 5, 6], None).unwrap();
238        let bytes2 = seal2.to_vec();
239        let restored2 = SealRef::from_bytes(&bytes2).unwrap();
240        assert_eq!(restored2.seal_id, vec![4, 5, 6]);
241        assert_eq!(restored2.nonce, None);
242
243        // Verify that None and Some(0) produce different bytes
244        let seal_none = SealRef::new(vec![1, 2, 3], None).unwrap();
245        let seal_zero = SealRef::new(vec![1, 2, 3], Some(0)).unwrap();
246        assert_ne!(seal_none.to_vec(), seal_zero.to_vec());
247    }
248
249    #[test]
250    fn test_seal_ref_from_bytes_errors() {
251        // Empty bytes
252        assert_eq!(SealRef::from_bytes(&[]), Err("empty bytes"));
253
254        // Invalid nonce flag
255        assert_eq!(SealRef::from_bytes(&[5]), Err("invalid nonce flag"));
256
257        // Truncated nonce
258        assert_eq!(SealRef::from_bytes(&[1, 0, 0]), Err("truncated nonce"));
259
260        // Truncated seal_id length
261        assert_eq!(SealRef::from_bytes(&[0]), Err("truncated seal_id length"));
262
263        // Truncated seal_id data
264        assert_eq!(SealRef::from_bytes(&[0, 3, 0, 0, 0, 1]), Err("truncated seal_id"));
265
266        // Seal_id too large
267        let mut large = vec![0, 0x01, 0x04, 0x00, 0x00]; // length 1025
268        large.extend(vec![0u8; 1025]);
269        assert_eq!(
270            SealRef::from_bytes(&large),
271            Err("seal_id exceeds maximum allowed size (1KB)")
272        );
273
274        // Empty seal_id
275        assert_eq!(SealRef::from_bytes(&[0, 0, 0, 0, 0]), Err("seal_id cannot be empty"));
276    }
277
278    #[test]
279    fn test_anchor_ref_serialization() {
280        let anchor = AnchorRef::new(vec![4, 5, 6], 100, vec![7, 8]).unwrap();
281        let bytes = anchor.to_vec();
282        assert!(!bytes.is_empty());
283    }
284
285    #[test]
286    fn test_seal_ref_empty_id() {
287        let result = SealRef::new(vec![], Some(42));
288        assert!(result.is_err());
289        assert_eq!(result.unwrap_err(), "seal_id cannot be empty");
290    }
291
292    #[test]
293    fn test_seal_ref_too_large() {
294        let large_id = vec![0u8; MAX_SEAL_ID_SIZE + 1];
295        let result = SealRef::new(large_id, Some(42));
296        assert!(result.is_err());
297        assert_eq!(
298            result.unwrap_err(),
299            "seal_id exceeds maximum allowed size (1KB)"
300        );
301    }
302
303    #[test]
304    fn test_seal_ref_at_max_size() {
305        let max_id = vec![0u8; MAX_SEAL_ID_SIZE];
306        let result = SealRef::new(max_id, Some(42));
307        assert!(result.is_ok());
308    }
309
310    #[test]
311    fn test_anchor_ref_empty_id() {
312        let result = AnchorRef::new(vec![], 100, vec![7, 8]);
313        assert!(result.is_err());
314        assert_eq!(result.unwrap_err(), "anchor_id cannot be empty");
315    }
316
317    #[test]
318    fn test_anchor_ref_id_too_large() {
319        let large_id = vec![0u8; MAX_ANCHOR_ID_SIZE + 1];
320        let result = AnchorRef::new(large_id, 100, vec![7, 8]);
321        assert!(result.is_err());
322        assert_eq!(
323            result.unwrap_err(),
324            "anchor_id exceeds maximum allowed size (1KB)"
325        );
326    }
327
328    #[test]
329    fn test_anchor_ref_metadata_too_large() {
330        let large_metadata = vec![0u8; MAX_ANCHOR_METADATA_SIZE + 1];
331        let result = AnchorRef::new(vec![1, 2, 3], 100, large_metadata);
332        assert!(result.is_err());
333        assert_eq!(
334            result.unwrap_err(),
335            "metadata exceeds maximum allowed size (4KB)"
336        );
337    }
338
339    #[test]
340    fn test_anchor_ref_at_max_sizes() {
341        let max_id = vec![0u8; MAX_ANCHOR_ID_SIZE];
342        let max_metadata = vec![0u8; MAX_ANCHOR_METADATA_SIZE];
343        let result = AnchorRef::new(max_id, 100, max_metadata);
344        assert!(result.is_ok());
345    }
346
347    #[test]
348    fn test_seal_ref_new_unchecked() {
349        let seal = SealRef::new_unchecked(vec![1, 2, 3], Some(42));
350        assert_eq!(seal.seal_id, vec![1, 2, 3]);
351        assert_eq!(seal.nonce, Some(42));
352    }
353
354    #[test]
355    fn test_anchor_ref_new_unchecked() {
356        let anchor = AnchorRef::new_unchecked(vec![4, 5, 6], 100, vec![7, 8]);
357        assert_eq!(anchor.anchor_id, vec![4, 5, 6]);
358        assert_eq!(anchor.block_height, 100);
359        assert_eq!(anchor.metadata, vec![7, 8]);
360    }
361}