use crate::gost28147::{Lut, build_lut, encrypt_with_lut};
use crate::sbox::{Sbox, SBOX_CRYPTOPRO};
use digest::{HashMarker, Output, OutputSizeUser, Reset, Update};
use digest::typenum::U32;
const C3: [u8; 32] = [
0xff, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0xff,
0xff, 0x00, 0x00, 0xff, 0x00, 0xff, 0xff, 0x00,
0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,
0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00,
];
#[inline]
fn xor32(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
let mut out = [0u8; 32];
for i in 0..4 {
let av = u64::from_ne_bytes([a[8*i],a[8*i+1],a[8*i+2],a[8*i+3],a[8*i+4],a[8*i+5],a[8*i+6],a[8*i+7]]);
let bv = u64::from_ne_bytes([b[8*i],b[8*i+1],b[8*i+2],b[8*i+3],b[8*i+4],b[8*i+5],b[8*i+6],b[8*i+7]]);
out[8*i..8*i+8].copy_from_slice(&(av ^ bv).to_ne_bytes());
}
out
}
#[inline]
fn perm_a(x: &[u8; 32]) -> [u8; 32] {
let mut out = [0u8; 32];
for i in 0..8 { out[i] = x[16 + i] ^ x[24 + i]; }
out[8..32].copy_from_slice(&x[0..24]);
out
}
#[inline]
fn perm_p(x: &[u8; 32]) -> [u8; 32] {
let mut out = [0u8; 32];
for i in 0..4usize {
for j in 0..8usize {
out[i + 4 * j] = x[8 * i + j];
}
}
out
}
#[inline]
fn chi_in_place(y: &mut [u8; 32]) {
let b0 = y[30] ^ y[28] ^ y[26] ^ y[24] ^ y[0] ^ y[6];
let b1 = y[31] ^ y[29] ^ y[27] ^ y[25] ^ y[1] ^ y[7];
y.copy_within(0..30, 2);
y[0] = b0;
y[1] = b1;
}
#[inline]
fn chi(y: &[u8; 32]) -> [u8; 32] {
let mut out = *y;
chi_in_place(&mut out);
out
}
#[inline]
fn chi_n(y: &[u8; 32], n: usize) -> [u8; 32] {
let mut x = *y;
for _ in 0..n { chi_in_place(&mut x); }
x
}
#[expect(clippy::cast_possible_truncation, reason = "intentional: keeps low 8 bits of sum")]
fn add256_be(a: &[u8; 32], b: &[u8; 32]) -> [u8; 32] {
let mut out = [0u8; 32];
let mut carry = 0u16;
for i in (0..32).rev() {
let sum = u16::from(a[i]) + u16::from(b[i]) + carry;
out[i] = sum as u8;
carry = sum >> 8;
}
out
}
#[inline]
fn derive_key(u: &[u8; 32], v: &[u8; 32]) -> [u8; 32] {
let w = xor32(u, v);
let mut k = perm_p(&w);
k.reverse();
k
}
#[inline]
fn advance_uv(u: &mut [u8; 32], v: &mut [u8; 32], c: &[u8; 32]) {
*u = xor32(&perm_a(u), c);
*v = perm_a(&perm_a(v));
}
#[inline]
fn encrypt_chunk(lut: &Lut, key: &[u8; 32], chunk: [u8; 8]) -> [u8; 8] {
let mut subkeys = [0u32; 8];
for (i, c) in key.chunks_exact(4).enumerate() {
subkeys[i] = u32::from_le_bytes([c[0], c[1], c[2], c[3]]);
}
let mut rev = chunk;
rev.reverse();
let mut out = encrypt_with_lut(&subkeys, lut, rev);
out.reverse();
out
}
#[expect(clippy::many_single_char_names, reason = "standard GOST variable names from RFC 5831")]
fn step(h: &[u8; 32], m: &[u8; 32], lut: &Lut) -> [u8; 32] {
let zeros = [0u8; 32];
let mut u = *h;
let mut v = *m;
let k1 = derive_key(&u, &v);
advance_uv(&mut u, &mut v, &zeros); let k2 = derive_key(&u, &v);
advance_uv(&mut u, &mut v, &C3);
let k3 = derive_key(&u, &v);
advance_uv(&mut u, &mut v, &zeros); let k4 = derive_key(&u, &v);
let mut s = [0u8; 32];
s[24..32].copy_from_slice(&encrypt_chunk(lut, &k1, h[24..32].try_into().unwrap()));
s[16..24].copy_from_slice(&encrypt_chunk(lut, &k2, h[16..24].try_into().unwrap()));
s[8..16].copy_from_slice( &encrypt_chunk(lut, &k3, h[8..16].try_into().unwrap()));
s[0..8].copy_from_slice( &encrypt_chunk(lut, &k4, h[0..8].try_into().unwrap()));
chi_n(&xor32(h, &chi(&xor32(m, &chi_n(&s, 12)))), 61)
}
pub struct Gost341194 {
lut: Lut,
h: [u8; 32],
checksum: [u8; 32],
bit_len: u64,
buf: [u8; 32],
buf_len: usize,
}
impl Gost341194 {
#[must_use]
pub fn new_with_cryptopro() -> Self {
Self::new_with_sbox(&SBOX_CRYPTOPRO)
}
#[must_use]
pub fn new_with_sbox(sbox: &Sbox) -> Self {
Self {
lut: build_lut(sbox),
h: [0u8; 32],
checksum: [0u8; 32],
bit_len: 0,
buf: [0u8; 32],
buf_len: 0,
}
}
fn process_block(&mut self, block: &[u8; 32]) {
let mut rev = *block;
rev.reverse();
self.checksum = add256_be(&self.checksum, &rev);
self.h = step(&self.h, &rev, &self.lut);
self.bit_len = self.bit_len.wrapping_add(256);
}
#[must_use]
pub fn finalize_bytes(self) -> [u8; 32] {
let mut h = self.h;
let mut checksum = self.checksum;
let mut bit_len = self.bit_len;
if self.buf_len > 0 {
bit_len = bit_len.wrapping_add((self.buf_len as u64) * 8);
let mut block = [0u8; 32];
block[..self.buf_len].copy_from_slice(&self.buf[..self.buf_len]);
block.reverse();
checksum = add256_be(&checksum, &block);
h = step(&h, &block, &self.lut);
}
let mut len_block = [0u8; 32];
len_block[24..32].copy_from_slice(&bit_len.to_be_bytes());
h = step(&h, &len_block, &self.lut);
h = step(&h, &checksum, &self.lut);
h.reverse();
h
}
}
impl HashMarker for Gost341194 {}
impl OutputSizeUser for Gost341194 {
type OutputSize = U32;
}
impl Update for Gost341194 {
fn update(&mut self, data: &[u8]) {
let mut offset = 0;
if self.buf_len > 0 {
let take = (32 - self.buf_len).min(data.len());
self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&data[..take]);
self.buf_len += take;
offset += take;
if self.buf_len == 32 {
let block: [u8; 32] = self.buf;
self.process_block(&block);
self.buf_len = 0;
}
}
while offset + 32 <= data.len() {
let block: [u8; 32] = data[offset..offset + 32].try_into().unwrap();
self.process_block(&block);
offset += 32;
}
let remaining = data.len() - offset;
if remaining > 0 {
self.buf[..remaining].copy_from_slice(&data[offset..]);
self.buf_len = remaining;
}
}
}
impl digest::FixedOutput for Gost341194 {
fn finalize_into(self, out: &mut Output<Self>) {
out.copy_from_slice(&self.finalize_bytes());
}
}
impl Reset for Gost341194 {
fn reset(&mut self) {
self.h = [0u8; 32];
self.checksum = [0u8; 32];
self.bit_len = 0;
self.buf = [0u8; 32];
self.buf_len = 0;
}
}
#[cfg(test)]
mod tests {
use super::*;
use digest::Update;
fn hash_hex(sbox: &Sbox, data: &[u8]) -> [u8; 32] {
let mut h = Gost341194::new_with_sbox(sbox);
Update::update(&mut h, data);
h.finalize_bytes()
}
mod finalize_bytes {
use super::*;
#[test]
fn empty_input_test_sbox() {
let got = hash_hex(&crate::sbox::SBOX_TEST, b"");
assert_eq!(got, [
0xce, 0x85, 0xb9, 0x9c, 0xc4, 0x67, 0x52, 0xff,
0xfe, 0xe3, 0x5c, 0xab, 0x9a, 0x7b, 0x02, 0x78,
0xab, 0xb4, 0xc2, 0xd2, 0x05, 0x5c, 0xff, 0x68,
0x5a, 0xf4, 0x91, 0x2c, 0x49, 0x49, 0x0f, 0x8d,
]);
}
#[test]
fn abc_test_sbox() {
let got = hash_hex(&crate::sbox::SBOX_TEST, b"abc");
assert_eq!(got, [
0xf3, 0x13, 0x43, 0x48, 0xc4, 0x4f, 0xb1, 0xb2,
0xa2, 0x77, 0x72, 0x9e, 0x22, 0x85, 0xeb, 0xb5,
0xcb, 0x5e, 0x0f, 0x29, 0xc9, 0x75, 0xbc, 0x75,
0x3b, 0x70, 0x49, 0x7c, 0x06, 0xa4, 0xd5, 0x1d,
]);
}
}
}