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}