use crate::gost28147::Gost28147;
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..32 { out[i] = a[i] ^ b[i]; }
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(y: &[u8; 32]) -> [u8; 32] {
let mut out = [0u8; 32];
out[0] = y[30] ^ y[28] ^ y[26] ^ y[24] ^ y[0] ^ y[6];
out[1] = y[31] ^ y[29] ^ y[27] ^ y[25] ^ y[1] ^ y[7];
out[2..32].copy_from_slice(&y[0..30]);
out
}
#[inline]
fn chi_n(y: &[u8; 32], n: usize) -> [u8; 32] {
let mut x = *y;
for _ in 0..n { x = chi(&x); }
x
}
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 = a[i] as u16 + b[i] as u16 + carry;
out[i] = sum as u8;
carry = sum >> 8;
}
out
}
fn step(h: &[u8; 32], m: &[u8; 32], sbox: &Sbox) -> [u8; 32] {
let zeros = [0u8; 32];
let mut u = *h;
let mut v = *m;
let k1 = { let w = xor32(&u, &v); let mut k = perm_p(&w); k.reverse(); k };
u = xor32(&perm_a(&u), &zeros);
v = perm_a(&perm_a(&v));
let k2 = { let w = xor32(&u, &v); let mut k = perm_p(&w); k.reverse(); k };
u = xor32(&perm_a(&u), &C3);
v = perm_a(&perm_a(&v));
let k3 = { let w = xor32(&u, &v); let mut k = perm_p(&w); k.reverse(); k };
u = xor32(&perm_a(&u), &zeros);
v = perm_a(&perm_a(&v));
let k4 = { let w = xor32(&u, &v); let mut k = perm_p(&w); k.reverse(); k };
let enc = |key: &[u8; 32], chunk: &[u8; 8]| -> [u8; 8] {
let mut rev = *chunk;
rev.reverse();
let cipher = Gost28147::with_sbox(key, sbox);
let mut out = cipher.encrypt_block_raw(&rev);
out.reverse();
out
};
let mut s = [0u8; 32];
s[24..32].copy_from_slice(&enc(&k1, &h[24..32].try_into().unwrap()));
s[16..24].copy_from_slice(&enc(&k2, &h[16..24].try_into().unwrap()));
s[8..16].copy_from_slice( &enc(&k3, &h[8..16].try_into().unwrap()));
s[0..8].copy_from_slice( &enc(&k4, &h[0..8].try_into().unwrap()));
let t1 = chi_n(&s, 12);
let t2 = xor32(m, &t1);
let t3 = chi(&t2);
let t4 = xor32(h, &t3);
chi_n(&t4, 61)
}
pub struct Gost341194 {
sbox: Sbox,
h: [u8; 32],
checksum: [u8; 32],
bit_len: u64,
buf: [u8; 32],
buf_len: usize,
}
impl Gost341194 {
pub fn new_with_cryptopro() -> Self {
Self::new_with_sbox(&SBOX_CRYPTOPRO)
}
pub fn new_with_sbox(sbox: &Sbox) -> Self {
Self {
sbox: *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.sbox);
self.bit_len = self.bit_len.wrapping_add(256);
}
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.sbox);
}
let mut len_block = [0u8; 32];
len_block[24..32].copy_from_slice(&bit_len.to_be_bytes());
h = step(&h, &len_block, &self.sbox);
h = step(&h, &checksum, &self.sbox);
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 = Self::new_with_sbox(&self.sbox);
}
}
#[cfg(test)]
mod tests {
extern crate std;
use super::*;
use digest::Update;
use crate::sbox::{SBOX_CRYPTOPRO, SBOX_TEST};
fn hash(sbox: &Sbox, data: &[u8]) -> [u8; 32] {
let mut h = Gost341194::new_with_sbox(sbox);
Update::update(&mut h, data);
h.finalize_bytes()
}
fn from_hex(s: &str) -> [u8; 32] {
let bytes = (0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i+2], 16).unwrap())
.collect::<std::vec::Vec<u8>>();
bytes.try_into().unwrap()
}
#[test]
fn cryptopro_empty_string() {
assert_eq!(
hash(&SBOX_CRYPTOPRO, b""),
from_hex("981e5f3ca30c841487830f84fb433e13ac1101569b9c13584ac483234cd656c0")
);
}
#[test]
fn cryptopro_single_char_a() {
assert_eq!(
hash(&SBOX_CRYPTOPRO, b"a"),
from_hex("e74c52dd282183bf37af0079c9f78055715a103f17e3133ceff1aacf2f403011")
);
}
#[test]
fn cryptopro_abc() {
assert_eq!(
hash(&SBOX_CRYPTOPRO, b"abc"),
from_hex("b285056dbf18d7392d7677369524dd14747459ed8143997e163b2986f92fd42c")
);
}
#[test]
fn cryptopro_message_digest() {
assert_eq!(
hash(&SBOX_CRYPTOPRO, b"message digest"),
from_hex("bc6041dd2aa401ebfa6e9886734174febdb4729aa972d60f549ac39b29721ba0")
);
}
#[test]
fn cryptopro_quick_brown_fox() {
assert_eq!(
hash(&SBOX_CRYPTOPRO, b"The quick brown fox jumps over the lazy dog"),
from_hex("9004294a361a508c586fe53d1f1b02746765e71b765472786e4770d565830a76")
);
}
#[test]
fn testparam_empty_string() {
assert_eq!(
hash(&SBOX_TEST, b""),
from_hex("ce85b99cc46752fffee35cab9a7b0278abb4c2d2055cff685af4912c49490f8d")
);
}
#[test]
fn testparam_single_char_a() {
assert_eq!(
hash(&SBOX_TEST, b"a"),
from_hex("d42c539e367c66e9c88a801f6649349c21871b4344c6a573f849fdce62f314dd")
);
}
#[test]
fn testparam_abc() {
assert_eq!(
hash(&SBOX_TEST, b"abc"),
from_hex("f3134348c44fb1b2a277729e2285ebb5cb5e0f29c975bc753b70497c06a4d51d")
);
}
#[test]
fn cryptopro_long_input_consistent() {
let h1 = hash(&SBOX_CRYPTOPRO, &[0u8; 64]);
let mut hasher = Gost341194::new_with_sbox(&SBOX_CRYPTOPRO);
Update::update(&mut hasher, &[0u8; 32]);
Update::update(&mut hasher, &[0u8; 32]);
let h2 = hasher.finalize_bytes();
assert_eq!(h1, h2);
}
#[test]
fn incremental_equals_single_pass() {
let data = b"The quick brown fox jumps over the lazy dog";
let h_single = hash(&SBOX_CRYPTOPRO, data);
let mut hasher = Gost341194::new_with_sbox(&SBOX_CRYPTOPRO);
for chunk in data.chunks(7) {
Update::update(&mut hasher, chunk);
}
let h_incremental = hasher.finalize_bytes();
assert_eq!(h_single, h_incremental);
}
#[test]
fn reset_restores_state() {
use digest::Reset;
let mut h = Gost341194::new_with_sbox(&SBOX_CRYPTOPRO);
Update::update(&mut h, b"garbage data");
h.reset();
Update::update(&mut h, b"abc");
let after_reset = h.finalize_bytes();
assert_eq!(after_reset, hash(&SBOX_CRYPTOPRO, b"abc"));
}
#[test]
fn different_inputs_produce_different_hashes() {
assert_ne!(hash(&SBOX_CRYPTOPRO, b"hello"), hash(&SBOX_CRYPTOPRO, b"world"));
assert_ne!(hash(&SBOX_CRYPTOPRO, b"a"), hash(&SBOX_CRYPTOPRO, b"b"));
assert_ne!(hash(&SBOX_CRYPTOPRO, b""), hash(&SBOX_CRYPTOPRO, b" "));
}
#[test]
fn different_sboxes_produce_different_hashes() {
let data = b"test input";
assert_ne!(hash(&SBOX_CRYPTOPRO, data), hash(&SBOX_TEST, data));
}
#[test]
fn hash_is_deterministic() {
let data = b"determinism check";
assert_eq!(hash(&SBOX_CRYPTOPRO, data), hash(&SBOX_CRYPTOPRO, data));
}
#[test]
fn appending_byte_changes_hash() {
let h1 = hash(&SBOX_CRYPTOPRO, b"hello");
let h2 = hash(&SBOX_CRYPTOPRO, b"hello!");
assert_ne!(h1, h2);
}
}