Skip to main content

edgesentry_rs/
integrity.rs

1//! BLAKE3 hash-chain integrity — payload hashing and chain verification.
2//!
3//! This module covers the tamper-detection layer: hashing a raw payload with
4//! BLAKE3 and verifying that a sequence of `AuditRecord`s forms an unbroken
5//! hash chain.
6
7use thiserror::Error;
8
9use crate::record::AuditRecord;
10
11/// Compute the BLAKE3 hash of a raw payload.
12pub fn compute_payload_hash(payload: &[u8]) -> [u8; 32] {
13    *blake3::hash(payload).as_bytes()
14}
15
16/// Errors produced by [`verify_chain`].
17#[derive(Debug, Error, PartialEq, Eq)]
18pub enum ChainError {
19    #[error("invalid previous hash at index {index}")]
20    InvalidPrevHash { index: usize },
21    #[error("invalid sequence at index {index}: expected {expected}, actual {actual}")]
22    InvalidSequence {
23        index: usize,
24        expected: u64,
25        actual: u64,
26    },
27}
28
29/// Verify that `records` form a valid hash chain.
30///
31/// - The first record must have `prev_record_hash == [0u8; 32]`.
32/// - Each subsequent record's `prev_record_hash` must equal the hash of the
33///   preceding record.
34/// - Sequences must be strictly monotonically increasing by 1.
35pub fn verify_chain(records: &[AuditRecord]) -> Result<(), ChainError> {
36    if records.is_empty() {
37        return Ok(());
38    }
39
40    for (index, record) in records.iter().enumerate() {
41        if index == 0 {
42            if record.prev_record_hash != AuditRecord::zero_hash() {
43                return Err(ChainError::InvalidPrevHash { index });
44            }
45            continue;
46        }
47
48        let previous = &records[index - 1];
49        if record.prev_record_hash != previous.hash() {
50            return Err(ChainError::InvalidPrevHash { index });
51        }
52
53        let expected_sequence = previous.sequence + 1;
54        if record.sequence != expected_sequence {
55            return Err(ChainError::InvalidSequence {
56                index,
57                expected: expected_sequence,
58                actual: record.sequence,
59            });
60        }
61    }
62
63    Ok(())
64}