1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
//! Hash digest and pluggable [`Hasher`] trait.
//!
//! `audit-trail` does not bundle a concrete hash function. Callers wire in an
//! implementation of [`Hasher`] backed by SHA-256, BLAKE3, SHA3-256, or any
//! other collision-resistant 32-byte hash. The trait is hot-path friendly:
//! no allocations, no boxed trait objects, no `dyn`.
use fmt;
/// Size, in bytes, of a hash output produced by a [`Hasher`].
///
/// Fixed at 32 bytes to cover the common cryptographic primitives
/// (SHA-256, BLAKE3, SHA3-256, KangarooTwelve-256).
pub const HASH_LEN: usize = 32;
/// Fixed-size hash output.
///
/// Used as the `prev_hash` and `hash` fields on every [`crate::Record`].
///
/// # Example
///
/// ```
/// use audit_trail::Digest;
///
/// let zero = Digest::ZERO;
/// assert_eq!(zero.as_bytes(), &[0u8; 32]);
/// ```
;
/// Pluggable hash function used to chain audit records.
///
/// Implementations must be deterministic and collision-resistant. A typical
/// adapter wraps a `Sha256`, `Blake3`, or `Sha3_256` from an external crate.
///
/// The trait is stateful: callers invoke [`reset`](Hasher::reset) at the
/// start of each record, [`update`](Hasher::update) with field bytes, then
/// [`finalize`](Hasher::finalize) to write the output digest. Implementations
/// may reuse internal buffers across calls to avoid heap allocation.
///
/// # Example
///
/// ```
/// use audit_trail::{Digest, Hasher, HASH_LEN};
///
/// /// A trivially-insecure XOR "hasher" for demonstration only.
/// struct XorHasher([u8; HASH_LEN]);
///
/// impl Hasher for XorHasher {
/// fn reset(&mut self) { self.0 = [0u8; HASH_LEN]; }
/// fn update(&mut self, bytes: &[u8]) {
/// for (i, b) in bytes.iter().enumerate() {
/// self.0[i % HASH_LEN] ^= *b;
/// }
/// }
/// fn finalize(&mut self, out: &mut Digest) {
/// *out = Digest::from_bytes(self.0);
/// }
/// }
/// ```