nordisk/heimdall
guests/verify-bundle/src/digest.rs
// SPDX-License-Identifier: AGPL-3.0-only
//! SHA-256 and BLAKE3 as lowercase hex — the two digests every bundle member carries.
use sha2::{Digest, Sha256};
pub fn hex_lower(bytes: &[u8]) -> String {
const LUT: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for &b in bytes {
out.push(LUT[(b >> 4) as usize] as char);
out.push(LUT[(b & 0x0f) as usize] as char);
}
out
}
/// Decode hex (either case). `None` on odd length or a non-hex character.
pub fn hex_decode(s: &str) -> Option<Vec<u8>> {
let s = s.as_bytes();
if !s.len().is_multiple_of(2) {
return None;
}
fn nib(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some(b - b'a' + 10),
b'A'..=b'F' => Some(b - b'A' + 10),
_ => None,
}
}
let mut out = Vec::with_capacity(s.len() / 2);
for pair in s.chunks(2) {
out.push((nib(pair[0])? << 4) | nib(pair[1])?);
}
Some(out)
}
pub fn sha256(bytes: &[u8]) -> [u8; 32] {
Sha256::digest(bytes).into()
}
pub fn sha256_hex(bytes: &[u8]) -> String {
hex_lower(&sha256(bytes))
}
pub fn blake3_hex(bytes: &[u8]) -> String {
hex_lower(blake3::hash(bytes).as_bytes())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digests_of_the_empty_string_are_the_published_vectors() {
assert_eq!(
sha256_hex(b""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
assert_eq!(
blake3_hex(b""),
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262"
);
}
#[test]
fn hex_decode_refuses_odd_length_and_non_hex() {
assert_eq!(hex_decode("abc"), None);
assert_eq!(hex_decode("zz"), None);
assert_eq!(hex_decode("0aFF"), Some(vec![0x0a, 0xff]));
}
}