mirror of
https://github.com/BLAKE3-team/BLAKE3
synced 2024-04-19 16:33:59 +02:00
add portable.rs
This commit is contained in:
parent
2030966062
commit
21df6b1103
|
@ -0,0 +1,2 @@
|
|||
Cargo.lock
|
||||
target
|
|
@ -0,0 +1,25 @@
|
|||
[package]
|
||||
name = "blake3"
|
||||
version = "0.1.0"
|
||||
authors = ["Jack O'Connor <oconnor663@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["c", "std"]
|
||||
c = []
|
||||
std = []
|
||||
|
||||
[dependencies]
|
||||
arrayref = "0.3.5"
|
||||
arrayvec = { version = "0.5.1", default-features = false, features = ["array-sizes-33-128"] }
|
||||
bitflags = "1.2.1"
|
||||
constant_time_eq = "0.1.4"
|
||||
rayon = { version = "1.2.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
page_size = "0.4.1"
|
||||
rand = "0.7.2"
|
||||
reference_impl = { path = "./reference_impl" }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0.47"
|
|
@ -0,0 +1,106 @@
|
|||
use arrayref::{array_refs, mut_array_refs};
|
||||
|
||||
mod portable;
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
/// The default number of bytes in a hash, 32.
|
||||
pub const OUT_LEN: usize = 32;
|
||||
|
||||
/// The number of bytes in a key, 32.
|
||||
pub const KEY_LEN: usize = 32;
|
||||
|
||||
// These are pub for tests and benchmarks. Callers don't need them.
|
||||
#[doc(hidden)]
|
||||
pub const BLOCK_LEN: usize = 64;
|
||||
#[doc(hidden)]
|
||||
pub const CHUNK_LEN: usize = 2048;
|
||||
|
||||
const IV: [u32; 8] = [
|
||||
0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19,
|
||||
];
|
||||
|
||||
const MSG_SCHEDULE: [[usize; 16]; 7] = [
|
||||
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
|
||||
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
|
||||
[7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
|
||||
[9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
|
||||
[2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
|
||||
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
|
||||
];
|
||||
|
||||
const CHUNK_OFFSET_DELTAS: &[u64; 16] = &[
|
||||
CHUNK_LEN as u64 * 0,
|
||||
CHUNK_LEN as u64 * 1,
|
||||
CHUNK_LEN as u64 * 2,
|
||||
CHUNK_LEN as u64 * 3,
|
||||
CHUNK_LEN as u64 * 4,
|
||||
CHUNK_LEN as u64 * 5,
|
||||
CHUNK_LEN as u64 * 6,
|
||||
CHUNK_LEN as u64 * 7,
|
||||
CHUNK_LEN as u64 * 8,
|
||||
CHUNK_LEN as u64 * 9,
|
||||
CHUNK_LEN as u64 * 10,
|
||||
CHUNK_LEN as u64 * 11,
|
||||
CHUNK_LEN as u64 * 12,
|
||||
CHUNK_LEN as u64 * 13,
|
||||
CHUNK_LEN as u64 * 14,
|
||||
CHUNK_LEN as u64 * 15,
|
||||
];
|
||||
|
||||
const PARENT_OFFSET_DELTAS: &[u64; 16] = &[0; 16];
|
||||
|
||||
// These are the internal flags that we use to domain separate root/non-root,
|
||||
// chunk/parent, and chunk beginning/middle/end. These get set at the high end
|
||||
// of the block flags word in the compression function, so their values start
|
||||
// high and go down.
|
||||
bitflags::bitflags! {
|
||||
struct Flags: u8 {
|
||||
const CHUNK_START = 1 << 0;
|
||||
const CHUNK_END = 1 << 1;
|
||||
const PARENT = 1 << 2;
|
||||
const ROOT = 1 << 3;
|
||||
const KEYED_HASH = 1 << 4;
|
||||
const DERIVE_KEY = 1 << 5;
|
||||
}
|
||||
}
|
||||
|
||||
fn words_from_key_bytes(bytes: &[u8; KEY_LEN]) -> [u32; 8] {
|
||||
// Parse the message bytes as little endian words.
|
||||
let refs = array_refs!(bytes, 4, 4, 4, 4, 4, 4, 4, 4);
|
||||
[
|
||||
u32::from_le_bytes(*refs.0),
|
||||
u32::from_le_bytes(*refs.1),
|
||||
u32::from_le_bytes(*refs.2),
|
||||
u32::from_le_bytes(*refs.3),
|
||||
u32::from_le_bytes(*refs.4),
|
||||
u32::from_le_bytes(*refs.5),
|
||||
u32::from_le_bytes(*refs.6),
|
||||
u32::from_le_bytes(*refs.7),
|
||||
]
|
||||
}
|
||||
|
||||
fn bytes_from_state_words(words: &[u32; 8]) -> [u8; OUT_LEN] {
|
||||
let mut bytes = [0; OUT_LEN];
|
||||
{
|
||||
let refs = mut_array_refs!(&mut bytes, 4, 4, 4, 4, 4, 4, 4, 4);
|
||||
*refs.0 = words[0].to_le_bytes();
|
||||
*refs.1 = words[1].to_le_bytes();
|
||||
*refs.2 = words[2].to_le_bytes();
|
||||
*refs.3 = words[3].to_le_bytes();
|
||||
*refs.4 = words[4].to_le_bytes();
|
||||
*refs.5 = words[5].to_le_bytes();
|
||||
*refs.6 = words[6].to_le_bytes();
|
||||
*refs.7 = words[7].to_le_bytes();
|
||||
}
|
||||
bytes
|
||||
}
|
||||
|
||||
fn offset_low(offset: u64) -> u32 {
|
||||
offset as u32
|
||||
}
|
||||
|
||||
fn offset_high(offset: u64) -> u32 {
|
||||
(offset >> 32) as u32
|
||||
}
|
|
@ -0,0 +1,314 @@
|
|||
use crate::{offset_high, offset_low, BLOCK_LEN, IV, MSG_SCHEDULE, OUT_LEN};
|
||||
use arrayref::{array_mut_ref, array_ref, array_refs};
|
||||
|
||||
#[inline(always)]
|
||||
fn words_from_block(bytes: &[u8; BLOCK_LEN]) -> [u32; 16] {
|
||||
let refs = array_refs!(bytes, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4);
|
||||
[
|
||||
u32::from_le_bytes(*refs.0),
|
||||
u32::from_le_bytes(*refs.1),
|
||||
u32::from_le_bytes(*refs.2),
|
||||
u32::from_le_bytes(*refs.3),
|
||||
u32::from_le_bytes(*refs.4),
|
||||
u32::from_le_bytes(*refs.5),
|
||||
u32::from_le_bytes(*refs.6),
|
||||
u32::from_le_bytes(*refs.7),
|
||||
u32::from_le_bytes(*refs.8),
|
||||
u32::from_le_bytes(*refs.9),
|
||||
u32::from_le_bytes(*refs.10),
|
||||
u32::from_le_bytes(*refs.11),
|
||||
u32::from_le_bytes(*refs.12),
|
||||
u32::from_le_bytes(*refs.13),
|
||||
u32::from_le_bytes(*refs.14),
|
||||
u32::from_le_bytes(*refs.15),
|
||||
]
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn g(state: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize, x: u32, y: u32) {
|
||||
state[a] = state[a].wrapping_add(state[b]).wrapping_add(x);
|
||||
state[d] = (state[d] ^ state[a]).rotate_right(16);
|
||||
state[c] = state[c].wrapping_add(state[d]);
|
||||
state[b] = (state[b] ^ state[c]).rotate_right(12);
|
||||
state[a] = state[a].wrapping_add(state[b]).wrapping_add(y);
|
||||
state[d] = (state[d] ^ state[a]).rotate_right(8);
|
||||
state[c] = state[c].wrapping_add(state[d]);
|
||||
state[b] = (state[b] ^ state[c]).rotate_right(7);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn round(state: &mut [u32; 16], msg: &[u32; 16], round: usize) {
|
||||
// Select the message schedule based on the round.
|
||||
let schedule = MSG_SCHEDULE[round];
|
||||
|
||||
// Mix the columns.
|
||||
g(state, 0, 4, 8, 12, msg[schedule[0]], msg[schedule[1]]);
|
||||
g(state, 1, 5, 9, 13, msg[schedule[2]], msg[schedule[3]]);
|
||||
g(state, 2, 6, 10, 14, msg[schedule[4]], msg[schedule[5]]);
|
||||
g(state, 3, 7, 11, 15, msg[schedule[6]], msg[schedule[7]]);
|
||||
|
||||
// Mix the diagonals.
|
||||
g(state, 0, 5, 10, 15, msg[schedule[8]], msg[schedule[9]]);
|
||||
g(state, 1, 6, 11, 12, msg[schedule[10]], msg[schedule[11]]);
|
||||
g(state, 2, 7, 8, 13, msg[schedule[12]], msg[schedule[13]]);
|
||||
g(state, 3, 4, 9, 14, msg[schedule[14]], msg[schedule[15]]);
|
||||
}
|
||||
|
||||
fn compress(
|
||||
cv: &[u32; 8],
|
||||
block: &[u8; BLOCK_LEN],
|
||||
block_len: u8,
|
||||
offset: u64,
|
||||
flags: u8,
|
||||
) -> [u32; 16] {
|
||||
let block_words = words_from_block(block);
|
||||
let mut state = [
|
||||
cv[0],
|
||||
cv[1],
|
||||
cv[2],
|
||||
cv[3],
|
||||
cv[4],
|
||||
cv[5],
|
||||
cv[6],
|
||||
cv[7],
|
||||
IV[0],
|
||||
IV[1],
|
||||
IV[2],
|
||||
IV[3],
|
||||
offset_low(offset),
|
||||
offset_high(offset),
|
||||
block_len as u32,
|
||||
flags as u32,
|
||||
];
|
||||
|
||||
round(&mut state, &block_words, 0);
|
||||
round(&mut state, &block_words, 1);
|
||||
round(&mut state, &block_words, 2);
|
||||
round(&mut state, &block_words, 3);
|
||||
round(&mut state, &block_words, 4);
|
||||
round(&mut state, &block_words, 5);
|
||||
round(&mut state, &block_words, 6);
|
||||
|
||||
state[0] ^= state[8];
|
||||
state[1] ^= state[9];
|
||||
state[2] ^= state[10];
|
||||
state[3] ^= state[11];
|
||||
state[4] ^= state[12];
|
||||
state[5] ^= state[13];
|
||||
state[6] ^= state[14];
|
||||
state[7] ^= state[15];
|
||||
state[8] ^= cv[0];
|
||||
state[9] ^= cv[1];
|
||||
state[10] ^= cv[2];
|
||||
state[11] ^= cv[3];
|
||||
state[12] ^= cv[4];
|
||||
state[13] ^= cv[5];
|
||||
state[14] ^= cv[6];
|
||||
state[15] ^= cv[7];
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
pub fn hash1<A: arrayvec::Array<Item = u8>>(
|
||||
input: &A,
|
||||
key: &[u32; 8],
|
||||
offset: u64,
|
||||
flags: u8,
|
||||
flags_start: u8,
|
||||
flags_end: u8,
|
||||
out: &mut [u8; OUT_LEN],
|
||||
) {
|
||||
debug_assert_eq!(A::CAPACITY % BLOCK_LEN, 0, "uneven blocks");
|
||||
let mut cv = *key;
|
||||
let mut block_flags = flags | flags_start;
|
||||
let mut slice = input.as_slice();
|
||||
while slice.len() >= BLOCK_LEN {
|
||||
if slice.len() == BLOCK_LEN {
|
||||
block_flags |= flags_end;
|
||||
}
|
||||
let output = compress(
|
||||
&cv,
|
||||
array_ref!(slice, 0, BLOCK_LEN),
|
||||
BLOCK_LEN as u8,
|
||||
offset,
|
||||
block_flags,
|
||||
);
|
||||
cv = *array_ref!(output, 0, 8);
|
||||
block_flags = flags;
|
||||
slice = &slice[BLOCK_LEN..];
|
||||
}
|
||||
*out = crate::bytes_from_state_words(&cv);
|
||||
}
|
||||
|
||||
pub fn hash_many<A: arrayvec::Array<Item = u8>>(
|
||||
inputs: &[&A],
|
||||
key: &[u32; 8],
|
||||
mut offset: u64,
|
||||
offset_deltas: &[u64; 16],
|
||||
flags: u8,
|
||||
flags_start: u8,
|
||||
flags_end: u8,
|
||||
out: &mut [u8],
|
||||
) {
|
||||
debug_assert!(out.len() >= inputs.len() * OUT_LEN, "out too short");
|
||||
for (&input, output) in inputs.iter().zip(out.chunks_exact_mut(OUT_LEN)) {
|
||||
hash1(
|
||||
input,
|
||||
key,
|
||||
offset,
|
||||
flags,
|
||||
flags_start,
|
||||
flags_end,
|
||||
array_mut_ref!(output, 0, OUT_LEN),
|
||||
);
|
||||
offset += offset_deltas[1];
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_hash1_1() {
|
||||
let block = [1; BLOCK_LEN];
|
||||
let key = [2; 8];
|
||||
let offset = 3 * crate::CHUNK_LEN as u64;
|
||||
let flags = 4;
|
||||
let flags_start = 8;
|
||||
let flags_end = 16;
|
||||
|
||||
let out = compress(
|
||||
&key,
|
||||
&block,
|
||||
BLOCK_LEN as u8,
|
||||
offset,
|
||||
flags | flags_start | flags_end,
|
||||
);
|
||||
let expected_out = crate::bytes_from_state_words(array_ref!(out, 0, 8));
|
||||
|
||||
let mut test_out = [0; OUT_LEN];
|
||||
hash1(
|
||||
&block,
|
||||
&key,
|
||||
offset,
|
||||
flags,
|
||||
flags_start,
|
||||
flags_end,
|
||||
&mut test_out,
|
||||
);
|
||||
|
||||
assert_eq!(expected_out, test_out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash1_3() {
|
||||
let mut blocks = [0; BLOCK_LEN * 3];
|
||||
crate::test::paint_test_input(&mut blocks);
|
||||
let key = [2; 8];
|
||||
let offset = 3 * crate::CHUNK_LEN as u64;
|
||||
let flags = 4;
|
||||
let flags_start = 8;
|
||||
let flags_end = 16;
|
||||
|
||||
let mut expected_cv = key;
|
||||
let out = compress(
|
||||
&mut expected_cv,
|
||||
array_ref!(blocks, 0, BLOCK_LEN),
|
||||
BLOCK_LEN as u8,
|
||||
offset,
|
||||
flags | flags_start,
|
||||
);
|
||||
expected_cv = *array_ref!(out, 0, 8);
|
||||
let out = compress(
|
||||
&mut expected_cv,
|
||||
array_ref!(blocks, BLOCK_LEN, BLOCK_LEN),
|
||||
BLOCK_LEN as u8,
|
||||
offset,
|
||||
flags,
|
||||
);
|
||||
expected_cv = *array_ref!(out, 0, 8);
|
||||
let out = compress(
|
||||
&mut expected_cv,
|
||||
array_ref!(blocks, 2 * BLOCK_LEN, BLOCK_LEN),
|
||||
BLOCK_LEN as u8,
|
||||
offset,
|
||||
flags | flags_end,
|
||||
);
|
||||
expected_cv = *array_ref!(out, 0, 8);
|
||||
let expected_out = crate::bytes_from_state_words(&expected_cv);
|
||||
|
||||
let mut test_out = [0; OUT_LEN];
|
||||
hash1(
|
||||
&blocks,
|
||||
&key,
|
||||
offset,
|
||||
flags,
|
||||
flags_start,
|
||||
flags_end,
|
||||
&mut test_out,
|
||||
);
|
||||
|
||||
assert_eq!(expected_out, test_out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_many() {
|
||||
let mut input_buf = [0; BLOCK_LEN * 9];
|
||||
crate::test::paint_test_input(&mut input_buf);
|
||||
let inputs = [
|
||||
array_ref!(input_buf, 0 * BLOCK_LEN, 3 * BLOCK_LEN),
|
||||
array_ref!(input_buf, 3 * BLOCK_LEN, 3 * BLOCK_LEN),
|
||||
array_ref!(input_buf, 6 * BLOCK_LEN, 3 * BLOCK_LEN),
|
||||
];
|
||||
let key = [2; 8];
|
||||
let offset = 3 * crate::CHUNK_LEN as u64;
|
||||
let delta = crate::CHUNK_LEN as u64;
|
||||
let flags = 4;
|
||||
let flags_start = 8;
|
||||
let flags_end = 16;
|
||||
|
||||
let mut expected_out = [0; 3 * OUT_LEN];
|
||||
hash1(
|
||||
inputs[0],
|
||||
&key,
|
||||
offset + 0 * delta,
|
||||
flags,
|
||||
flags_start,
|
||||
flags_end,
|
||||
array_mut_ref!(&mut expected_out, 0 * OUT_LEN, OUT_LEN),
|
||||
);
|
||||
hash1(
|
||||
inputs[1],
|
||||
&key,
|
||||
offset + 1 * delta,
|
||||
flags,
|
||||
flags_start,
|
||||
flags_end,
|
||||
array_mut_ref!(&mut expected_out, 1 * OUT_LEN, OUT_LEN),
|
||||
);
|
||||
hash1(
|
||||
inputs[2],
|
||||
&key,
|
||||
offset + 2 * delta,
|
||||
flags,
|
||||
flags_start,
|
||||
flags_end,
|
||||
array_mut_ref!(&mut expected_out, 2 * OUT_LEN, OUT_LEN),
|
||||
);
|
||||
|
||||
let mut test_out = [0; OUT_LEN * 3];
|
||||
hash_many(
|
||||
&inputs,
|
||||
&key,
|
||||
offset,
|
||||
crate::CHUNK_OFFSET_DELTAS,
|
||||
flags,
|
||||
flags_start,
|
||||
flags_end,
|
||||
&mut test_out,
|
||||
);
|
||||
|
||||
assert_eq!(&expected_out[..], &test_out[..]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
use core::cmp;
|
||||
|
||||
// Paint a byte pattern that won't repeat, so that we don't accidentally
|
||||
// miss buffer offset bugs.
|
||||
pub fn paint_test_input(buf: &mut [u8]) {
|
||||
let mut offset = 0;
|
||||
let mut counter: u32 = 1;
|
||||
while offset < buf.len() {
|
||||
let bytes = counter.to_le_bytes();
|
||||
let take = cmp::min(bytes.len(), buf.len() - offset);
|
||||
buf[offset..][..take].copy_from_slice(&bytes[..take]);
|
||||
counter += 1;
|
||||
offset += take;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue