belt_hash/
lib.rs

1//! Pure Rust implementation of the [BelT] hash function specified in
2//! [STB 34.101.31-2020].
3//!
4//! # Usage
5//!
6//! ```rust
7//! use belt_hash::{BeltHash, Digest};
8//! use hex_literal::hex;
9//!
10//! // create a BelT hasher instance
11//! let mut hasher = BeltHash::new();
12//!
13//! // process input message
14//! hasher.update(b"hello world");
15//!
16//! // acquire hash digest in the form of GenericArray,
17//! // which in this case is equivalent to [u8; 32]
18//! let result = hasher.finalize();
19//! let expected = hex!(
20//!     "afb175816416fbadad4629ecbd78e1887789881f2d2e5b80c22a746b7ac7ba88"
21//! );
22//! assert_eq!(result[..], expected[..]);
23//! ```
24//!
25//! Also see [examples] in the RustCrypto/hashes readme.
26//!
27//! [BelT]: https://ru.wikipedia.org/wiki/BelT
28//! [STB 34.101.31-2020]: http://apmi.bsu.by/assets/files/std/belt-spec371.pdf
29//! [examples]: https://github.com/RustCrypto/hashes#usage
30#![no_std]
31#![doc(
32    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg",
33    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg"
34)]
35#![warn(missing_docs, rust_2018_idioms)]
36#![forbid(unsafe_code)]
37
38pub use digest::{self, Digest};
39
40use belt_block::belt_block_raw;
41use core::fmt;
42#[cfg(feature = "oid")]
43use digest::const_oid::{AssociatedOid, ObjectIdentifier};
44use digest::{
45    block_buffer::Eager,
46    core_api::{
47        AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, CoreWrapper, FixedOutputCore,
48        OutputSizeUser, Reset, UpdateCore,
49    },
50    typenum::{Unsigned, U32},
51    HashMarker, Output,
52};
53
54const U32_MASK: u128 = (1 << 32) - 1;
55
56/// Core BelT hasher state.
57#[derive(Clone)]
58pub struct BeltHashCore {
59    r: u128,
60    s: [u32; 4],
61    h: [u32; 8],
62}
63
64impl BeltHashCore {
65    fn compress_block(&mut self, block: &Block<Self>) {
66        let x1 = [
67            get_u32(block, 0),
68            get_u32(block, 1),
69            get_u32(block, 2),
70            get_u32(block, 3),
71        ];
72        let x2 = [
73            get_u32(block, 4),
74            get_u32(block, 5),
75            get_u32(block, 6),
76            get_u32(block, 7),
77        ];
78        let (t, h) = belt_compress(x1, x2, self.h);
79        self.h = h;
80        self.s.iter_mut().zip(t).for_each(|(s, t)| *s ^= t);
81    }
82}
83
84impl HashMarker for BeltHashCore {}
85
86impl BlockSizeUser for BeltHashCore {
87    type BlockSize = U32;
88}
89
90impl BufferKindUser for BeltHashCore {
91    type BufferKind = Eager;
92}
93
94impl OutputSizeUser for BeltHashCore {
95    type OutputSize = U32;
96}
97
98impl UpdateCore for BeltHashCore {
99    #[inline]
100    fn update_blocks(&mut self, blocks: &[Block<Self>]) {
101        self.r = self.r.wrapping_add(blocks.len() as u128);
102        for block in blocks {
103            self.compress_block(block);
104        }
105    }
106}
107
108impl FixedOutputCore for BeltHashCore {
109    #[inline]
110    fn finalize_fixed_core(&mut self, buffer: &mut Buffer<Self>, out: &mut Output<Self>) {
111        let pos = buffer.get_pos();
112        if pos != 0 {
113            let block = buffer.pad_with_zeros();
114            self.compress_block(block);
115        }
116        let bs = Self::BlockSize::USIZE as u128;
117        let r = encode_r(8 * ((bs * self.r) + pos as u128));
118        let (_, y) = belt_compress(r, self.s, self.h);
119        for (chunk, val) in out.chunks_exact_mut(4).zip(y) {
120            chunk.copy_from_slice(&val.to_le_bytes());
121        }
122    }
123}
124
125impl Default for BeltHashCore {
126    #[inline]
127    fn default() -> Self {
128        Self {
129            r: 0,
130            s: [0; 4],
131            #[rustfmt::skip]
132            h: [
133                0xC8BA94B1, 0x3BF5080A, 0x8E006D36, 0xE45D4A58,
134                0x9DFA0485, 0xACC7B61B, 0xC2722E25, 0x0DCEFD02,
135            ],
136        }
137    }
138}
139
140impl Reset for BeltHashCore {
141    #[inline]
142    fn reset(&mut self) {
143        *self = Default::default();
144    }
145}
146
147impl AlgorithmName for BeltHashCore {
148    fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        f.write_str("BeltHash")
150    }
151}
152
153impl fmt::Debug for BeltHashCore {
154    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
155        f.write_str("BeltHashCore { ... }")
156    }
157}
158
159#[cfg(feature = "oid")]
160#[cfg_attr(docsrs, doc(cfg(feature = "oid")))]
161impl AssociatedOid for BeltHashCore {
162    const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.31.81");
163}
164
165/// BelT hasher state.
166pub type BeltHash = CoreWrapper<BeltHashCore>;
167
168/// Compression function described in the section 6.3.2
169#[inline(always)]
170pub fn belt_compress(x1: [u32; 4], x2: [u32; 4], x34: [u32; 8]) -> ([u32; 4], [u32; 8]) {
171    let x3 = [x34[0], x34[1], x34[2], x34[3]];
172    let x4 = [x34[4], x34[5], x34[6], x34[7]];
173
174    // Step 2
175    let t1 = belt_block_raw(xor(x3, x4), &concat(x1, x2));
176    let s = xor(xor(t1, x3), x4);
177    // Step 3
178    let t2 = belt_block_raw(x1, &concat(s, x4));
179    let y1 = xor(t2, x1);
180    // Step 4
181    let t3 = belt_block_raw(x2, &concat(s.map(|v| !v), x3));
182    let y2 = xor(t3, x2);
183    // Step 5
184    (s, concat(y1, y2))
185}
186
187#[inline(always)]
188fn xor(a: [u32; 4], b: [u32; 4]) -> [u32; 4] {
189    // TODO: use array zip on stabilization and MSRV bump
190    [a[0] ^ b[0], a[1] ^ b[1], a[2] ^ b[2], a[3] ^ b[3]]
191}
192
193#[inline(always)]
194fn concat(a: [u32; 4], b: [u32; 4]) -> [u32; 8] {
195    [a[0], a[1], a[2], a[3], b[0], b[1], b[2], b[3]]
196}
197
198#[inline(always)]
199fn get_u32(block: &[u8], i: usize) -> u32 {
200    u32::from_le_bytes(block[4 * i..][..4].try_into().unwrap())
201}
202
203#[inline(always)]
204fn encode_r(r: u128) -> [u32; 4] {
205    [
206        (r & U32_MASK) as u32,
207        ((r >> 32) & U32_MASK) as u32,
208        ((r >> 64) & U32_MASK) as u32,
209        ((r >> 96) & U32_MASK) as u32,
210    ]
211}
212
213#[cfg(test)]
214mod tests {
215    use super::{belt_compress, get_u32};
216    use hex_literal::hex;
217
218    const ENUM4: [usize; 4] = [0, 1, 2, 3];
219    const ENUM8: [usize; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
220
221    /// Test vectors for the `belt-compress` functions from the
222    /// specification (Table A.8).
223    #[test]
224    fn compress() {
225        let x = &hex!(
226            "B194BAC8 0A08F53B 366D008E 584A5DE4"
227            "8504FA9D 1BB6C7AC 252E72C2 02FDCE0D"
228            "5BE3D612 17B96181 FE6786AD 716B890B"
229            "5CB0C0FF 33C356B8 35C405AE D8E07F99"
230        );
231        let expected_s = &hex!("46FE7425 C9B181EB 41DFEE3E 72163D5A");
232        let expected_y = &hex!(
233            "ED2F5481 D593F40D 87FCE37D 6BC1A2E1"
234            "B7D1A2CC 975C82D3 C0497488 C90D99D8"
235        );
236        let x1 = ENUM4.map(|i| get_u32(x, i));
237        let x2 = ENUM4.map(|i| get_u32(x, 4 + i));
238        let x34 = ENUM8.map(|i| get_u32(x, 8 + i));
239
240        let (s, y) = belt_compress(x1, x2, x34);
241
242        assert_eq!(s, ENUM4.map(|i| get_u32(expected_s, i)));
243        assert_eq!(y, ENUM8.map(|i| get_u32(expected_y, i)));
244    }
245}