///|
/// BLAKE2b (RFC 7693), unkeyed, variable digest length.
///
/// BLAKE2b is not an OpenPGP message-digest algorithm, so it is deliberately
/// absent from `HashId`; it exists here because Argon2 (RFC 9580 §3.7.1.4
/// pins Argon2id) is defined over it. Like every other primitive, the
/// implementation lives in `src/primitives` so the rest of the library never
/// depends on a hash backend directly.
///|
/// BLAKE2b compression-function block size in octets.
const BLAKE2B_BLOCK_SIZE : Int32 = 128
///|
/// BLAKE2b round count for the 64-bit variant.
const BLAKE2B_ROUNDS : Int32 = 12
///|
/// BLAKE2b parameter block XOR constant: digest length in the low byte,
/// key length (always 0 here) in the second byte, fanout=1, depth=1.
const BLAKE2B_PARAM_XOR : UInt64 = 0x01010000UL
///|
/// Rotate a 64-bit word right. `n` must be in 1..63 (all BLAKE2b/Argon2
/// rotations are).
fn rotr64(x : UInt64, n : Int32) -> UInt64 {
(x >> n) | (x << (64 - n))
}
///|
/// The BLAKE2b initialization vector (the SHA-512 IV).
fn blake2b_iv() -> FixedArray[UInt64] {
[
0x6A09E667F3BCC908UL, 0xBB67AE8584CAA73BUL, 0x3C6EF372FE94F82BUL, 0xA54FF53A5F1D36F1UL,
0x510E527FADE682D1UL, 0x9B05688C2B3E6C1FUL, 0x1F83D9ABFB41BD6BUL, 0x5BE0CD19137E2179UL,
]
}
///|
/// The BLAKE2b message schedule (RFC 7693 §2.7); rounds 10 and 11 repeat
/// rows 0 and 1, which the `% 10` in the round loop handles.
let blake2b_sigma : FixedArray[Int32] = [
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, //
]
///|
/// Read a 64-bit little-endian word at `off`.
fn read_u64_le(b : ArrayView[Byte], off : Int32) -> UInt64 {
b[off].to_uint64() |
(b[off + 1].to_uint64() << 8) |
(b[off + 2].to_uint64() << 16) |
(b[off + 3].to_uint64() << 24) |
(b[off + 4].to_uint64() << 32) |
(b[off + 5].to_uint64() << 40) |
(b[off + 6].to_uint64() << 48) |
(b[off + 7].to_uint64() << 56)
}
///|
/// The BLAKE2b mixing function G (RFC 7693 §3.1), operating directly on the
/// working vector.
fn blake2b_g(
v : FixedArray[UInt64],
a : Int32,
b : Int32,
c : Int32,
d : Int32,
x : UInt64,
y : UInt64,
) -> Unit {
let va = v[a] + v[b] + x
let vd = rotr64(v[d] ^ va, 32)
let vc = v[c] + vd
let vb = rotr64(v[b] ^ vc, 24)
let va2 = va + vb + y
let vd2 = rotr64(vd ^ va2, 16)
let vc2 = vc + vd2
let vb2 = rotr64(vb ^ vc2, 63)
v[a] = va2
v[b] = vb2
v[c] = vc2
v[d] = vd2
}
///|
/// The BLAKE2b compression function F (RFC 7693 §3.2). `block` is exactly
/// 128 octets; `t` is the number of message octets processed so far
/// (including this block); `last` toggles the finalization flag.
fn blake2b_compress(
h : FixedArray[UInt64],
block : ArrayView[Byte],
t : UInt64,
last : Bool,
) -> Unit {
let m : FixedArray[UInt64] = FixedArray::make(16, 0UL)
for i in 0..<16 {
m[i] = read_u64_le(block, i * 8)
}
let iv = blake2b_iv()
let v : FixedArray[UInt64] = FixedArray::make(16, 0UL)
for i in 0..<8 {
v[i] = h[i]
v[i + 8] = iv[i]
}
v[12] = v[12] ^ t
if last {
v[14] = v[14] ^ 0xFFFFFFFFFFFFFFFFUL
}
for r in 0.. Blake2bState {
let h = blake2b_iv()
h[0] = h[0] ^ (BLAKE2B_PARAM_XOR ^ out_len.to_uint64())
{
h,
buf: Array::make(BLAKE2B_BLOCK_SIZE, b'\x00'),
buflen: 0,
t: 0UL,
out_len,
}
}
///|
fn Blake2bState::update(self : Blake2bState, data : ArrayView[Byte]) -> Unit {
for i in 0.. Bytes {
self.t = self.t + self.buflen.to_uint64()
for i in self.buflen.. Bytes raise {
guard out_len >= 1 && out_len <= 64 else {
@error.fail("blake2b: digest length must be 1..64")
}
let st = Blake2bState::new(out_len)
st.update(data.to_owned().to_array())
st.finalize()
}