use std::fmt::Debug;
use sha2::{Digest, Sha256};
pub const HASH_LENGTH: usize = 32;
#[allow(dead_code)]
const MAX_DATA_LENGTH: usize = 64 * 1024 * 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum HashPurpose {
Compliance,
Internal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum HashAlgorithm {
Sha256 = 1,
Blake3 = 2,
}
impl HashAlgorithm {
#[must_use]
pub const fn for_purpose(purpose: HashPurpose) -> Self {
match purpose {
HashPurpose::Compliance => Self::Sha256,
HashPurpose::Internal => Self::Blake3,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct InternalHash([u8; HASH_LENGTH]);
impl InternalHash {
pub const LENGTH: usize = HASH_LENGTH;
#[must_use]
pub const fn from_bytes(bytes: [u8; HASH_LENGTH]) -> Self {
Self(bytes)
}
#[must_use]
pub const fn as_bytes(&self) -> &[u8; HASH_LENGTH] {
&self.0
}
}
impl From<[u8; HASH_LENGTH]> for InternalHash {
fn from(value: [u8; HASH_LENGTH]) -> Self {
Self(value)
}
}
impl From<InternalHash> for [u8; HASH_LENGTH] {
fn from(value: InternalHash) -> Self {
value.0
}
}
impl Debug for InternalHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"InternalHash({:016x}...)",
u64::from_le_bytes(
self.0[..8]
.try_into()
.expect("invariant: hash is HASH_LENGTH bytes, slice [..8] always fits [u8; 8]"),
)
)
}
}
#[must_use]
pub fn internal_hash(data: &[u8]) -> InternalHash {
debug_assert!(
data.len() <= MAX_DATA_LENGTH,
"data exceeds {MAX_DATA_LENGTH} byte sanity limit"
);
let hash = blake3::hash(data);
let hash_bytes: [u8; HASH_LENGTH] = *hash.as_bytes();
assert!(
hash_bytes.iter().any(|&b| b != 0),
"BLAKE3 produced all-zero hash - cryptographic library bug"
);
kimberlite_properties::sometimes!(
true,
"crypto.blake3_internal_hash_exercised",
"simulation should exercise the BLAKE3 internal hash path"
);
InternalHash(hash_bytes)
}
#[must_use]
pub fn hash_with_purpose(purpose: HashPurpose, data: &[u8]) -> (HashAlgorithm, [u8; HASH_LENGTH]) {
debug_assert!(
data.len() <= MAX_DATA_LENGTH,
"data exceeds {MAX_DATA_LENGTH} byte sanity limit"
);
let (algorithm, hash_bytes) = match purpose {
HashPurpose::Compliance => {
let digest = Sha256::digest(data);
kimberlite_properties::sometimes!(
true,
"crypto.sha256_compliance_hash_exercised",
"simulation should exercise the SHA-256 compliance hash path"
);
(HashAlgorithm::Sha256, digest.into())
}
HashPurpose::Internal => {
let hash = blake3::hash(data);
(HashAlgorithm::Blake3, *hash.as_bytes())
}
};
assert!(
hash_bytes.iter().any(|&b| b != 0),
"Hash produced all-zero output - cryptographic library bug"
);
(algorithm, hash_bytes)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_internal_hash_deterministic() {
let data = b"test data for hashing";
let hash1 = internal_hash(data);
let hash2 = internal_hash(data);
assert_eq!(hash1, hash2);
}
#[test]
fn test_internal_hash_different_inputs() {
let hash1 = internal_hash(b"input one");
let hash2 = internal_hash(b"input two");
assert_ne!(hash1, hash2);
}
#[test]
fn test_hash_with_purpose_compliance_uses_sha256() {
let (algo, _) = hash_with_purpose(HashPurpose::Compliance, b"data");
assert_eq!(algo, HashAlgorithm::Sha256);
}
#[test]
fn test_hash_with_purpose_internal_uses_blake3() {
let (algo, _) = hash_with_purpose(HashPurpose::Internal, b"data");
assert_eq!(algo, HashAlgorithm::Blake3);
}
#[test]
fn test_hash_with_purpose_deterministic() {
let data = b"same data";
let (algo1, digest1) = hash_with_purpose(HashPurpose::Internal, data);
let (algo2, digest2) = hash_with_purpose(HashPurpose::Internal, data);
assert_eq!(algo1, algo2);
assert_eq!(digest1, digest2);
}
#[test]
fn test_compliance_and_internal_differ() {
let data = b"same data";
let (_, compliance_digest) = hash_with_purpose(HashPurpose::Compliance, data);
let (_, internal_digest) = hash_with_purpose(HashPurpose::Internal, data);
assert_ne!(compliance_digest, internal_digest);
}
#[test]
fn test_internal_hash_matches_blake3_crate() {
let data = b"verify against blake3 crate directly";
let internal = internal_hash(data);
let direct = blake3::hash(data);
assert_eq!(internal.as_bytes(), direct.as_bytes());
}
#[test]
fn test_compliance_matches_sha256_crate() {
use sha2::{Digest, Sha256};
let data = b"verify against sha2 crate directly";
let (_, digest) = hash_with_purpose(HashPurpose::Compliance, data);
let direct: [u8; 32] = Sha256::digest(data).into();
assert_eq!(digest, direct);
}
#[test]
fn test_algorithm_for_purpose() {
assert_eq!(
HashAlgorithm::for_purpose(HashPurpose::Compliance),
HashAlgorithm::Sha256
);
assert_eq!(
HashAlgorithm::for_purpose(HashPurpose::Internal),
HashAlgorithm::Blake3
);
}
#[test]
fn test_internal_hash_conversions() {
let bytes = [42u8; HASH_LENGTH];
let hash = InternalHash::from(bytes);
let back: [u8; HASH_LENGTH] = hash.into();
assert_eq!(bytes, back);
}
}