Skip to main content

void_crypto/
cid_types.rs

1//! Typed CID newtypes for compile-time content-addressing safety.
2//!
3//! Each CID type corresponds to a distinct encrypted object kind with its own
4//! AAD, encryption scheme, and deserialization target:
5//!
6//! - `CommitCid` — points to an envelope-encrypted commit blob (`AAD_COMMIT`)
7//! - `MetadataCid` — points to an AEAD-encrypted metadata bundle (`AAD_METADATA`)
8//! - `ShardCid` — points to a header+compressed shard blob (`AAD_SHARD`)
9//! - `ManifestCid` — points to an AEAD-encrypted tree manifest (`AAD_MANIFEST`)
10//! - `RepoManifestCid` — points to an AEAD-encrypted repo manifest (`AAD_REPO_MANIFEST`)
11//!
12//! All wrap serialized CIDv1 bytes (variable length, typically ~36 bytes for SHA-256).
13//! Conversions to/from `cid::Cid` objects live in `void_core::cid` since that module
14//! owns the `cid` crate dependency.
15
16use std::fmt;
17
18// ============================================================================
19// Macro for variable-length CID newtypes
20// ============================================================================
21
22/// Defines a CID newtype wrapping `Vec<u8>` with serde serialization support.
23///
24/// Generated types include:
25/// - `from_bytes(bytes: Vec<u8>) -> Self`
26/// - `as_bytes(&self) -> &[u8]`
27/// - `into_bytes(self) -> Vec<u8>`
28/// - `Debug` impl showing byte length
29macro_rules! define_cid_newtype {
30    (
31        $(#[$meta:meta])*
32        $name:ident
33    ) => {
34        $(#[$meta])*
35        #[derive(Clone, PartialEq, Eq, Hash,
36                 serde::Serialize, serde::Deserialize)]
37        pub struct $name(Vec<u8>);
38
39        impl $name {
40            /// Create from serialized CID bytes.
41            pub fn from_bytes(bytes: Vec<u8>) -> Self {
42                Self(bytes)
43            }
44
45            /// Access the underlying CID bytes.
46            pub fn as_bytes(&self) -> &[u8] {
47                &self.0
48            }
49
50            /// Consume and return the underlying CID bytes.
51            pub fn into_bytes(self) -> Vec<u8> {
52                self.0
53            }
54        }
55
56        impl fmt::Debug for $name {
57            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58                write!(f, "{}({} bytes)", stringify!($name), self.0.len())
59            }
60        }
61    };
62}
63
64// ============================================================================
65// CID newtypes — one per encrypted object kind
66// ============================================================================
67
68define_cid_newtype!(
69    /// CID pointing to an envelope-encrypted commit blob (`AAD_COMMIT`).
70    ///
71    /// Wraps serialized CIDv1 bytes. The referenced blob decrypts to an
72    /// CBOR-serialized `Commit` struct.
73    CommitCid
74);
75
76define_cid_newtype!(
77    /// CID pointing to an AEAD-encrypted metadata bundle (`AAD_METADATA`).
78    ///
79    /// Wraps serialized CIDv1 bytes. The referenced blob decrypts to an
80    /// CBOR-serialized `MetadataBundle` struct.
81    MetadataCid
82);
83
84define_cid_newtype!(
85    /// CID pointing to an encrypted shard blob (`AAD_SHARD`).
86    ///
87    /// Wraps serialized CIDv1 bytes. The referenced blob contains
88    /// zstd-compressed file content (opaque block).
89    ShardCid
90);
91
92define_cid_newtype!(
93    /// CID pointing to an AEAD-encrypted tree manifest (`AAD_MANIFEST`).
94    ///
95    /// Wraps serialized CIDv1 bytes. The referenced blob decrypts to an
96    /// CBOR-serialized `TreeManifest` struct.
97    ManifestCid
98);
99
100define_cid_newtype!(
101    /// CID pointing to an AEAD-encrypted repo manifest (`AAD_REPO_MANIFEST`).
102    ///
103    /// Wraps serialized CIDv1 bytes. The referenced blob decrypts to a
104    /// JSON-serialized `Manifest` struct.
105    RepoManifestCid
106);
107
108// ============================================================================
109// Tests
110// ============================================================================
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn cid_newtype_from_bytes_roundtrip() {
118        let mut bytes = vec![0x01, 0x55, 0x12, 0x20];
119        bytes.extend_from_slice(&[0xaa; 32]);
120        let cid = CommitCid::from_bytes(bytes.clone());
121
122        assert_eq!(cid.as_bytes(), &bytes);
123        assert_eq!(cid.into_bytes(), bytes);
124    }
125
126    #[test]
127    fn different_cid_types_are_incompatible() {
128        let bytes = vec![0x01, 0x55, 0x12, 0x20];
129        let commit_cid = CommitCid::from_bytes(bytes.clone());
130        let metadata_cid = MetadataCid::from_bytes(bytes);
131
132        // These are different types — can't compare or assign between them
133        let _ = commit_cid;
134        let _ = metadata_cid;
135    }
136
137    #[test]
138    fn cid_equality() {
139        let bytes = vec![1, 2, 3, 4];
140        let a = ShardCid::from_bytes(bytes.clone());
141        let b = ShardCid::from_bytes(bytes);
142        let c = ShardCid::from_bytes(vec![5, 6, 7, 8]);
143
144        assert_eq!(a, b);
145        assert_ne!(a, c);
146    }
147
148    #[test]
149    fn cid_debug_format() {
150        let cid = ManifestCid::from_bytes(vec![0; 36]);
151        let debug = format!("{:?}", cid);
152        assert!(debug.contains("ManifestCid"));
153        assert!(debug.contains("36 bytes"));
154    }
155
156    #[test]
157    fn cid_cbor_roundtrip() {
158        let mut bytes = vec![0x01, 0x55, 0x12, 0x20];
159        bytes.extend_from_slice(&[0xab; 32]);
160        let cid = CommitCid::from_bytes(bytes);
161        let mut encoded = Vec::new();
162        ciborium::into_writer(&cid, &mut encoded).unwrap();
163        let deserialized: CommitCid = ciborium::from_reader(&encoded[..]).unwrap();
164        assert_eq!(cid, deserialized);
165    }
166}