pub struct Verifier<H>where
H: Hasher,{ /* private fields */ }Expand description
Replays a chain of records and proves their hash linkage is intact.
The verifier is generic over its Hasher: callers must supply an
implementation that produces the same digests the original Chain
produced. Two different hash algorithms will not interoperate.
§Example
use audit_trail::{
Action, Actor, Chain, Clock, Digest, Hasher, Outcome, Record, RecordId, Sink,
SinkError, Target, Timestamp, Verifier, HASH_LEN,
};
// XOR-fold hasher (insecure — for demonstration only).
struct XorHasher([u8; HASH_LEN], usize);
impl Hasher for XorHasher {
fn reset(&mut self) { self.0 = [0u8; HASH_LEN]; self.1 = 0; }
fn update(&mut self, bytes: &[u8]) {
for b in bytes { self.0[self.1 % HASH_LEN] ^= *b; self.1 = self.1.wrapping_add(1); }
}
fn finalize(&mut self, out: &mut Digest) { *out = Digest::from_bytes(self.0); }
}
// Capture every record the chain emits.
#[derive(Default)]
struct CaptureSink { records: Vec<(RecordId, Timestamp, Digest, Digest, Outcome)> }
impl Sink for CaptureSink {
fn write(&mut self, r: &Record<'_>) -> Result<(), SinkError> {
self.records.push((r.id(), r.timestamp(), r.prev_hash(), r.hash(), r.outcome()));
Ok(())
}
}
struct Tick(core::cell::Cell<u64>);
impl Clock for Tick {
fn now(&self) -> Timestamp {
let v = self.0.get(); self.0.set(v + 1); Timestamp::from_nanos(v)
}
}
// Build a 3-record chain.
let mut chain = Chain::new(
XorHasher([0u8; HASH_LEN], 0),
CaptureSink::default(),
Tick(core::cell::Cell::new(1)),
);
chain.append(Actor::new("a"), Action::new("x"), Target::new("t"), Outcome::Success).unwrap();
chain.append(Actor::new("a"), Action::new("y"), Target::new("u"), Outcome::Success).unwrap();
chain.append(Actor::new("a"), Action::new("z"), Target::new("v"), Outcome::Failure).unwrap();
let (_h, sink, _c) = chain.into_parts();
// Replay every captured record through the verifier.
let actors = ["a", "a", "a"];
let actions = ["x", "y", "z"];
let targets = ["t", "u", "v"];
let mut verifier = Verifier::new(XorHasher([0u8; HASH_LEN], 0));
for (i, (id, ts, prev, hash, outcome)) in sink.records.iter().enumerate() {
let record = Record::new(
*id, *ts,
Actor::new(actors[i]), Action::new(actions[i]), Target::new(targets[i]),
*outcome, *prev, *hash,
);
verifier.verify(&record).unwrap();
}
assert_eq!(verifier.next_id(), RecordId::from_u64(3));Implementations§
Source§impl<H> Verifier<H>where
H: Hasher,
impl<H> Verifier<H>where
H: Hasher,
Sourcepub fn new(hasher: H) -> Self
pub fn new(hasher: H) -> Self
Construct a verifier that expects to start from the genesis record.
Timestamp monotonicity is enforced by default. Disable it with
Verifier::with_strict_timestamps.
Sourcepub fn resume(
hasher: H,
next_id: RecordId,
last_hash: Digest,
last_timestamp: Timestamp,
) -> Self
pub fn resume( hasher: H, next_id: RecordId, last_hash: Digest, last_timestamp: Timestamp, ) -> Self
Construct a verifier that resumes mid-chain.
next_id, last_hash, and last_timestamp must match the state of
the chain immediately after the most recently verified record.
Sourcepub const fn with_strict_timestamps(self, strict: bool) -> Self
pub const fn with_strict_timestamps(self, strict: bool) -> Self
Toggle strict timestamp monotonicity. Default is true.
When false, timestamps may be equal or out of order without
triggering Error::NonMonotonicClock. This is occasionally useful
when verifying chains imported from systems with coarser clocks.
Sourcepub const fn last_hash(&self) -> Digest
pub const fn last_hash(&self) -> Digest
Hash of the last record this verifier accepted, or Digest::ZERO
if none yet.
Sourcepub const fn last_timestamp(&self) -> Timestamp
pub const fn last_timestamp(&self) -> Timestamp
Timestamp of the last record this verifier accepted, or
Timestamp::EPOCH if none yet.
Sourcepub fn verify(&mut self, record: &Record<'_>) -> Result<()>
pub fn verify(&mut self, record: &Record<'_>) -> Result<()>
Verify a single record against the running chain state.
On success the verifier’s internal cursor advances. On failure the
verifier’s cursor is left unchanged so callers can inspect
Verifier::next_id to learn which record failed.
§Errors
Error::IdMismatch— record id is not the expected next id.Error::LinkMismatch— record’sprev_hashdoes not equal the previous record’shash.Error::HashMismatch— record’s storedhashdoes not match the digest recomputed from its fields.Error::NonMonotonicClock— record’s timestamp is not strictly greater than the previous record’s (only when strict timestamps are enabled).
Sourcepub fn into_hasher(self) -> H
pub fn into_hasher(self) -> H
Consume the verifier and return its hasher.