Skip to main content

ruvix_boot/
attestation.rs

1//! Boot attestation types and utilities.
2//!
3//! Boot attestation records the initial system state:
4//! - RVF package hash
5//! - Capability table hash
6//! - Region layout hash
7//! - Boot timestamp
8
9use sha2::{Sha256, Digest};
10
11/// Boot attestation entry recorded as the first witness log entry.
12///
13/// Contains cryptographic hashes of the initial system state
14/// for later verification and audit.
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[repr(C)]
17pub struct BootAttestation {
18    /// SHA-256 hash of the RVF package that was booted.
19    pub rvf_hash: [u8; 32],
20
21    /// SHA-256 hash of the initial capability table.
22    pub capability_table_hash: [u8; 32],
23
24    /// SHA-256 hash of the region layout.
25    pub region_layout_hash: [u8; 32],
26
27    /// Boot timestamp in nanoseconds since UNIX epoch.
28    pub boot_timestamp_ns: u64,
29
30    /// Boot sequence number (for multi-boot detection).
31    pub boot_sequence: u64,
32
33    /// Platform identifier.
34    pub platform_id: u64,
35
36    /// Reserved for future use.
37    pub reserved: [u8; 16],
38}
39
40impl BootAttestation {
41    /// Size of boot attestation in bytes.
42    pub const SIZE: usize = 32 + 32 + 32 + 8 + 8 + 8 + 16; // 136 bytes
43
44    /// Creates a new boot attestation.
45    #[must_use]
46    pub fn new(
47        rvf_hash: [u8; 32],
48        capability_table_hash: [u8; 32],
49        region_layout_hash: [u8; 32],
50        boot_timestamp_ns: u64,
51    ) -> Self {
52        Self {
53            rvf_hash,
54            capability_table_hash,
55            region_layout_hash,
56            boot_timestamp_ns,
57            boot_sequence: 0,
58            platform_id: 0,
59            reserved: [0u8; 16],
60        }
61    }
62
63    /// Creates a boot attestation with full metadata.
64    #[must_use]
65    pub fn with_metadata(
66        rvf_hash: [u8; 32],
67        capability_table_hash: [u8; 32],
68        region_layout_hash: [u8; 32],
69        boot_timestamp_ns: u64,
70        boot_sequence: u64,
71        platform_id: u64,
72    ) -> Self {
73        Self {
74            rvf_hash,
75            capability_table_hash,
76            region_layout_hash,
77            boot_timestamp_ns,
78            boot_sequence,
79            platform_id,
80            reserved: [0u8; 16],
81        }
82    }
83
84    /// Computes the hash of this attestation.
85    #[must_use]
86    pub fn hash(&self) -> [u8; 32] {
87        let mut hasher = Sha256::new();
88        hasher.update(&self.rvf_hash);
89        hasher.update(&self.capability_table_hash);
90        hasher.update(&self.region_layout_hash);
91        hasher.update(&self.boot_timestamp_ns.to_le_bytes());
92        hasher.update(&self.boot_sequence.to_le_bytes());
93        hasher.update(&self.platform_id.to_le_bytes());
94        hasher.update(&self.reserved);
95
96        let result = hasher.finalize();
97        let mut hash = [0u8; 32];
98        hash.copy_from_slice(&result);
99        hash
100    }
101
102    /// Serializes the attestation to bytes.
103    #[must_use]
104    pub fn to_bytes(&self) -> [u8; Self::SIZE] {
105        let mut bytes = [0u8; Self::SIZE];
106
107        bytes[0..32].copy_from_slice(&self.rvf_hash);
108        bytes[32..64].copy_from_slice(&self.capability_table_hash);
109        bytes[64..96].copy_from_slice(&self.region_layout_hash);
110        bytes[96..104].copy_from_slice(&self.boot_timestamp_ns.to_le_bytes());
111        bytes[104..112].copy_from_slice(&self.boot_sequence.to_le_bytes());
112        bytes[112..120].copy_from_slice(&self.platform_id.to_le_bytes());
113        bytes[120..136].copy_from_slice(&self.reserved);
114
115        bytes
116    }
117
118    /// Deserializes an attestation from bytes.
119    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
120        if bytes.len() < Self::SIZE {
121            return None;
122        }
123
124        let mut rvf_hash = [0u8; 32];
125        rvf_hash.copy_from_slice(&bytes[0..32]);
126
127        let mut capability_table_hash = [0u8; 32];
128        capability_table_hash.copy_from_slice(&bytes[32..64]);
129
130        let mut region_layout_hash = [0u8; 32];
131        region_layout_hash.copy_from_slice(&bytes[64..96]);
132
133        let boot_timestamp_ns = u64::from_le_bytes([
134            bytes[96], bytes[97], bytes[98], bytes[99],
135            bytes[100], bytes[101], bytes[102], bytes[103],
136        ]);
137
138        let boot_sequence = u64::from_le_bytes([
139            bytes[104], bytes[105], bytes[106], bytes[107],
140            bytes[108], bytes[109], bytes[110], bytes[111],
141        ]);
142
143        let platform_id = u64::from_le_bytes([
144            bytes[112], bytes[113], bytes[114], bytes[115],
145            bytes[116], bytes[117], bytes[118], bytes[119],
146        ]);
147
148        let mut reserved = [0u8; 16];
149        reserved.copy_from_slice(&bytes[120..136]);
150
151        Some(Self {
152            rvf_hash,
153            capability_table_hash,
154            region_layout_hash,
155            boot_timestamp_ns,
156            boot_sequence,
157            platform_id,
158            reserved,
159        })
160    }
161
162    /// Verifies that this attestation matches expected values.
163    #[must_use]
164    pub fn verify(&self, expected_rvf_hash: &[u8; 32]) -> bool {
165        self.rvf_hash == *expected_rvf_hash
166    }
167}
168
169impl Default for BootAttestation {
170    fn default() -> Self {
171        Self {
172            rvf_hash: [0u8; 32],
173            capability_table_hash: [0u8; 32],
174            region_layout_hash: [0u8; 32],
175            boot_timestamp_ns: 0,
176            boot_sequence: 0,
177            platform_id: 0,
178            reserved: [0u8; 16],
179        }
180    }
181}
182
183/// Witness log entry for attestation.
184///
185/// Used for entries other than boot attestation that record
186/// proof-gated mutations and other security events.
187#[derive(Debug, Clone, Copy, PartialEq, Eq)]
188#[repr(C)]
189pub struct AttestationEntry {
190    /// Entry type identifier.
191    pub entry_type: AttestationEntryType,
192
193    /// Hash of the attested data.
194    pub data_hash: [u8; 32],
195
196    /// Timestamp in nanoseconds since UNIX epoch.
197    pub timestamp_ns: u64,
198
199    /// Task that generated this attestation.
200    pub task_id: u32,
201
202    /// Component that generated this attestation.
203    pub component_id: u32,
204
205    /// Additional flags.
206    pub flags: AttestationFlags,
207
208    /// Reserved for future use.
209    pub reserved: [u8; 8],
210}
211
212/// Attestation entry type.
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214#[repr(u8)]
215pub enum AttestationEntryType {
216    /// Proof-gated vector mutation.
217    VectorMutation = 0,
218
219    /// Proof-gated graph mutation.
220    GraphMutation = 1,
221
222    /// Capability delegation.
223    CapabilityDelegate = 2,
224
225    /// Capability revocation.
226    CapabilityRevoke = 3,
227
228    /// Component state checkpoint.
229    Checkpoint = 4,
230
231    /// Component state rollback.
232    Rollback = 5,
233
234    /// Custom application-defined.
235    Custom = 255,
236}
237
238/// Attestation entry flags.
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
240#[repr(transparent)]
241pub struct AttestationFlags(pub u32);
242
243impl AttestationFlags {
244    /// No flags.
245    pub const NONE: Self = Self(0);
246
247    /// Entry contains high-priority mutation.
248    pub const HIGH_PRIORITY: Self = Self(1 << 0);
249
250    /// Entry was generated by a deadline-driven task.
251    pub const DEADLINE_DRIVEN: Self = Self(1 << 1);
252
253    /// Entry required Deep proof tier.
254    pub const DEEP_PROOF: Self = Self(1 << 2);
255
256    /// Entry was generated during rollback.
257    pub const DURING_ROLLBACK: Self = Self(1 << 3);
258
259    /// Checks if a flag is set.
260    #[inline]
261    #[must_use]
262    pub const fn contains(&self, flag: Self) -> bool {
263        (self.0 & flag.0) != 0
264    }
265
266    /// Returns the union of two flag sets.
267    #[inline]
268    #[must_use]
269    pub const fn union(self, other: Self) -> Self {
270        Self(self.0 | other.0)
271    }
272}
273
274impl AttestationEntry {
275    /// Size of attestation entry in bytes.
276    pub const SIZE: usize = 1 + 32 + 8 + 4 + 4 + 4 + 8; // 61 bytes
277
278    /// Creates a new attestation entry.
279    #[must_use]
280    pub fn new(
281        entry_type: AttestationEntryType,
282        data_hash: [u8; 32],
283        timestamp_ns: u64,
284        task_id: u32,
285        component_id: u32,
286    ) -> Self {
287        Self {
288            entry_type,
289            data_hash,
290            timestamp_ns,
291            task_id,
292            component_id,
293            flags: AttestationFlags::NONE,
294            reserved: [0u8; 8],
295        }
296    }
297
298    /// Creates an attestation entry with flags.
299    #[must_use]
300    pub fn with_flags(
301        entry_type: AttestationEntryType,
302        data_hash: [u8; 32],
303        timestamp_ns: u64,
304        task_id: u32,
305        component_id: u32,
306        flags: AttestationFlags,
307    ) -> Self {
308        Self {
309            entry_type,
310            data_hash,
311            timestamp_ns,
312            task_id,
313            component_id,
314            flags,
315            reserved: [0u8; 8],
316        }
317    }
318
319    /// Computes the hash of this entry.
320    #[must_use]
321    pub fn hash(&self) -> [u8; 32] {
322        let mut hasher = Sha256::new();
323        hasher.update(&[self.entry_type as u8]);
324        hasher.update(&self.data_hash);
325        hasher.update(&self.timestamp_ns.to_le_bytes());
326        hasher.update(&self.task_id.to_le_bytes());
327        hasher.update(&self.component_id.to_le_bytes());
328        hasher.update(&self.flags.0.to_le_bytes());
329        hasher.update(&self.reserved);
330
331        let result = hasher.finalize();
332        let mut hash = [0u8; 32];
333        hash.copy_from_slice(&result);
334        hash
335    }
336}
337
338impl Default for AttestationEntry {
339    fn default() -> Self {
340        Self {
341            entry_type: AttestationEntryType::Custom,
342            data_hash: [0u8; 32],
343            timestamp_ns: 0,
344            task_id: 0,
345            component_id: 0,
346            flags: AttestationFlags::NONE,
347            reserved: [0u8; 8],
348        }
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_boot_attestation_creation() {
358        let att = BootAttestation::new(
359            [1u8; 32],
360            [2u8; 32],
361            [3u8; 32],
362            1234567890,
363        );
364
365        assert_eq!(att.rvf_hash, [1u8; 32]);
366        assert_eq!(att.capability_table_hash, [2u8; 32]);
367        assert_eq!(att.region_layout_hash, [3u8; 32]);
368        assert_eq!(att.boot_timestamp_ns, 1234567890);
369    }
370
371    #[test]
372    fn test_boot_attestation_serialization() {
373        let att = BootAttestation::new(
374            [0xAA; 32],
375            [0xBB; 32],
376            [0xCC; 32],
377            999999999,
378        );
379
380        let bytes = att.to_bytes();
381        let recovered = BootAttestation::from_bytes(&bytes).unwrap();
382
383        assert_eq!(att, recovered);
384    }
385
386    #[test]
387    fn test_boot_attestation_hash() {
388        let att1 = BootAttestation::new([1u8; 32], [2u8; 32], [3u8; 32], 100);
389        let att2 = BootAttestation::new([1u8; 32], [2u8; 32], [3u8; 32], 100);
390        let att3 = BootAttestation::new([1u8; 32], [2u8; 32], [4u8; 32], 100);
391
392        // Same inputs = same hash
393        assert_eq!(att1.hash(), att2.hash());
394
395        // Different inputs = different hash
396        assert_ne!(att1.hash(), att3.hash());
397    }
398
399    #[test]
400    fn test_boot_attestation_verify() {
401        let expected_hash = [0xDEu8; 32];
402        let att = BootAttestation::new(expected_hash, [0u8; 32], [0u8; 32], 0);
403
404        assert!(att.verify(&expected_hash));
405        assert!(!att.verify(&[0u8; 32]));
406    }
407
408    #[test]
409    fn test_attestation_entry_creation() {
410        let entry = AttestationEntry::new(
411            AttestationEntryType::VectorMutation,
412            [0xAB; 32],
413            1234567890,
414            1,
415            2,
416        );
417
418        assert_eq!(entry.entry_type, AttestationEntryType::VectorMutation);
419        assert_eq!(entry.task_id, 1);
420        assert_eq!(entry.component_id, 2);
421    }
422
423    #[test]
424    fn test_attestation_flags() {
425        let flags = AttestationFlags::HIGH_PRIORITY.union(AttestationFlags::DEEP_PROOF);
426
427        assert!(flags.contains(AttestationFlags::HIGH_PRIORITY));
428        assert!(flags.contains(AttestationFlags::DEEP_PROOF));
429        assert!(!flags.contains(AttestationFlags::DEADLINE_DRIVEN));
430    }
431
432    #[test]
433    fn test_boot_attestation_size() {
434        assert_eq!(BootAttestation::SIZE, 136);
435    }
436}