You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
389 lines
13 KiB
389 lines
13 KiB
// Copyright 2015 blake2-rfc Developers
|
|
//
|
|
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
|
|
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
|
|
// http://opensource.org/licenses/MIT>, at your option. This file may not be
|
|
// copied, modified, or distributed except according to those terms.
|
|
|
|
use arrayvec::ArrayVec;
|
|
|
|
pub const SIGMA: [[usize; 16]; 10] = [
|
|
[ 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],
|
|
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
|
|
[ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
|
|
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
|
|
];
|
|
|
|
macro_rules! blake2_impl {
|
|
($state:ident, $result:ident, $func:ident, $word:ident, $vec:ident,
|
|
$bytes:expr, $R1:expr, $R2:expr, $R3:expr, $R4:expr, $IV:expr) => {
|
|
use core::cmp;
|
|
|
|
#[cfg(feature = "std")]
|
|
use std::io;
|
|
|
|
use $crate::as_bytes::AsBytes;
|
|
use $crate::bytes::BytesExt;
|
|
use $crate::constant_time_eq::constant_time_eq;
|
|
use $crate::simd::{Vector4, $vec};
|
|
|
|
/// Container for a hash result.
|
|
///
|
|
/// This container uses a constant-time comparison for equality.
|
|
/// If a constant-time comparison is not necessary, the hash
|
|
/// result can be extracted with the `as_bytes` method.
|
|
#[derive(Clone, Copy, Debug)]
|
|
pub struct $result {
|
|
h: [$vec; 2],
|
|
nn: usize,
|
|
}
|
|
|
|
#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))]
|
|
impl $result {
|
|
/// Returns the contained hash result as a byte string.
|
|
#[inline]
|
|
pub fn as_bytes(&self) -> &[u8] { &self.h.as_bytes()[..self.nn] }
|
|
|
|
/// Returns the length of the hash result.
|
|
///
|
|
/// This is the same value that was used to create the hash
|
|
/// context.
|
|
#[inline]
|
|
pub fn len(&self) -> usize { self.nn }
|
|
}
|
|
|
|
impl AsRef<[u8]> for $result {
|
|
#[inline]
|
|
fn as_ref(&self) -> &[u8] { self.as_bytes() }
|
|
}
|
|
|
|
impl Eq for $result { }
|
|
|
|
impl PartialEq for $result {
|
|
#[inline]
|
|
fn eq(&self, other: &Self) -> bool {
|
|
constant_time_eq(self.as_bytes(), other.as_bytes())
|
|
}
|
|
}
|
|
|
|
impl PartialEq<[u8]> for $result {
|
|
#[inline]
|
|
fn eq(&self, other: &[u8]) -> bool {
|
|
constant_time_eq(self.as_bytes(), other)
|
|
}
|
|
}
|
|
|
|
/// State context.
|
|
#[derive(Clone, Debug)]
|
|
pub struct $state {
|
|
m: [$word; 16],
|
|
h: [$vec; 2],
|
|
t: u64,
|
|
nn: usize,
|
|
}
|
|
|
|
const IV: [$word; 8] = $IV;
|
|
|
|
#[inline(always)]
|
|
fn iv0() -> $vec { $vec::new(IV[0], IV[1], IV[2], IV[3]) }
|
|
#[inline(always)]
|
|
fn iv1() -> $vec { $vec::new(IV[4], IV[5], IV[6], IV[7]) }
|
|
|
|
/// Convenience function for all-in-one computation.
|
|
pub fn $func(nn: usize, k: &[u8], data: &[u8]) -> $result {
|
|
let mut state = $state::with_key(nn, k);
|
|
state.update(data);
|
|
state.finalize()
|
|
}
|
|
|
|
impl $state {
|
|
/// Creates a new hashing context without a key.
|
|
pub fn new(nn: usize) -> Self { Self::with_key(nn, &[]) }
|
|
|
|
/// Creates a new hashing context with a key.
|
|
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
|
|
pub fn with_key(nn: usize, k: &[u8]) -> Self {
|
|
let kk = k.len();
|
|
assert!(nn >= 1 && nn <= $bytes && kk <= $bytes);
|
|
|
|
let p0 = 0x01010000 ^ ((kk as $word) << 8) ^ (nn as $word);
|
|
let mut state = $state {
|
|
m: [0; 16],
|
|
h: [iv0() ^ $vec::new(p0, 0, 0, 0), iv1()],
|
|
t: 0,
|
|
nn: nn,
|
|
};
|
|
|
|
if kk > 0 {
|
|
state.m.as_mut_bytes().copy_bytes_from(k);
|
|
state.t = $bytes * 2;
|
|
}
|
|
state
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
|
|
pub fn with_parameter_block(p: &[$word; 8]) -> Self {
|
|
let nn = p[0] as u8 as usize;
|
|
let kk = (p[0] >> 8) as u8 as usize;
|
|
assert!(nn >= 1 && nn <= $bytes && kk <= $bytes);
|
|
|
|
$state {
|
|
m: [0; 16],
|
|
h: [iv0() ^ $vec::new(p[0], p[1], p[2], p[3]),
|
|
iv1() ^ $vec::new(p[4], p[5], p[6], p[7])],
|
|
t: 0,
|
|
nn: nn,
|
|
}
|
|
}
|
|
|
|
/// Updates the hashing context with more data.
|
|
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
|
|
pub fn update(&mut self, data: &[u8]) {
|
|
let mut rest = data;
|
|
|
|
let off = (self.t % ($bytes * 2)) as usize;
|
|
if off != 0 || self.t == 0 {
|
|
let len = cmp::min(($bytes * 2) - off, rest.len());
|
|
|
|
let part = &rest[..len];
|
|
rest = &rest[part.len()..];
|
|
|
|
self.m.as_mut_bytes()[off..].copy_bytes_from(part);
|
|
self.t = self.t.checked_add(part.len() as u64)
|
|
.expect("hash data length overflow");
|
|
}
|
|
|
|
while rest.len() >= $bytes * 2 {
|
|
self.compress(0, 0);
|
|
|
|
let part = &rest[..($bytes * 2)];
|
|
rest = &rest[part.len()..];
|
|
|
|
self.m.as_mut_bytes().copy_bytes_from(part);
|
|
self.t = self.t.checked_add(part.len() as u64)
|
|
.expect("hash data length overflow");
|
|
}
|
|
|
|
if rest.len() > 0 {
|
|
self.compress(0, 0);
|
|
|
|
self.m.as_mut_bytes().copy_bytes_from(rest);
|
|
self.t = self.t.checked_add(rest.len() as u64)
|
|
.expect("hash data length overflow");
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
|
|
fn finalize_with_flag(&mut self, f1: $word) {
|
|
let off = (self.t % ($bytes * 2)) as usize;
|
|
if off != 0 {
|
|
self.m.as_mut_bytes()[off..].set_bytes(0);
|
|
}
|
|
|
|
self.compress(!0, f1);
|
|
}
|
|
|
|
/// Consumes the hashing context and returns the resulting hash.
|
|
#[inline]
|
|
pub fn finalize(mut self) -> $result {
|
|
self.finalize_with_flag(0);
|
|
self.into_result()
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
#[inline]
|
|
pub fn finalize_last_node(mut self) -> $result {
|
|
self.finalize_with_flag(!0);
|
|
self.into_result()
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub fn finalize_inplace(&mut self) -> &[u8] {
|
|
self.finalize_with_flag(0);
|
|
self.result_inplace()
|
|
}
|
|
|
|
#[doc(hidden)]
|
|
pub fn finalize_last_node_inplace(&mut self) -> &[u8] {
|
|
self.finalize_with_flag(!0);
|
|
self.result_inplace()
|
|
}
|
|
|
|
#[inline]
|
|
fn into_result(self) -> $result {
|
|
$result {
|
|
h: [self.h[0].to_le(), self.h[1].to_le()],
|
|
nn: self.nn,
|
|
}
|
|
}
|
|
|
|
fn result_inplace(&mut self) -> &[u8] {
|
|
self.h[0] = self.h[0].to_le();
|
|
self.h[1] = self.h[1].to_le();
|
|
|
|
let result = &self.h.as_bytes()[..self.nn];
|
|
self.nn = 0; // poison self
|
|
result
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn quarter_round(v: &mut [$vec; 4], rd: u32, rb: u32, m: $vec) {
|
|
v[0] = v[0].wrapping_add(v[1]).wrapping_add(m.from_le());
|
|
v[3] = (v[3] ^ v[0]).rotate_right_const(rd);
|
|
v[2] = v[2].wrapping_add(v[3]);
|
|
v[1] = (v[1] ^ v[2]).rotate_right_const(rb);
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn shuffle(v: &mut [$vec; 4]) {
|
|
v[1] = v[1].shuffle_left_1();
|
|
v[2] = v[2].shuffle_left_2();
|
|
v[3] = v[3].shuffle_left_3();
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn unshuffle(v: &mut [$vec; 4]) {
|
|
v[1] = v[1].shuffle_right_1();
|
|
v[2] = v[2].shuffle_right_2();
|
|
v[3] = v[3].shuffle_right_3();
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn round(v: &mut [$vec; 4], m: &[$word; 16], s: &[usize; 16]) {
|
|
$state::quarter_round(v, $R1, $R2, $vec::gather(m,
|
|
s[ 0], s[ 2], s[ 4], s[ 6]));
|
|
$state::quarter_round(v, $R3, $R4, $vec::gather(m,
|
|
s[ 1], s[ 3], s[ 5], s[ 7]));
|
|
|
|
$state::shuffle(v);
|
|
$state::quarter_round(v, $R1, $R2, $vec::gather(m,
|
|
s[ 8], s[10], s[12], s[14]));
|
|
$state::quarter_round(v, $R3, $R4, $vec::gather(m,
|
|
s[ 9], s[11], s[13], s[15]));
|
|
$state::unshuffle(v);
|
|
}
|
|
|
|
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation, eq_op))]
|
|
fn compress(&mut self, f0: $word, f1: $word) {
|
|
use $crate::blake2::SIGMA;
|
|
|
|
let m = &self.m;
|
|
let h = &mut self.h;
|
|
|
|
let t0 = self.t as $word;
|
|
let t1 = match $bytes {
|
|
64 => 0,
|
|
32 => (self.t >> 32) as $word,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let mut v = [
|
|
h[0],
|
|
h[1],
|
|
iv0(),
|
|
iv1() ^ $vec::new(t0, t1, f0, f1),
|
|
];
|
|
|
|
$state::round(&mut v, m, &SIGMA[0]);
|
|
$state::round(&mut v, m, &SIGMA[1]);
|
|
$state::round(&mut v, m, &SIGMA[2]);
|
|
$state::round(&mut v, m, &SIGMA[3]);
|
|
$state::round(&mut v, m, &SIGMA[4]);
|
|
$state::round(&mut v, m, &SIGMA[5]);
|
|
$state::round(&mut v, m, &SIGMA[6]);
|
|
$state::round(&mut v, m, &SIGMA[7]);
|
|
$state::round(&mut v, m, &SIGMA[8]);
|
|
$state::round(&mut v, m, &SIGMA[9]);
|
|
if $bytes > 32 {
|
|
$state::round(&mut v, m, &SIGMA[0]);
|
|
$state::round(&mut v, m, &SIGMA[1]);
|
|
}
|
|
|
|
h[0] = h[0] ^ (v[0] ^ v[2]);
|
|
h[1] = h[1] ^ (v[1] ^ v[3]);
|
|
}
|
|
}
|
|
|
|
impl Default for $state {
|
|
fn default() -> Self {
|
|
Self::new($bytes)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "std")]
|
|
impl io::Write for $state {
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
if self.t.checked_add(buf.len() as u64).is_none() {
|
|
return Err(io::Error::new(io::ErrorKind::WriteZero,
|
|
"counter overflow"));
|
|
}
|
|
|
|
self.update(buf);
|
|
Ok(buf.len())
|
|
}
|
|
|
|
#[inline]
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation, unreadable_literal))]
|
|
#[cold]
|
|
#[doc(hidden)]
|
|
pub fn selftest_seq(len: usize) -> ArrayVec<[u8; 1024]> {
|
|
use core::num::Wrapping;
|
|
|
|
let seed = Wrapping(len as u32);
|
|
let mut out = ArrayVec::new();
|
|
|
|
let mut a = Wrapping(0xDEAD4BAD) * seed;
|
|
let mut b = Wrapping(1);
|
|
|
|
for _ in 0..len {
|
|
let t = a + b;
|
|
a = b;
|
|
b = t;
|
|
out.push((t >> 24).0 as u8);
|
|
}
|
|
out
|
|
}
|
|
|
|
macro_rules! blake2_selftest_impl {
|
|
($state:ident, $func:ident, $res:expr, $md_len:expr, $in_len:expr) => {
|
|
/// Runs the self-test for this hash function.
|
|
#[cold]
|
|
pub fn selftest() {
|
|
use $crate::blake2::selftest_seq;
|
|
|
|
const BLAKE2_RES: [u8; 32] = $res;
|
|
const B2_MD_LEN: [usize; 4] = $md_len;
|
|
const B2_IN_LEN: [usize; 6] = $in_len;
|
|
|
|
let mut state = $state::new(32);
|
|
|
|
for &outlen in &B2_MD_LEN {
|
|
for &inlen in &B2_IN_LEN {
|
|
let data = selftest_seq(inlen);
|
|
let md = $func(outlen, &[], &data);
|
|
state.update(md.as_bytes());
|
|
|
|
let key = selftest_seq(outlen);
|
|
let md = $func(outlen, &key, &data);
|
|
state.update(md.as_bytes());
|
|
}
|
|
}
|
|
|
|
assert_eq!(&state.finalize(), &BLAKE2_RES[..]);
|
|
}
|
|
}
|
|
}
|
|
|