use sha2::digest::{typenum::Unsigned, OutputSizeUser};
use sha2::{Digest, Sha256};
pub const SHA256_OUTPUT_LEN: usize = <<Sha256 as OutputSizeUser>::OutputSize as Unsigned>::USIZE;
pub fn sha256(data: &[u8]) -> [u8; SHA256_OUTPUT_LEN] {
let mut hasher = Sha256::new();
hasher.update(data);
hasher.finalize().into()
}
pub fn sha256_chunks(chunks: &[&[u8]]) -> [u8; SHA256_OUTPUT_LEN] {
let mut hasher = Sha256::new();
for chunk in chunks {
hasher.update(chunk);
}
hasher.finalize().into()
}
pub struct SharedPrefixDigests {
pub with_suffix: [u8; SHA256_OUTPUT_LEN],
pub with_first_digest: [u8; SHA256_OUTPUT_LEN],
}
pub fn sha256_prefix_and_digest_suffix(
prefix: &[&[u8]],
first_suffix: &[u8],
) -> SharedPrefixDigests {
Sha256PrefixState::absorb(prefix).digests_with_suffix(first_suffix)
}
pub struct Sha256PrefixState {
base: Sha256,
}
impl Sha256PrefixState {
pub fn absorb(prefix: &[&[u8]]) -> Self {
let mut base = Sha256::new();
for chunk in prefix {
base.update(chunk);
}
Self { base }
}
pub fn update(&mut self, chunk: &[u8]) {
self.base.update(chunk);
}
pub fn digest_with_suffix(&self, suffix: &[u8]) -> [u8; SHA256_OUTPUT_LEN] {
let mut digest = self.base.clone();
digest.update(suffix);
digest.finalize().into()
}
pub fn digests_with_suffix(&self, first_suffix: &[u8]) -> SharedPrefixDigests {
let with_suffix = self.digest_with_suffix(first_suffix);
let mut second = self.base.clone();
second.update(with_suffix);
SharedPrefixDigests {
with_suffix,
with_first_digest: second.finalize().into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chunked_hashing_equals_hashing_the_concatenation() {
let joined = sha256(b"personal-reticulum");
let chunked = sha256_chunks(&[b"personal", b"-", b"reticulum"]);
assert_eq!(joined, chunked);
assert_eq!(sha256_chunks(&[b"personal-reticulum"]), joined);
}
#[test]
fn prefix_and_digest_suffix_hashes_match_independent_hashes() {
let SharedPrefixDigests {
with_suffix,
with_first_digest,
} = sha256_prefix_and_digest_suffix(&[b"shared ", b"payload"], b" salt");
assert_eq!(with_suffix, sha256_chunks(&[b"shared payload", b" salt"]));
assert_eq!(
with_first_digest,
sha256_chunks(&[b"shared payload", &with_suffix])
);
assert_eq!(
Sha256PrefixState::absorb(&[b"shared ", b"payload"]).digest_with_suffix(b" salt"),
with_suffix,
);
}
}