1
0
Fork 0
mirror of https://github.com/BLAKE3-team/BLAKE3 synced 2024-06-07 07:06:05 +02:00

hash_chunks and hash_parents

This commit is contained in:
Jack O'Connor 2023-06-17 21:27:00 -07:00
parent 1cb2797abc
commit 2722065bb4
3 changed files with 304 additions and 73 deletions

View File

@ -1,5 +1,6 @@
use crate::{portable, CVWords, IncrementCounter, BLOCK_LEN, CHUNK_LEN, UNIVERSAL_HASH_LEN};
use arrayref::{array_mut_ref, array_ref};
use core::ops::{Deref, DerefMut};
cfg_if::cfg_if! {
if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
@ -295,19 +296,10 @@ impl Platform {
// XXX: should separate the thing that hashes the remainder from this interface
}
// Writes out N=num_cvs/2 transposed parent CVs in-place over the first N columns of the input
// CVs. Columns N and above are unmodified. N must be less than or equal to 2*simd_degree. If
// num_cvs is odd, the final input CV is ignored, and the caller should copy it from column
// 2N+1 to column N after this function returns. The PARENT flag is added internally.
pub fn hash_parents(
&self,
cvs: &mut TransposedVectors,
num_cvs: usize,
key: &[u32; 8],
flags: u8,
) {
debug_assert!(num_cvs <= 2 * self.simd_degree());
portable::hash_parents(cvs, num_cvs, key, flags);
pub fn hash_parents(&self, in_out: ParentInOut, key: &[u32; 8], flags: u8) {
let (_, num_parents) = in_out.input();
debug_assert!(num_parents <= self.simd_degree());
portable::hash_parents(in_out, key, flags);
// XXX: should separate the thing that copies the last CV over from this interface
}
@ -559,6 +551,58 @@ pub fn le_bytes_from_words_64(words: &[u32; 16]) -> [u8; 64] {
}
#[cfg_attr(any(target_arch = "x86", target_arch = "x86_64"), repr(C, align(64)))]
#[derive(Default)]
pub struct TransposedVectors(pub [[u32; 2 * MAX_SIMD_DEGREE]; 8]);
pub struct StridedOutput(*mut u32);
impl Deref for TransposedVectors {
type Target = [[u32; 2 * MAX_SIMD_DEGREE]; 8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for TransposedVectors {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub enum ParentInOut<'a> {
InPlace {
in_out: &'a mut TransposedVectors,
num_parents: usize,
},
Separate {
input: &'a TransposedVectors,
num_parents: usize,
output: &'a mut TransposedVectors,
output_column: usize,
},
}
impl<'a> ParentInOut<'a> {
// (vectors, num_parents)
pub fn input(&self) -> (&TransposedVectors, usize) {
match self {
ParentInOut::InPlace {
in_out,
num_parents,
} => (in_out, *num_parents),
ParentInOut::Separate {
input, num_parents, ..
} => (input, *num_parents),
}
}
// (vectors, output_column)
pub fn output(&mut self) -> (&mut TransposedVectors, usize) {
match self {
ParentInOut::InPlace { in_out, .. } => (in_out, 0),
ParentInOut::Separate {
output,
output_column,
..
} => (output, *output_column),
}
}
}

View File

@ -1,6 +1,7 @@
use crate::platform::{ParentInOut, TransposedVectors};
use crate::{
counter_high, counter_low, platform::TransposedVectors, CVBytes, CVWords, IncrementCounter,
BLOCK_LEN, CHUNK_LEN, IV, MSG_SCHEDULE, OUT_LEN, UNIVERSAL_HASH_LEN,
counter_high, counter_low, CVBytes, CVWords, IncrementCounter, BLOCK_LEN, CHUNK_LEN, IV,
MSG_SCHEDULE, OUT_LEN, UNIVERSAL_HASH_LEN,
};
use arrayref::{array_mut_ref, array_ref};
use core::cmp;
@ -178,46 +179,68 @@ pub fn hash_many<const N: usize>(
}
}
/// General contract:
/// - `input` is N chunks, each exactly 1 KiB, 1 <= N <= DEGREE
/// - `output_column` is a multiple of DEGREE.
/// The CHUNK_START and CHUNK_END flags are set internally. Writes N transposed CVs to the output,
/// from `output_column` to `output_column+N-1`. Columns prior to `output_column` must be
/// unmodified.
///
/// The DEGREE of this portable implementation is 1, so the input here is always exactly 1 KiB.
pub fn hash_chunks(
input: &[u8],
key: &[u32; 8],
counter: u64,
flags: u8,
output: &mut TransposedVectors,
output_offset: usize,
output_column: usize,
) {
const LAST_BLOCK_INDEX: usize = (CHUNK_LEN / BLOCK_LEN) - 1;
// There might be a partial chunk at the end. If so, we ignore it here, and the caller will
// hash it separately.
let num_chunks = input.len() / CHUNK_LEN;
for chunk_index in 0..num_chunks {
let mut cv = *key;
for block_index in 0..CHUNK_LEN / BLOCK_LEN {
compress_in_place(
&mut cv,
input[CHUNK_LEN * chunk_index + BLOCK_LEN * block_index..][..BLOCK_LEN]
.try_into()
.unwrap(),
BLOCK_LEN as u8,
counter + chunk_index as u64,
match block_index {
0 => flags | crate::CHUNK_START,
LAST_BLOCK_INDEX => flags | crate::CHUNK_END,
_ => flags,
},
);
}
for word_index in 0..cv.len() {
output.0[word_index][output_offset + chunk_index] = cv[word_index];
}
debug_assert_eq!(CHUNK_LEN, input.len());
let mut cv = *key;
for block_index in 0..16 {
let block_flags = match block_index {
0 => flags | crate::CHUNK_START,
15 => flags | crate::CHUNK_END,
_ => flags,
};
compress_in_place(
&mut cv,
input[BLOCK_LEN * block_index..][..BLOCK_LEN]
.try_into()
.unwrap(),
BLOCK_LEN as u8,
counter as u64,
block_flags,
);
}
for word_index in 0..cv.len() {
output[word_index][output_column] = cv[word_index];
}
}
pub fn hash_parents(cvs: &mut TransposedVectors, num_cvs: usize, key: &[u32; 8], flags: u8) {
// Note that there may be an odd number of children. If there's a leftover child, it gets
// appended to the outputs by the caller. We will not overwrite it.
let num_parents = num_cvs / 2;
todo!()
/// General contract:
/// - `cvs` contains `2*num_parents` transposed CVs, 1 <= num_parents <= DEGREE, starting at column 0
/// There may be additional CVs present beyond the `2*num_parents` CVs indicated, but this function
/// isn't aware of them and must not modify them. No flags are set internally (the caller must set
/// `PARENT` in `flags`). Writes `num_parents` transposed parent CVs to the output, starting at
/// column 0.
///
/// The DEGREE of this portable implementation is 1, so the input here is always exactly 2 CVs
/// (num_parents == 1).
pub fn hash_parents(mut in_out: ParentInOut, key: &[u32; 8], flags: u8) {
let (input, num_parents) = in_out.input();
debug_assert_eq!(num_parents, 1);
let mut block = [0u8; BLOCK_LEN];
for i in 0..8 {
block[4 * i..][..4].copy_from_slice(&input[i][0].to_le_bytes());
block[4 * (i + 8)..][..4].copy_from_slice(&input[i][1].to_le_bytes());
}
let mut cv = *key;
compress_in_place(&mut cv, &block, BLOCK_LEN as u8, 0, flags);
let (output, output_column) = in_out.output();
for i in 0..8 {
output[i][output_column] = cv[i];
}
}
pub fn xof(
@ -290,6 +313,8 @@ pub fn universal_hash(
pub mod test {
use super::*;
const DEGREE: usize = 1;
// These are basically testing the portable implementation against itself, but we also check
// that compress_in_place and compress_xof are consistent. And there are tests against the
// reference implementation and against hardcoded test vectors elsewhere.
@ -305,6 +330,16 @@ pub mod test {
crate::test::test_hash_many_fn(hash_many, hash_many);
}
#[test]
fn test_hash_chunks() {
crate::test::test_hash_chunks_fn(hash_chunks, DEGREE);
}
#[test]
fn test_hash_parents() {
crate::test::test_hash_parents_fn(hash_parents, DEGREE);
}
#[test]
fn test_xof_and_xor() {
crate::test::test_xof_and_xor_fns(xof, xof_xor);

View File

@ -1,6 +1,8 @@
use crate::platform::{ParentInOut, TransposedVectors, MAX_SIMD_DEGREE};
use crate::{
CVBytes, CVWords, IncrementCounter, BLOCK_LEN, CHUNK_LEN, OUT_LEN, UNIVERSAL_HASH_LEN,
};
use arrayref::array_ref;
use arrayvec::ArrayVec;
use core::cmp;
@ -49,8 +51,8 @@ pub const TEST_CASES: &[usize] = &[
pub const TEST_CASES_MAX: usize = 100 * CHUNK_LEN;
// There's a test to make sure these two are equal below.
pub const TEST_KEY: CVBytes = *b"whats the Elvish word for friend";
pub const TEST_KEY_WORDS: CVWords = [
pub const TEST_KEY: &CVBytes = b"whats the Elvish word for friend";
pub const TEST_KEY_WORDS: &CVWords = &[
1952540791, 1752440947, 1816469605, 1752394102, 1919907616, 1868963940, 1919295602, 1684956521,
];
@ -84,7 +86,7 @@ type CompressXofFn = unsafe fn(
// A shared helper function for platform-specific tests.
pub fn test_compress_fn(compress_in_place_fn: CompressInPlaceFn, compress_xof_fn: CompressXofFn) {
let initial_state = TEST_KEY_WORDS;
let initial_state = *TEST_KEY_WORDS;
let block_len: u8 = 61;
let mut block = [0; BLOCK_LEN];
paint_test_input(&mut block[..block_len as usize]);
@ -128,7 +130,7 @@ pub fn test_hash_many_fn(
// 31 (16 + 8 + 4 + 2 + 1) inputs
const NUM_INPUTS: usize = 31;
let mut input_buf = [0; CHUNK_LEN * NUM_INPUTS];
crate::test::paint_test_input(&mut input_buf);
paint_test_input(&mut input_buf);
// First hash chunks.
let mut chunks = ArrayVec::<&[u8; CHUNK_LEN], NUM_INPUTS>::new();
@ -138,7 +140,7 @@ pub fn test_hash_many_fn(
let mut portable_chunks_out = [0; NUM_INPUTS * OUT_LEN];
crate::portable::hash_many(
&chunks,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
IncrementCounter::Yes,
crate::KEYED_HASH,
@ -151,7 +153,7 @@ pub fn test_hash_many_fn(
unsafe {
hash_many_chunks_fn(
&chunks[..],
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
IncrementCounter::Yes,
crate::KEYED_HASH,
@ -177,7 +179,7 @@ pub fn test_hash_many_fn(
let mut portable_parents_out = [0; NUM_INPUTS * OUT_LEN];
crate::portable::hash_many(
&parents,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
IncrementCounter::No,
crate::KEYED_HASH | crate::PARENT,
@ -190,7 +192,7 @@ pub fn test_hash_many_fn(
unsafe {
hash_many_parents_fn(
&parents[..],
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
IncrementCounter::No,
crate::KEYED_HASH | crate::PARENT,
@ -210,6 +212,156 @@ pub fn test_hash_many_fn(
}
}
// Both xof() and xof_xof() have this signature.
type HashChunksFn = unsafe fn(
input: &[u8],
key: &[u32; 8],
counter: u64,
flags: u8,
output: &mut TransposedVectors,
output_column: usize,
);
pub fn test_hash_chunks_fn(target_fn: HashChunksFn, degree: usize) {
assert!(degree <= MAX_SIMD_DEGREE);
let mut input = [0u8; MAX_SIMD_DEGREE * CHUNK_LEN];
paint_test_input(&mut input);
for test_degree in 1..=degree {
for &counter in INITIAL_COUNTERS {
// Make two calls, to test the output_column parameter.
let mut test_output = TransposedVectors::default();
unsafe {
target_fn(
&input[..test_degree * CHUNK_LEN],
TEST_KEY_WORDS,
counter,
crate::KEYED_HASH,
&mut test_output,
0,
);
target_fn(
&input[..test_degree * CHUNK_LEN],
TEST_KEY_WORDS,
counter + test_degree as u64,
crate::KEYED_HASH,
&mut test_output,
test_degree,
);
}
let mut portable_output = TransposedVectors::default();
for i in 0..2 * test_degree {
crate::portable::hash_chunks(
&input[..test_degree * CHUNK_LEN],
TEST_KEY_WORDS,
counter + i as u64,
crate::KEYED_HASH,
&mut portable_output,
i,
);
}
assert_eq!(portable_output.0, test_output.0);
}
}
}
fn paint_transposed_input(input: &mut TransposedVectors) {
let mut val = 0;
for row in 0..8 {
for col in 0..2 * MAX_SIMD_DEGREE {
input[row][col] = val;
val += 1;
}
}
}
// Both xof() and xof_xof() have this signature.
type HashParentsFn = unsafe fn(in_out: ParentInOut, key: &[u32; 8], flags: u8);
pub fn test_hash_parents_fn(target_fn: HashParentsFn, degree: usize) {
assert!(degree <= MAX_SIMD_DEGREE);
for test_degree in 1..=degree {
// separate
{
let mut input = TransposedVectors::default();
paint_transposed_input(&mut input);
let mut test_output = TransposedVectors(input.0);
unsafe {
target_fn(
ParentInOut::Separate {
input: &input,
num_parents: test_degree,
output: &mut test_output,
output_column: 0,
},
TEST_KEY_WORDS,
crate::KEYED_HASH | crate::PARENT,
);
}
let mut portable_output = TransposedVectors(input.0);
for i in 0..test_degree {
for row in 0..8 {
input[row][0] = input[row][2 * i];
input[row][1] = input[row][2 * i + 1];
}
crate::portable::hash_parents(
ParentInOut::Separate {
input: &input,
num_parents: 1,
output: &mut portable_output,
output_column: i,
},
TEST_KEY_WORDS,
crate::KEYED_HASH | crate::PARENT,
);
}
assert_eq!(portable_output.0, test_output.0);
}
// in-place
{
let mut test_io = TransposedVectors::default();
paint_transposed_input(&mut test_io);
unsafe {
target_fn(
ParentInOut::InPlace {
in_out: &mut test_io,
num_parents: test_degree,
},
TEST_KEY_WORDS,
crate::KEYED_HASH | crate::PARENT,
);
}
let mut portable_input = TransposedVectors::default();
let mut portable_output = TransposedVectors::default();
paint_transposed_input(&mut portable_input);
paint_transposed_input(&mut portable_output);
for i in 0..test_degree {
for row in 0..8 {
portable_input[row][0] = portable_input[row][2 * i];
portable_input[row][1] = portable_input[row][2 * i + 1];
}
crate::portable::hash_parents(
ParentInOut::Separate {
input: &portable_input,
num_parents: 1,
output: &mut portable_output,
output_column: i,
},
TEST_KEY_WORDS,
crate::KEYED_HASH | crate::PARENT,
);
}
assert_eq!(portable_output.0, test_io.0);
}
}
}
// Both xof() and xof_xof() have this signature.
type XofFn = unsafe fn(
block: &[u8; BLOCK_LEN],
@ -229,7 +381,7 @@ pub fn test_xof_and_xor_fns(target_xof: XofFn, target_xof_xor: XofFn) {
];
for input_len in [0, 1, BLOCK_LEN] {
let mut input_block = [0u8; BLOCK_LEN];
crate::test::paint_test_input(&mut input_block[..input_len]);
paint_test_input(&mut input_block[..input_len]);
for output_len in [0, 1, BLOCK_LEN, BLOCK_LEN + 1, BLOCK_LEN * NUM_OUTPUTS] {
let mut test_output_buf = [0xff; BLOCK_LEN * NUM_OUTPUTS];
for &counter in INITIAL_COUNTERS {
@ -238,7 +390,7 @@ pub fn test_xof_and_xor_fns(target_xof: XofFn, target_xof_xor: XofFn) {
crate::portable::xof(
&input_block,
input_len as u8,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
flags,
&mut expected_output_buf[..output_len],
@ -248,7 +400,7 @@ pub fn test_xof_and_xor_fns(target_xof: XofFn, target_xof_xor: XofFn) {
target_xof(
&input_block,
input_len as u8,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
flags,
&mut test_output_buf[..output_len],
@ -267,7 +419,7 @@ pub fn test_xof_and_xor_fns(target_xof: XofFn, target_xof_xor: XofFn) {
target_xof_xor(
&input_block,
input_len as u8,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
flags,
&mut test_output_buf[..output_len],
@ -281,7 +433,7 @@ pub fn test_xof_and_xor_fns(target_xof: XofFn, target_xof_xor: XofFn) {
target_xof_xor(
&input_block,
input_len as u8,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
counter,
flags,
&mut test_output_buf[..output_len],
@ -306,7 +458,7 @@ fn test_compare_reference_impl_xof() {
input_block[..input.len()].copy_from_slice(input);
let mut reference_output_buf = [0; BLOCK_LEN * NUM_OUTPUTS];
let mut reference_hasher = reference_impl::Hasher::new_keyed(&TEST_KEY);
let mut reference_hasher = reference_impl::Hasher::new_keyed(TEST_KEY);
reference_hasher.update(input);
reference_hasher.finalize(&mut reference_output_buf);
@ -315,7 +467,7 @@ fn test_compare_reference_impl_xof() {
crate::platform::Platform::detect().xof(
&input_block,
input.len() as u8,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
0,
crate::KEYED_HASH | crate::CHUNK_START | crate::CHUNK_END | crate::ROOT,
&mut test_output_buf[..output_len],
@ -334,7 +486,7 @@ fn test_compare_reference_impl_xof() {
crate::platform::Platform::detect().xof(
&input_block,
input.len() as u8,
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
1,
crate::KEYED_HASH | crate::CHUNK_START | crate::CHUNK_END | crate::ROOT,
&mut test_output_buf[..output_len - BLOCK_LEN],
@ -354,12 +506,12 @@ pub fn test_universal_hash_fn(target_fn: UniversalHashFn) {
// 31 (16 + 8 + 4 + 2 + 1) inputs
const NUM_INPUTS: usize = 31;
let mut input_buf = [0; BLOCK_LEN * NUM_INPUTS];
crate::test::paint_test_input(&mut input_buf);
paint_test_input(&mut input_buf);
for len in [0, 1, BLOCK_LEN, BLOCK_LEN + 1, input_buf.len()] {
for &counter in INITIAL_COUNTERS {
let portable_output =
crate::portable::universal_hash(&input_buf[..len], &TEST_KEY_WORDS, counter);
let test_output = unsafe { target_fn(&input_buf[..len], &TEST_KEY_WORDS, counter) };
crate::portable::universal_hash(&input_buf[..len], TEST_KEY_WORDS, counter);
let test_output = unsafe { target_fn(&input_buf[..len], TEST_KEY_WORDS, counter) };
assert_eq!(portable_output, test_output);
}
}
@ -396,12 +548,12 @@ fn reference_impl_universal_hash(
fn test_compare_reference_impl_universal_hash() {
const NUM_INPUTS: usize = 31;
let mut input_buf = [0; BLOCK_LEN * NUM_INPUTS];
crate::test::paint_test_input(&mut input_buf);
paint_test_input(&mut input_buf);
for len in [0, 1, BLOCK_LEN, BLOCK_LEN + 1, input_buf.len()] {
let reference_output = reference_impl_universal_hash(&input_buf[..len], &TEST_KEY);
let reference_output = reference_impl_universal_hash(&input_buf[..len], TEST_KEY);
let test_output = crate::platform::Platform::detect().universal_hash(
&input_buf[..len],
&TEST_KEY_WORDS,
TEST_KEY_WORDS,
0,
);
assert_eq!(reference_output, test_output);
@ -412,7 +564,7 @@ fn test_compare_reference_impl_universal_hash() {
fn test_key_bytes_equal_key_words() {
assert_eq!(
TEST_KEY_WORDS,
crate::platform::words_from_le_bytes_32(&TEST_KEY),
&crate::platform::words_from_le_bytes_32(TEST_KEY),
);
}
@ -516,23 +668,23 @@ fn test_compare_reference_impl() {
// keyed
{
let mut reference_hasher = reference_impl::Hasher::new_keyed(&TEST_KEY);
let mut reference_hasher = reference_impl::Hasher::new_keyed(TEST_KEY);
reference_hasher.update(input);
let mut expected_out = [0; OUT];
reference_hasher.finalize(&mut expected_out);
// all at once
let test_out = crate::keyed_hash(&TEST_KEY, input);
let test_out = crate::keyed_hash(TEST_KEY, input);
assert_eq!(test_out, *array_ref!(expected_out, 0, 32));
// incremental
let mut hasher = crate::Hasher::new_keyed(&TEST_KEY);
let mut hasher = crate::Hasher::new_keyed(TEST_KEY);
hasher.update(input);
assert_eq!(hasher.finalize(), *array_ref!(expected_out, 0, 32));
assert_eq!(hasher.finalize(), test_out);
// incremental (rayon)
#[cfg(feature = "rayon")]
{
let mut hasher = crate::Hasher::new_keyed(&TEST_KEY);
let mut hasher = crate::Hasher::new_keyed(TEST_KEY);
hasher.update_rayon(input);
assert_eq!(hasher.finalize(), *array_ref!(expected_out, 0, 32));
assert_eq!(hasher.finalize(), test_out);