Skip to main content

coven_crypto/
hashing.rs

1//! Content hashing (§17) and keyed original-write authorship (D11).
2
3use hmac::Mac;
4use sha2::{Digest, Sha256};
5
6/// SHA-256 of a file's bytes, kept inside encrypted writes (§17).
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub struct ContentHash([u8; 32]);
9
10impl ContentHash {
11    /// Decode the content hash recorded in an encrypted row.
12    pub fn from_bytes(bytes: [u8; 32]) -> Self {
13        Self(bytes)
14    }
15    /// The hash bytes; never place these in provider-visible paths or metadata.
16    pub fn as_bytes(&self) -> &[u8; 32] {
17        &self.0
18    }
19}
20
21/// A stream's incremental SHA-256 state; the file never has to fit in memory.
22pub struct ContentHasher(Sha256);
23
24impl ContentHasher {
25    /// Start hashing a file's bytes (§17).
26    pub fn new() -> Self {
27        Self(Sha256::new())
28    }
29    /// Feed the next bytes of the stream in order, with any buffer size.
30    pub fn update(&mut self, bytes: &[u8]) {
31        self.0.update(bytes);
32    }
33    /// Finish the content hash once the stream has ended.
34    pub fn finish(self) -> ContentHash {
35        ContentHash(self.0.finalize().into())
36    }
37}
38
39impl std::fmt::Debug for ContentHasher {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        f.write_str("ContentHasher(..)")
42    }
43}
44
45#[cfg(test)]
46#[path = "hashing_tests.rs"]
47mod tests;
48
49/// Keyed hashing of one write part's authorship leaves (D11). Each leaf is
50/// keyed with the part's audience key, so a member who cannot read the
51/// audience cannot confirm a guessed row from a published root. Branches and
52/// the empty tree are unkeyed: a device holding a part's leaf hashes can open
53/// any of its leaves again without the key.
54#[derive(Clone)]
55pub struct AuthorshipHasher(zeroize::Zeroizing<[u8; 32]>);
56
57impl AuthorshipHasher {
58    pub(crate) fn new(key: &[u8; 32]) -> Self {
59        Self(zeroize::Zeroizing::new(*key))
60    }
61
62    /// One leaf's encoded bytes, as D11 defines them.
63    pub fn leaf(&self, leaf: &[u8]) -> [u8; 32] {
64        let mut mac = crate::derivation::mac(&self.0);
65        mac.update(&[0]);
66        mac.update(leaf);
67        mac.finalize().into_bytes().into()
68    }
69}
70
71/// A branch of an authorship tree over its ordered children (D11).
72pub fn authorship_node(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
73    let mut hash = Sha256::new();
74    hash.update([1]);
75    hash.update(left);
76    hash.update(right);
77    hash.finalize().into()
78}
79
80/// The root of an authorship tree with no leaves (D11).
81pub fn authorship_empty() -> [u8; 32] {
82    Sha256::digest([2]).into()
83}
84
85/// The digest an accepted change's kept record holds in place of a value
86/// nothing reads in full (D7.3): SHA-256 over the context-framed label
87/// `coven/kept-value/v1` and the value's encoded bytes. Two values have equal
88/// digests exactly when their encodings are equal, under SHA-256's collision
89/// resistance.
90pub fn kept_value_digest(encoded: &[u8]) -> [u8; 32] {
91    Sha256::digest(crate::cipher::context(&[crate::derivation::KEPT_VALUE, encoded])).into()
92}
93
94impl std::fmt::Debug for AuthorshipHasher {
95    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96        f.write_str("AuthorshipHasher([REDACTED])")
97    }
98}