use alloc::vec::Vec;
use super::{
Blake3,
kernels::{Blake3KernelId, kernel as kernel_for_id, required_caps},
};
use crate::traits::Digest as _;
const ALL: &[Blake3KernelId] = &[
Blake3KernelId::Portable,
#[cfg(target_arch = "x86_64")]
Blake3KernelId::X86Sse41,
#[cfg(target_arch = "x86_64")]
Blake3KernelId::X86Avx2,
#[cfg(target_arch = "x86_64")]
Blake3KernelId::X86Avx512,
#[cfg(target_arch = "aarch64")]
Blake3KernelId::Aarch64Neon,
#[cfg(target_arch = "s390x")]
Blake3KernelId::S390xVector,
#[cfg(target_arch = "powerpc64")]
Blake3KernelId::PowerVsx,
#[cfg(target_arch = "riscv64")]
Blake3KernelId::RiscvV,
];
#[derive(Clone, Debug)]
pub struct KernelResult {
pub digest: [u8; 32],
}
fn force_hasher_kernel(mut h: Blake3, id: Blake3KernelId) -> Blake3 {
let kernel = kernel_for_id(id);
h.bulk_kernel_id = kernel.id;
h.chunk_state.kernel_id = kernel.id;
h
}
fn hasher_for_kernel(id: Blake3KernelId) -> Blake3 {
force_hasher_kernel(Blake3::new(), id)
}
fn digest_with_kernel(id: Blake3KernelId, data: &[u8]) -> [u8; 32] {
let kernel = kernel_for_id(id);
let mut h = hasher_for_kernel(id);
h.update_with(data, kernel.id, kernel.id);
h.finalize()
}
#[must_use]
pub fn run_all_blake3_kernels(data: &[u8]) -> Vec<KernelResult> {
let caps = crate::platform::caps();
let mut out = Vec::with_capacity(ALL.len());
for &id in ALL {
if caps.has(required_caps(id)) {
out.push(KernelResult {
digest: digest_with_kernel(id, data),
});
}
}
out
}
pub fn verify_blake3_kernels(data: &[u8]) -> Result<(), &'static str> {
let results = run_all_blake3_kernels(data);
let Some(first) = results.first() else {
return Ok(());
};
for r in &results[1..] {
if r.digest != first.digest {
return Err("blake3 kernel mismatch");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::traits::Xof as _;
const KEY: &[u8; 32] = b"whats the Elvish word for friend";
const CONTEXT: &str = "BLAKE3 2019-12-27 16:29:52 test vectors context";
fn keyed_hasher_for_kernel(id: Blake3KernelId, key: &[u8; 32]) -> Blake3 {
force_hasher_kernel(Blake3::new_keyed(key), id)
}
fn derive_hasher_for_kernel(id: Blake3KernelId, context: &str) -> Blake3 {
force_hasher_kernel(Blake3::new_derive_key(context), id)
}
fn pattern(len: usize) -> Vec<u8> {
(0..len).map(|i| (i % 251) as u8).collect()
}
#[test]
fn all_kernels_match_official_crate_and_streaming_splits() {
let caps = crate::platform::caps();
#[cfg(not(miri))]
let lens = [0usize, 1, 2, 3, 63, 64, 65, 1023, 1024, 1025, 2047, 2048, 2049, 10_000];
#[cfg(miri)]
let lens = [0usize, 1, 63, 64, 65, 1023, 1024, 1025, 2048];
#[cfg(not(miri))]
let chunks = [1usize, 7, 31, 32, 63, 64, 65, 256, 1024, 4096];
#[cfg(miri)]
let chunks = [1usize, 31, 32, 63, 64, 65, 256];
for &id in ALL {
if !caps.has(required_caps(id)) {
continue;
}
let kernel = kernel_for_id(id);
for &len in &lens {
let msg = pattern(len);
let ours = digest_with_kernel(id, &msg);
let expected = *blake3::hash(&msg).as_bytes();
assert_eq!(ours, expected, "blake3 hash mismatch for kernel={}", id.as_str());
for &chunk in &chunks {
let mut h = hasher_for_kernel(id);
for part in msg.chunks(chunk) {
h.update_with(part, kernel.id, kernel.id);
}
assert_eq!(
h.finalize(),
ours,
"blake3 streaming mismatch kernel={} len={} chunk={}",
id.as_str(),
len,
chunk
);
}
{
let mut h = keyed_hasher_for_kernel(id, KEY);
for part in msg.chunks(63) {
h.update_with(part, kernel.id, kernel.id);
}
let ours = h.finalize();
let expected = *blake3::keyed_hash(KEY, &msg).as_bytes();
assert_eq!(ours, expected, "blake3 keyed mismatch kernel={}", id.as_str());
}
{
let mut h = derive_hasher_for_kernel(id, CONTEXT);
for part in msg.chunks(65) {
h.update_with(part, kernel.id, kernel.id);
}
let ours = h.finalize();
let expected = {
let mut hh = blake3::Hasher::new_derive_key(CONTEXT);
hh.update(&msg);
*hh.finalize().as_bytes()
};
assert_eq!(ours, expected, "blake3 derive mismatch kernel={}", id.as_str());
}
}
}
}
#[cfg(target_arch = "aarch64")]
#[test]
fn oneshot_1024_fast_path_matches_official_crate() {
let caps = crate::platform::caps();
let id = Blake3KernelId::Aarch64Neon;
if !caps.has(required_caps(id)) {
return;
}
let msg = pattern(1024);
let ours = digest_with_kernel(id, &msg);
let expected = *blake3::hash(&msg).as_bytes();
assert_eq!(ours, expected, "blake3 oneshot mismatch kernel={}", id.as_str());
let ours_keyed = Blake3::keyed_digest(KEY, &msg);
let expected_keyed = *blake3::keyed_hash(KEY, &msg).as_bytes();
assert_eq!(ours_keyed, expected_keyed, "blake3 keyed oneshot mismatch");
let ours_derived = Blake3::derive_key(CONTEXT, &msg);
let expected_derived = {
let mut h = blake3::Hasher::new_derive_key(CONTEXT);
h.update(&msg);
*h.finalize().as_bytes()
};
assert_eq!(ours_derived, expected_derived, "blake3 derive-key oneshot mismatch");
}
#[test]
fn xof_prefix_matches_official_crate() {
let caps = crate::platform::caps();
let data = pattern(1234);
for &id in ALL {
if !caps.has(required_caps(id)) {
continue;
}
let mut ours = [0u8; 131];
{
let kernel = kernel_for_id(id);
let mut h = hasher_for_kernel(id);
h.update_with(&data, kernel.id, kernel.id);
let mut xof = h.finalize_xof();
xof.squeeze(&mut ours);
}
let mut expected = [0u8; 131];
{
let mut h = blake3::Hasher::new();
h.update(&data);
let mut out = h.finalize_xof();
out.fill(&mut expected);
}
assert_eq!(ours, expected, "blake3 xof mismatch kernel={}", id.as_str());
}
}
#[test]
fn single_chunk_xof_prefix_matches_official_crate_for_forced_kernels() {
let caps = crate::platform::caps();
let lens = [0usize, 1, 63, 64, 65, 511, 1024];
for &id in ALL {
if !caps.has(required_caps(id)) {
continue;
}
let kernel = kernel_for_id(id);
for &len in &lens {
let msg = pattern(len);
let mut ours = [0u8; 96];
{
let mut h = hasher_for_kernel(id);
for part in msg.chunks(13) {
h.update_with(part, kernel.id, kernel.id);
}
let mut xof = h.finalize_xof();
xof.squeeze(&mut ours[..32]);
xof.squeeze(&mut ours[32..]);
}
let mut expected = [0u8; 96];
{
let mut h = blake3::Hasher::new();
for part in msg.chunks(13) {
h.update(part);
}
let mut xof = h.finalize_xof();
xof.fill(&mut expected[..32]);
xof.fill(&mut expected[32..]);
}
assert_eq!(
ours,
expected,
"single-chunk xof mismatch kernel={} len={len}",
id.as_str()
);
}
}
}
#[test]
fn run_all_agree() {
verify_blake3_kernels(b"abc").expect("kernels should agree");
verify_blake3_kernels(&pattern(8192)).expect("kernels should agree");
}
#[test]
fn oneshot_digest_matches_official_crate() {
#[cfg(not(miri))]
let lens = [
0usize, 1, 2, 3, 63, 64, 65, 1023, 1024, 1025, 2047, 2048, 2049, 4096, 8192, 65_536, 1_048_576,
];
#[cfg(miri)]
let lens = [0usize, 1, 63, 64, 65, 1023, 1024, 1025, 4096, 8192];
for &len in &lens {
let msg = pattern(len);
let ours = Blake3::digest(&msg);
let expected = *blake3::hash(&msg).as_bytes();
assert_eq!(ours, expected, "blake3 oneshot mismatch len={len}");
}
}
#[test]
fn oneshot_keyed_and_derive_match_official_crate() {
#[cfg(not(miri))]
let lens = [0usize, 1, 3, 64, 65, 1024, 4096, 10_000];
#[cfg(miri)]
let lens = [0usize, 1, 64, 65, 1024, 4096];
for &len in &lens {
let msg = pattern(len);
let ours = Blake3::keyed_digest(KEY, &msg);
let expected = *blake3::keyed_hash(KEY, &msg).as_bytes();
assert_eq!(ours, expected, "blake3 keyed oneshot mismatch len={len}");
let ours = Blake3::derive_key(CONTEXT, &msg);
let expected = blake3::derive_key(CONTEXT, &msg);
assert_eq!(ours, expected, "blake3 derive-key oneshot mismatch len={len}");
}
}
#[test]
fn hash_many_kernels_agree() {
use super::super::{CHUNK_LEN, IV, OUT_LEN};
let caps = crate::platform::caps();
let num_chunks = 8usize;
let mut input = vec![0u8; num_chunks * CHUNK_LEN];
for chunk_idx in 0..num_chunks {
let base = chunk_idx * CHUNK_LEN;
for i in 0..CHUNK_LEN {
input[base + i] = ((i % 251) as u8).wrapping_add(chunk_idx as u8);
}
}
let portable_kernel = kernel_for_id(Blake3KernelId::Portable);
let mut reference_out = vec![0u8; num_chunks * OUT_LEN];
unsafe {
(portable_kernel.hash_many_contiguous)(input.as_ptr(), num_chunks, &IV, 0, 0, reference_out.as_mut_ptr());
}
for &id in ALL {
if id == Blake3KernelId::Portable {
continue;
}
if !caps.has(required_caps(id)) {
continue;
}
let k = kernel_for_id(id);
let mut out = vec![0u8; num_chunks * OUT_LEN];
unsafe { (k.hash_many_contiguous)(input.as_ptr(), num_chunks, &IV, 0, 0, out.as_mut_ptr()) };
assert_eq!(
out,
reference_out,
"hash_many_contiguous mismatch: kernel={} differs from portable",
id.as_str()
);
}
}
#[test]
fn hash_many_various_sizes() {
use super::super::{CHUNK_LEN, IV, OUT_LEN};
let caps = crate::platform::caps();
for num_chunks in [1, 2, 3, 4, 5, 7, 8] {
let mut input = vec![0u8; num_chunks * CHUNK_LEN];
for chunk_idx in 0..num_chunks {
let base = chunk_idx * CHUNK_LEN;
for i in 0..CHUNK_LEN {
input[base + i] = ((i % 251) as u8).wrapping_add(chunk_idx as u8);
}
}
let portable_kernel = kernel_for_id(Blake3KernelId::Portable);
let mut reference_out = vec![0u8; num_chunks * OUT_LEN];
unsafe {
(portable_kernel.hash_many_contiguous)(input.as_ptr(), num_chunks, &IV, 0, 0, reference_out.as_mut_ptr());
}
for &id in ALL {
if !caps.has(required_caps(id)) {
continue;
}
let k = kernel_for_id(id);
let mut out = vec![0u8; num_chunks * OUT_LEN];
unsafe { (k.hash_many_contiguous)(input.as_ptr(), num_chunks, &IV, 0, 0, out.as_mut_ptr()) };
assert_eq!(
out,
reference_out,
"hash_many_contiguous mismatch: kernel={} num_chunks={}",
id.as_str(),
num_chunks
);
}
}
}
#[cfg(all(feature = "std", not(miri)))]
#[test]
fn large_inputs_match_official_crate() {
let lens = [512 * 1024 + 123, 4 * 1024 * 1024 + 17];
for &len in &lens {
let msg = pattern(len);
let ours = Blake3::digest(&msg);
let expected = *blake3::hash(&msg).as_bytes();
assert_eq!(ours, expected, "blake3 oneshot mismatch len={}", len);
let mut h = Blake3::new();
h.update(&msg);
let ours_stream = h.finalize();
assert_eq!(ours_stream, expected, "blake3 streaming mismatch len={}", len);
let ours_keyed = Blake3::keyed_digest(KEY, &msg);
let expected_keyed = *blake3::keyed_hash(KEY, &msg).as_bytes();
assert_eq!(ours_keyed, expected_keyed, "blake3 keyed mismatch len={}", len);
let ours_derived = Blake3::derive_key(CONTEXT, &msg);
let expected_derived = {
let mut hh = blake3::Hasher::new_derive_key(CONTEXT);
hh.update(&msg);
*hh.finalize().as_bytes()
};
assert_eq!(ours_derived, expected_derived, "blake3 derive-key mismatch len={}", len);
}
}
}