Skip to main content

javm_cap/
hash.rs

1//! Hash trait and default Blake2b-256 implementation.
2//!
3//! All v3 hash computations (image_hash chain, BMT, content-addressing)
4//! go through the `Hash` trait. `Blake2b256` is the spec default.
5//! The trait exists so we can swap in a mock hash for testing or
6//! a different hash function later without churning call sites.
7
8use alloc::vec::Vec;
9
10/// Hash function abstraction.
11///
12/// Implementations are stateless types (typically unit structs);
13/// `hash` is a pure function from bytes to a fixed-size digest.
14pub trait Hash {
15    /// Digest type. Must be `Copy` for use in BMT and cap fields.
16    type Out: Copy + Eq + core::fmt::Debug;
17
18    /// Hash a byte slice.
19    fn hash(bytes: &[u8]) -> Self::Out;
20
21    /// Hash the concatenation of two byte slices, without
22    /// materializing the concatenation. Default impl just allocates;
23    /// implementations should override for efficiency where possible.
24    fn hash_pair(a: &[u8], b: &[u8]) -> Self::Out {
25        let mut buf = Vec::with_capacity(a.len() + b.len());
26        buf.extend_from_slice(a);
27        buf.extend_from_slice(b);
28        Self::hash(&buf)
29    }
30}
31
32/// Default v3 hash: Blake2b-256 (32-byte output).
33pub struct Blake2b256;
34
35impl Hash for Blake2b256 {
36    type Out = [u8; 32];
37
38    fn hash(bytes: &[u8]) -> Self::Out {
39        use blake2::digest::{Update, VariableOutput};
40        let mut hasher = blake2::Blake2bVar::new(32).expect("32 ≤ Blake2b max output");
41        hasher.update(bytes);
42        let mut out = [0u8; 32];
43        hasher.finalize_variable(&mut out).expect("32-byte buffer");
44        out
45    }
46
47    fn hash_pair(a: &[u8], b: &[u8]) -> Self::Out {
48        use blake2::digest::{Update, VariableOutput};
49        let mut hasher = blake2::Blake2bVar::new(32).expect("32 ≤ Blake2b max output");
50        hasher.update(a);
51        hasher.update(b);
52        let mut out = [0u8; 32];
53        hasher.finalize_variable(&mut out).expect("32-byte buffer");
54        out
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn empty_hash_is_deterministic() {
64        let h1 = Blake2b256::hash(b"");
65        let h2 = Blake2b256::hash(b"");
66        assert_eq!(h1, h2);
67    }
68
69    #[test]
70    fn different_inputs_different_hashes() {
71        assert_ne!(Blake2b256::hash(b"hello"), Blake2b256::hash(b"world"));
72        assert_ne!(Blake2b256::hash(b"a"), Blake2b256::hash(b"b"));
73    }
74
75    #[test]
76    fn hash_pair_matches_hash_of_concat() {
77        let a: &[u8] = b"foo";
78        let b: &[u8] = b"barbaz";
79        let mut joined = Vec::new();
80        joined.extend_from_slice(a);
81        joined.extend_from_slice(b);
82        assert_eq!(Blake2b256::hash_pair(a, b), Blake2b256::hash(&joined));
83    }
84
85    #[test]
86    fn hash_pair_empty_matches_single() {
87        let a: &[u8] = b"";
88        let b: &[u8] = b"only";
89        assert_eq!(Blake2b256::hash_pair(a, b), Blake2b256::hash(b"only"));
90    }
91}