use sha2::{Sha256, Digest};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct BootAttestation {
pub rvf_hash: [u8; 32],
pub capability_table_hash: [u8; 32],
pub region_layout_hash: [u8; 32],
pub boot_timestamp_ns: u64,
pub boot_sequence: u64,
pub platform_id: u64,
pub reserved: [u8; 16],
}
impl BootAttestation {
pub const SIZE: usize = 32 + 32 + 32 + 8 + 8 + 8 + 16;
#[must_use]
pub fn new(
rvf_hash: [u8; 32],
capability_table_hash: [u8; 32],
region_layout_hash: [u8; 32],
boot_timestamp_ns: u64,
) -> Self {
Self {
rvf_hash,
capability_table_hash,
region_layout_hash,
boot_timestamp_ns,
boot_sequence: 0,
platform_id: 0,
reserved: [0u8; 16],
}
}
#[must_use]
pub fn with_metadata(
rvf_hash: [u8; 32],
capability_table_hash: [u8; 32],
region_layout_hash: [u8; 32],
boot_timestamp_ns: u64,
boot_sequence: u64,
platform_id: u64,
) -> Self {
Self {
rvf_hash,
capability_table_hash,
region_layout_hash,
boot_timestamp_ns,
boot_sequence,
platform_id,
reserved: [0u8; 16],
}
}
#[must_use]
pub fn hash(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(&self.rvf_hash);
hasher.update(&self.capability_table_hash);
hasher.update(&self.region_layout_hash);
hasher.update(&self.boot_timestamp_ns.to_le_bytes());
hasher.update(&self.boot_sequence.to_le_bytes());
hasher.update(&self.platform_id.to_le_bytes());
hasher.update(&self.reserved);
let result = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&result);
hash
}
#[must_use]
pub fn to_bytes(&self) -> [u8; Self::SIZE] {
let mut bytes = [0u8; Self::SIZE];
bytes[0..32].copy_from_slice(&self.rvf_hash);
bytes[32..64].copy_from_slice(&self.capability_table_hash);
bytes[64..96].copy_from_slice(&self.region_layout_hash);
bytes[96..104].copy_from_slice(&self.boot_timestamp_ns.to_le_bytes());
bytes[104..112].copy_from_slice(&self.boot_sequence.to_le_bytes());
bytes[112..120].copy_from_slice(&self.platform_id.to_le_bytes());
bytes[120..136].copy_from_slice(&self.reserved);
bytes
}
pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
if bytes.len() < Self::SIZE {
return None;
}
let mut rvf_hash = [0u8; 32];
rvf_hash.copy_from_slice(&bytes[0..32]);
let mut capability_table_hash = [0u8; 32];
capability_table_hash.copy_from_slice(&bytes[32..64]);
let mut region_layout_hash = [0u8; 32];
region_layout_hash.copy_from_slice(&bytes[64..96]);
let boot_timestamp_ns = u64::from_le_bytes([
bytes[96], bytes[97], bytes[98], bytes[99],
bytes[100], bytes[101], bytes[102], bytes[103],
]);
let boot_sequence = u64::from_le_bytes([
bytes[104], bytes[105], bytes[106], bytes[107],
bytes[108], bytes[109], bytes[110], bytes[111],
]);
let platform_id = u64::from_le_bytes([
bytes[112], bytes[113], bytes[114], bytes[115],
bytes[116], bytes[117], bytes[118], bytes[119],
]);
let mut reserved = [0u8; 16];
reserved.copy_from_slice(&bytes[120..136]);
Some(Self {
rvf_hash,
capability_table_hash,
region_layout_hash,
boot_timestamp_ns,
boot_sequence,
platform_id,
reserved,
})
}
#[must_use]
pub fn verify(&self, expected_rvf_hash: &[u8; 32]) -> bool {
self.rvf_hash == *expected_rvf_hash
}
}
impl Default for BootAttestation {
fn default() -> Self {
Self {
rvf_hash: [0u8; 32],
capability_table_hash: [0u8; 32],
region_layout_hash: [0u8; 32],
boot_timestamp_ns: 0,
boot_sequence: 0,
platform_id: 0,
reserved: [0u8; 16],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
pub struct AttestationEntry {
pub entry_type: AttestationEntryType,
pub data_hash: [u8; 32],
pub timestamp_ns: u64,
pub task_id: u32,
pub component_id: u32,
pub flags: AttestationFlags,
pub reserved: [u8; 8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum AttestationEntryType {
VectorMutation = 0,
GraphMutation = 1,
CapabilityDelegate = 2,
CapabilityRevoke = 3,
Checkpoint = 4,
Rollback = 5,
Custom = 255,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(transparent)]
pub struct AttestationFlags(pub u32);
impl AttestationFlags {
pub const NONE: Self = Self(0);
pub const HIGH_PRIORITY: Self = Self(1 << 0);
pub const DEADLINE_DRIVEN: Self = Self(1 << 1);
pub const DEEP_PROOF: Self = Self(1 << 2);
pub const DURING_ROLLBACK: Self = Self(1 << 3);
#[inline]
#[must_use]
pub const fn contains(&self, flag: Self) -> bool {
(self.0 & flag.0) != 0
}
#[inline]
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
}
impl AttestationEntry {
pub const SIZE: usize = 1 + 32 + 8 + 4 + 4 + 4 + 8;
#[must_use]
pub fn new(
entry_type: AttestationEntryType,
data_hash: [u8; 32],
timestamp_ns: u64,
task_id: u32,
component_id: u32,
) -> Self {
Self {
entry_type,
data_hash,
timestamp_ns,
task_id,
component_id,
flags: AttestationFlags::NONE,
reserved: [0u8; 8],
}
}
#[must_use]
pub fn with_flags(
entry_type: AttestationEntryType,
data_hash: [u8; 32],
timestamp_ns: u64,
task_id: u32,
component_id: u32,
flags: AttestationFlags,
) -> Self {
Self {
entry_type,
data_hash,
timestamp_ns,
task_id,
component_id,
flags,
reserved: [0u8; 8],
}
}
#[must_use]
pub fn hash(&self) -> [u8; 32] {
let mut hasher = Sha256::new();
hasher.update(&[self.entry_type as u8]);
hasher.update(&self.data_hash);
hasher.update(&self.timestamp_ns.to_le_bytes());
hasher.update(&self.task_id.to_le_bytes());
hasher.update(&self.component_id.to_le_bytes());
hasher.update(&self.flags.0.to_le_bytes());
hasher.update(&self.reserved);
let result = hasher.finalize();
let mut hash = [0u8; 32];
hash.copy_from_slice(&result);
hash
}
}
impl Default for AttestationEntry {
fn default() -> Self {
Self {
entry_type: AttestationEntryType::Custom,
data_hash: [0u8; 32],
timestamp_ns: 0,
task_id: 0,
component_id: 0,
flags: AttestationFlags::NONE,
reserved: [0u8; 8],
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boot_attestation_creation() {
let att = BootAttestation::new(
[1u8; 32],
[2u8; 32],
[3u8; 32],
1234567890,
);
assert_eq!(att.rvf_hash, [1u8; 32]);
assert_eq!(att.capability_table_hash, [2u8; 32]);
assert_eq!(att.region_layout_hash, [3u8; 32]);
assert_eq!(att.boot_timestamp_ns, 1234567890);
}
#[test]
fn test_boot_attestation_serialization() {
let att = BootAttestation::new(
[0xAA; 32],
[0xBB; 32],
[0xCC; 32],
999999999,
);
let bytes = att.to_bytes();
let recovered = BootAttestation::from_bytes(&bytes).unwrap();
assert_eq!(att, recovered);
}
#[test]
fn test_boot_attestation_hash() {
let att1 = BootAttestation::new([1u8; 32], [2u8; 32], [3u8; 32], 100);
let att2 = BootAttestation::new([1u8; 32], [2u8; 32], [3u8; 32], 100);
let att3 = BootAttestation::new([1u8; 32], [2u8; 32], [4u8; 32], 100);
assert_eq!(att1.hash(), att2.hash());
assert_ne!(att1.hash(), att3.hash());
}
#[test]
fn test_boot_attestation_verify() {
let expected_hash = [0xDEu8; 32];
let att = BootAttestation::new(expected_hash, [0u8; 32], [0u8; 32], 0);
assert!(att.verify(&expected_hash));
assert!(!att.verify(&[0u8; 32]));
}
#[test]
fn test_attestation_entry_creation() {
let entry = AttestationEntry::new(
AttestationEntryType::VectorMutation,
[0xAB; 32],
1234567890,
1,
2,
);
assert_eq!(entry.entry_type, AttestationEntryType::VectorMutation);
assert_eq!(entry.task_id, 1);
assert_eq!(entry.component_id, 2);
}
#[test]
fn test_attestation_flags() {
let flags = AttestationFlags::HIGH_PRIORITY.union(AttestationFlags::DEEP_PROOF);
assert!(flags.contains(AttestationFlags::HIGH_PRIORITY));
assert!(flags.contains(AttestationFlags::DEEP_PROOF));
assert!(!flags.contains(AttestationFlags::DEADLINE_DRIVEN));
}
#[test]
fn test_boot_attestation_size() {
assert_eq!(BootAttestation::SIZE, 136);
}
}