Skip to content

Instantly share code, notes, and snippets.

@oconnor663
Last active June 21, 2018 21:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oconnor663/848619ec034b657deaba708bc8255699 to your computer and use it in GitHub Desktop.
Save oconnor663/848619ec034b657deaba708bc8255699 to your computer and use it in GitHub Desktop.
toy blake2b implementation
extern crate byteorder;
use byteorder::{ByteOrder, LittleEndian};
type Digest = [u8; OUTBYTES];
pub const BLOCKBYTES: usize = 128;
pub const OUTBYTES: usize = 64;
pub const KEYBYTES: usize = 64;
pub const SALTBYTES: usize = 16;
pub const PERSONALBYTES: usize = 16;
const IV: [u64; 8] = [
0x6A09E667F3BCC908,
0xBB67AE8584CAA73B,
0x3C6EF372FE94F82B,
0xA54FF53A5F1D36F1,
0x510E527FADE682D1,
0x9B05688C2B3E6C1F,
0x1F83D9ABFB41BD6B,
0x5BE0CD19137E2179,
];
const SIGMA: [[u8; 16]; 12] = [
[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],
[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],
];
// G is the mixing function, called eight times per round in the compression
// function. V is the 16-word state vector of the compression function, usually
// described as a 4x4 matrix. A, B, C, and D are the mixing indices, set by the
// caller first to the four columns of V, and then to its four diagonals. X and
// Y are words of input, chosen by the caller according to the message
// schedule, SIGMA.
fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) {
v[a] = v[a].wrapping_add(v[b]).wrapping_add(x);
v[d] = (v[d] ^ v[a]).rotate_right(32);
v[c] = v[c].wrapping_add(v[d]);
v[b] = (v[b] ^ v[c]).rotate_right(24);
v[a] = v[a].wrapping_add(v[b]).wrapping_add(y);
v[d] = (v[d] ^ v[a]).rotate_right(16);
v[c] = v[c].wrapping_add(v[d]);
v[b] = (v[b] ^ v[c]).rotate_right(63);
}
// H is the 8-word state vector. `msg` is BLOCKBYTES of input, possibly padded
// with zero bytes in the final block. `count` is the number of bytes fed so
// far, including in this call, though not including padding in the final call.
// `finalize` is set to true only in the final call.
fn compress(h: &mut [u64; 8], msg: &[u8; BLOCKBYTES], count: u64, finalize: bool) {
// Initialize the compression state.
let mut v = [0; 16];
v[..8].copy_from_slice(h);
v[8..].copy_from_slice(&IV);
v[12] ^= count;
if finalize {
v[14] ^= !0;
}
// Parse the message bytes as ints in little endian order.
let mut m = [0; 16];
LittleEndian::read_u64_into(msg, &mut m);
for round in 0..12 {
// Select the message schedule based on the round.
let s = SIGMA[round];
// Mix the columns.
g(&mut v, 0, 4, 8, 12, m[s[0] as usize], m[s[1] as usize]);
g(&mut v, 1, 5, 9, 13, m[s[2] as usize], m[s[3] as usize]);
g(&mut v, 2, 6, 10, 14, m[s[4] as usize], m[s[5] as usize]);
g(&mut v, 3, 7, 11, 15, m[s[6] as usize], m[s[7] as usize]);
// Mix the rows.
g(&mut v, 0, 5, 10, 15, m[s[8] as usize], m[s[9] as usize]);
g(&mut v, 1, 6, 11, 12, m[s[10] as usize], m[s[11] as usize]);
g(&mut v, 2, 7, 8, 13, m[s[12] as usize], m[s[13] as usize]);
g(&mut v, 3, 4, 9, 14, m[s[14] as usize], m[s[15] as usize]);
}
for i in 0..8 {
h[i] ^= v[i] ^ v[i + 8];
}
}
pub struct State {
h: [u64; 8],
buf: [u8; BLOCKBYTES],
buflen: usize,
count: u64,
}
impl State {
pub fn new() -> Self {
let mut h = IV;
// Mask in the digest length.
h[0] ^= OUTBYTES as u64;
// Mask in the fanout and depth default parameters.
h[0] ^= 0x01010000;
Self {
h,
buf: [0; BLOCKBYTES],
buflen: 0,
count: 0,
}
}
pub fn update(&mut self, mut input: &[u8]) {
while !input.is_empty() {
if self.buflen == BLOCKBYTES {
compress(&mut self.h, &self.buf, self.count, false);
self.buflen = 0;
}
let take = (BLOCKBYTES - self.buflen).min(input.len());
self.buf[self.buflen..self.buflen + take].copy_from_slice(&input[..take]);
self.buflen += take;
self.count += take as u64;
input = &input[take..];
}
}
pub fn finalize(&mut self) -> Digest {
for i in self.buflen..BLOCKBYTES {
self.buf[i] = 0;
}
compress(&mut self.h, &self.buf, self.count, true);
let mut out = [0; OUTBYTES];
LittleEndian::write_u64_into(&self.h, &mut out);
out
}
}
pub fn blake2b(input: &[u8]) -> Digest {
let mut state = State::new();
state.update(input);
state.finalize()
}
#[cfg(test)]
mod test {
extern crate hex;
use super::*;
fn eq(h1: &Digest, s2: &str) {
let s1 = hex::encode(&h1[..]);
assert_eq!(s1, s2, "hash mismatch");
}
#[test]
fn test_vectors() {
let io = &[
(
&b""[..],
"786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
),
(
&b"abc"[..],
"ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923",
),
(
&[0; 1000],
"1ee4e51ecab5210a518f26150e882627ec839967f19d763e1508b12cfefed14858f6a1c9d1f969bc224dc9440f5a6955277e755b9c513f9ba4421c5e50c8d787",
),
];
for &(input, output) in io {
let hash = blake2b(input);
eq(&hash, output);
}
}
#[test]
fn test_a_thousand_one_by_one() {
let mut state = State::new();
for _ in 0..1000 {
state.update(&[0]);
}
let hash = state.finalize();
eq(
&hash,
"1ee4e51ecab5210a518f26150e882627ec839967f19d763e1508b12cfefed14858f6a1c9d1f969bc224dc9440f5a6955277e755b9c513f9ba4421c5e50c8d787",
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment