bash-hash: minor refactor (#746)

This commit is contained in:
Artyom Pavlov
2025-10-24 16:51:50 +03:00
committed by GitHub
parent 65f855157b
commit e0489181cb
8 changed files with 175 additions and 186 deletions

View File

@@ -14,6 +14,7 @@ Additionally all crates do not require the standard library (i.e. `no_std` capab
| Algorithm | Crate | Crates.io | Documentation | MSRV | [Security] |
|-----------|-------|:---------:|:-------------:|:----:|:----------:|
| [Ascon] hash | [`asconhash`] | [![crates.io](https://img.shields.io/crates/v/ascon-hash.svg)](https://crates.io/crates/ascon-hash) | [![Documentation](https://docs.rs/ascon-hash/badge.svg)](https://docs.rs/ascon-hash) | 1.85 | :green_heart: |
| [`bash-hash`][bash_hash_stb] | [`belthash`] | [![crates.io](https://img.shields.io/crates/v/bash-hash.svg)](https://crates.io/crates/bash-hash) | [![Documentation](https://docs.rs/bash-hash/badge.svg)](https://docs.rs/bash-hash) | 1.85 | :green_heart: |
| [BelT] hash | [`belthash`] | [![crates.io](https://img.shields.io/crates/v/belt-hash.svg)](https://crates.io/crates/belt-hash) | [![Documentation](https://docs.rs/belt-hash/badge.svg)](https://docs.rs/belt-hash) | 1.85 | :green_heart: |
| [BLAKE2] | [`blake2`] | [![crates.io](https://img.shields.io/crates/v/blake2.svg)](https://crates.io/crates/blake2) | [![Documentation](https://docs.rs/blake2/badge.svg)](https://docs.rs/blake2) | 1.85 | :green_heart: |
| [FSB] | [`fsb`] | [![crates.io](https://img.shields.io/crates/v/fsb.svg)](https://crates.io/crates/fsb) | [![Documentation](https://docs.rs/fsb/badge.svg)](https://docs.rs/fsb) | 1.85 | :green_heart: |
@@ -235,6 +236,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
[//]: # (crates)
[`asconhash`]: ./ascon-hash
[`bashhash`]: ./bash-hash
[`belthash`]: ./belt-hash
[`blake2`]: ./blake2
[`fsb`]: ./fsb
@@ -278,6 +280,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted
[//]: # (algorithms)
[Ascon]: https://ascon.iaik.tugraz.at
[bash_hash_stb]: https://apmi.bsu.by/assets/files/std/bash-spec241.pdf
[BelT]: https://ru.wikipedia.org/wiki/BelT
[BLAKE2]: https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2
[FSB]: https://en.wikipedia.org/wiki/Fast_syndrome-based_hash

View File

@@ -14,7 +14,7 @@ categories = ["cryptography", "no-std"]
[dependencies]
digest = "0.11.0-rc.3"
bash-f = { version = "0.1.0" }
bash-f = "0.1"
[dev-dependencies]
digest = { version = "0.11.0-rc.3", features = ["dev"] }

View File

@@ -7,7 +7,7 @@
![Rust Version][rustc-image]
[![Project Chat][chat-image]][chat-link]
Pure Rust implementation of the bash hash function specified in [STB 34.101.31-2020].
Pure Rust implementation of the bash hash function specified in [STB 34.101.77-2020].
## Examples
```rust

View File

@@ -1,7 +1,6 @@
use core::fmt;
use core::{fmt, marker::PhantomData};
use digest::{
HashMarker, Output,
array::Array,
block_api::{
AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, FixedOutputCore,
OutputSizeUser, Reset, UpdateCore,
@@ -10,48 +9,33 @@ use digest::{
typenum::U192,
};
use crate::variants::{Bash256, Bash384, Bash512, Variant};
use crate::OutputSize;
use bash_f::{STATE_WORDS, bash_f};
use digest::typenum::Unsigned;
/// Core Bash hasher state with generic security level.
/// Core `bash-hash` hasher generic over output size.
///
/// Implements bash-hash[] algorithm according to section 7 of STB 34.101.77-2020.
/// Parameters:
/// - BlockSize: block size r = (1536 - 4) / 8 bytes
/// - OutputSize: output size 2 / 8 bytes
#[derive(Clone)]
pub struct BashHashCore<V: Variant> {
/// Specified in Section 7 of STB 34.101.77-2020.
pub struct BashHashCore<OS: OutputSize> {
state: [u64; STATE_WORDS],
_variant: core::marker::PhantomData<V>,
_pd: PhantomData<OS>,
}
impl<V> BashHashCore<V>
where
V: Variant,
{
/// Calculate security level
///
/// According to section 5.3: = OutputSize * 8 / 2 = OutputSize * 4
impl<OS: OutputSize> Clone for BashHashCore<OS> {
#[inline]
const fn get_level() -> usize {
// 3. ← OutSize * 8 / 2
V::OutputSize::USIZE * 4
}
/// Calculate buffer size r in bytes
#[inline]
const fn get_r_bytes() -> usize {
V::BlockSize::USIZE
fn clone(&self) -> Self {
Self {
state: self.state,
_pd: PhantomData,
}
}
}
impl<OS: OutputSize> BashHashCore<OS> {
/// Compress one data block
fn compress_block(&mut self, block: &Block<Self>) {
let r_bytes = Self::get_r_bytes();
debug_assert_eq!(r_bytes % 8, 0);
// 4.1: S[...1536 - 4) ← Xi
for (dst, chunk) in self.state.iter_mut().zip(block[..r_bytes].chunks_exact(8)) {
// TODO: use `as_chunks` after MSRV is bumped to 1.88+
for (dst, chunk) in self.state.iter_mut().zip(block.chunks_exact(8)) {
// `chunk` is guaranteed to be 8 bytes long due to `r_bytes` being a multiple of 8
*dst = u64::from_le_bytes(chunk.try_into().unwrap());
}
@@ -61,33 +45,21 @@ where
}
}
impl<V> HashMarker for BashHashCore<V> where V: Variant {}
impl<OS: OutputSize> HashMarker for BashHashCore<OS> {}
impl<V> BlockSizeUser for BashHashCore<V>
where
V: Variant,
{
type BlockSize = V::BlockSize;
impl<OS: OutputSize> BlockSizeUser for BashHashCore<OS> {
type BlockSize = OS::BlockSize;
}
impl<V> BufferKindUser for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> BufferKindUser for BashHashCore<OS> {
type BufferKind = Eager;
}
impl<V> OutputSizeUser for BashHashCore<V>
where
V: Variant,
{
type OutputSize = V::OutputSize;
impl<OS: OutputSize> OutputSizeUser for BashHashCore<OS> {
type OutputSize = OS;
}
impl<V> UpdateCore for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> UpdateCore for BashHashCore<OS> {
#[inline]
fn update_blocks(&mut self, blocks: &[Block<Self>]) {
for block in blocks {
@@ -96,85 +68,62 @@ where
}
}
impl<V> FixedOutputCore for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> FixedOutputCore for BashHashCore<OS> {
fn finalize_fixed_core(&mut self, buffer: &mut Buffer<Self>, out: &mut Output<Self>) {
let pos = buffer.get_pos();
// 1. Split(X || 01, r) - split message with appended 01
// 2: Xn ← Xn || 0^(1536-4-|Xn|) - pad last block with zeros
let mut padding_block = Array::<u8, V::BlockSize>::default();
let block = buffer.pad_with_zeros();
padding_block.copy_from_slice(&block);
padding_block[pos] = 0x40;
let pos = buffer.get_pos();
let mut block = buffer.pad_with_zeros();
block[pos] = 0x40;
// 4. for i = 1, 2, ..., n, do:
self.compress_block(&padding_block);
self.compress_block(&block);
//5. Y ← S[...2)
self.state
.iter()
.flat_map(|w| w.to_le_bytes())
.take(V::OutputSize::USIZE)
.zip(out.iter_mut())
.for_each(|(src, dst)| *dst = src);
}
}
impl<V> Default for BashHashCore<V>
where
V: Variant,
{
#[inline]
fn default() -> Self {
let mut state = [0u64; STATE_WORDS];
// 3. S ← 0^1472 || ⟨ℓ/4⟩_64
let level = Self::get_level();
state[23] = (level / 4) as u64;
Self {
state,
_variant: core::marker::PhantomData,
// 5. Y ← S[...2)
// TODO: use `as_chunks` after MSRV is bumped to 1.88+
for (src, dst) in self.state.iter().zip(out.chunks_exact_mut(8)) {
dst.copy_from_slice(&src.to_le_bytes());
}
}
}
impl<V> Reset for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> Default for BashHashCore<OS> {
#[inline]
fn default() -> Self {
let mut state = [0u64; STATE_WORDS];
// 3. ← OutSize * 8 / 2
let level = OS::USIZE * 4;
// 3. S ← 0^1472 || ⟨ℓ/4⟩_64
state[23] = (level / 4) as u64;
Self {
state,
_pd: PhantomData,
}
}
}
impl<OS: OutputSize> Reset for BashHashCore<OS> {
#[inline]
fn reset(&mut self) {
*self = Default::default();
}
}
impl<V> AlgorithmName for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> AlgorithmName for BashHashCore<OS> {
fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result {
let level = Self::get_level();
write!(f, "Bash{}", level * 2)
write!(f, "BashHash{}", OS::USIZE)
}
}
impl<V> fmt::Debug for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> fmt::Debug for BashHashCore<OS> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("BashHashCore { ... }")
}
}
impl<V> Drop for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> Drop for BashHashCore<OS> {
fn drop(&mut self) {
#[cfg(feature = "zeroize")]
{
@@ -185,48 +134,31 @@ where
}
#[cfg(feature = "zeroize")]
impl<V> digest::zeroize::ZeroizeOnDrop for BashHashCore<V> where V: Variant {}
impl<OS: OutputSize> digest::zeroize::ZeroizeOnDrop for BashHashCore<OS> {}
impl<V> SerializableState for BashHashCore<V>
where
V: Variant,
{
impl<OS: OutputSize> SerializableState for BashHashCore<OS> {
type SerializedStateSize = U192;
fn serialize(&self) -> SerializedState<Self> {
let mut dst = SerializedState::<Self>::default();
for (word, chunk) in self.state.iter().zip(dst.chunks_exact_mut(8)) {
// `word` is guaranteed to be 8 bytes long due to `STATE_WORDS` being a multiple of 8
// and `chunk` being a slice of 8 bytes
chunk.copy_from_slice(&word.to_le_bytes());
let mut res = SerializedState::<Self>::default();
// TODO: use `as_chunks` after MSRV is bumped to 1.88+
for (src, dst) in self.state.iter().zip(res.chunks_exact_mut(8)) {
dst.copy_from_slice(&src.to_le_bytes());
}
dst
res
}
fn deserialize(
serialized_state: &SerializedState<Self>,
) -> Result<Self, DeserializeStateError> {
let mut state = [0u64; STATE_WORDS];
for (dst, chunk) in state.iter_mut().zip(serialized_state.chunks_exact(8)) {
// `chunk` is guaranteed to be 8 bytes long due to `STATE_WORDS` being a multiple of 8
// and `dst` being a slice of 8 bytes
*dst = u64::from_le_bytes(chunk.try_into().map_err(|_| DeserializeStateError)?);
// TODO: use `as_chunks` after MSRV is bumped to 1.88+
for (src, dst) in serialized_state.chunks_exact(8).zip(state.iter_mut()) {
*dst = u64::from_le_bytes(src.try_into().unwrap());
}
Ok(Self {
state,
_variant: core::marker::PhantomData,
_pd: PhantomData,
})
}
}
// Standard Bash hash variants according to section 5.3 and 7.1
// Bash256: = 128, output = 2 = 256 bits, block = (1536 - 4×128)/8 = 128 bytes
// Bash384: = 192, output = 2 = 384 bits, block = (1536 - 4×192)/8 = 96 bytes
// Bash512: = 256, output = 2 = 512 bits, block = (1536 - 4×256)/8 = 64 bytes
pub(crate) type Bash256Core = BashHashCore<Bash256>;
pub(crate) type Bash384Core = BashHashCore<Bash384>;
pub(crate) type Bash512Core = BashHashCore<Bash512>;

View File

@@ -8,29 +8,30 @@
#![warn(missing_docs, unreachable_pub)]
#![forbid(unsafe_code)]
use digest::typenum::{U32, U48, U64};
pub use digest::{self, Digest};
/// Block-level types
pub mod block_api;
#[cfg(feature = "oid")]
mod oids;
mod serialize;
mod variants;
digest::buffer_fixed!(
/// BASH256 hasher state.
pub struct BashHash256(block_api::Bash256Core);
oid: "1.2.112.0.2.0.34.101.77.11";
impl: FixedHashTraits;
);
pub use variants::OutputSize;
digest::buffer_fixed!(
/// BASH384 hasher state.
pub struct BashHash384(block_api::Bash384Core);
oid: "1.2.112.0.2.0.34.101.77.12";
impl: FixedHashTraits;
/// `bash-hash` hasher state generic over output size.
pub struct BashHash<OS: OutputSize>(block_api::BashHashCore<OS>);
// note: `SerializableState` is implemented in the `serialize` module
// to work around issues with complex trait bounds
impl: BaseFixedTraits AlgorithmName Default Clone HashMarker
Reset FixedOutputReset ZeroizeOnDrop;
);
digest::buffer_fixed!(
/// BASH512 hasher state.
pub struct BashHash512(block_api::Bash512Core);
oid: "1.2.112.0.2.0.34.101.77.13";
impl: FixedHashTraits;
);
/// `bash-hash-256` hasher state.
pub type BashHash256 = BashHash<U32>;
/// `bash-hash-384` hasher state.
pub type BashHash384 = BashHash<U48>;
/// `bash-hash-512` hasher state.
pub type BashHash512 = BashHash<U64>;

13
bash-hash/src/oids.rs Normal file
View File

@@ -0,0 +1,13 @@
use digest::const_oid::{AssociatedOid, ObjectIdentifier};
impl AssociatedOid for super::BashHash256 {
const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.11");
}
impl AssociatedOid for super::BashHash384 {
const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.12");
}
impl AssociatedOid for super::BashHash512 {
const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.13");
}

View File

@@ -0,0 +1,40 @@
use crate::{BashHash, OutputSize};
use core::ops::Add;
use digest::{
array::ArraySize,
block_buffer::BlockBuffer,
crypto_common::hazmat::{DeserializeStateError, SerializableState, SerializedState},
typenum::{Sum, U0, U192},
};
impl<OS: OutputSize> SerializableState for BashHash<OS>
where
U192: Add<OS::BlockSize>,
OS::BlockSize: Add<U0>,
Sum<U192, OS::BlockSize>: ArraySize,
Sum<OS::BlockSize, U0>: ArraySize,
{
type SerializedStateSize = Sum<U192, OS::BlockSize>;
#[inline]
fn serialize(&self) -> SerializedState<Self> {
let mut res = SerializedState::<Self>::default();
let (core_dst, buf_dst) = res.split_at_mut(192);
core_dst.copy_from_slice(&self.core.serialize());
buf_dst.copy_from_slice(&self.buffer.serialize());
res
}
#[inline]
fn deserialize(
serialized_state: &SerializedState<Self>,
) -> Result<Self, DeserializeStateError> {
let (serialized_core, serialized_buf) = serialized_state.split_at(192);
let core = SerializableState::deserialize(serialized_core.try_into().unwrap())?;
let buffer = BlockBuffer::deserialize(serialized_buf.try_into().unwrap())
.map_err(|_| DeserializeStateError)?;
Ok(Self { core, buffer })
}
}

View File

@@ -1,43 +1,43 @@
use digest::{
array::ArraySize,
crypto_common::BlockSizes,
typenum::{U32, U48, U64, U96, U128},
};
use digest::{array::ArraySize, crypto_common::BlockSizes, typenum};
/// Sealed trait to prevent external implementations.
pub trait Sealed: Clone {}
pub trait Sealed {}
/// Trait for Bash hash variants.
pub trait Variant: Sealed {
type BlockSize: ArraySize + BlockSizes;
type OutputSize: ArraySize;
/// Trait implemented for output sizes supported by `bash-hash`.
///
/// Supported output sizes form the following list: U4, U8, ..., U60, U64.
pub trait OutputSize: ArraySize + Sealed {
/// Block size in bytes computed as `192 - 2 * OutputSize`.
type BlockSize: BlockSizes;
}
#[derive(Clone)]
/// `Bash256` variant with 256-bit output and 128-byte block size.
pub struct Bash256;
#[derive(Clone)]
/// `Bash384` variant with 384-bit output and 96-byte block size.
pub struct Bash384;
#[derive(Clone)]
/// `Bash512` variant with 512-bit output and 64-byte block size.
pub struct Bash512;
macro_rules! impl_sizes {
($($variant:ident, $block_size:ident;)*) => {
$(
impl Sealed for typenum::$variant {}
impl Sealed for Bash256 {}
impl Sealed for Bash384 {}
impl Sealed for Bash512 {}
impl Variant for Bash256 {
type BlockSize = U128;
type OutputSize = U32;
impl OutputSize for typenum::$variant {
type BlockSize = typenum::$block_size;
}
)*
};
}
impl Variant for Bash384 {
type BlockSize = U96;
type OutputSize = U48;
}
impl Variant for Bash512 {
type BlockSize = U64;
type OutputSize = U64;
}
impl_sizes!(
U4, U184;
U8, U176;
U12, U168;
U16, U160;
U20, U152;
U24, U144;
U28, U136;
U32, U128;
U36, U120;
U40, U112;
U44, U104;
U48, U96;
U52, U88;
U56, U80;
U60, U72;
U64, U64;
);