From 24cb224a0609b0b0fce95b258b8d0fa40a71be75 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Thu, 23 Oct 2025 19:47:50 +0300 Subject: [PATCH 01/13] Implement bash-hash --- .github/workflows/bash-hash.yml | 72 +++++++++ Cargo.lock | 15 ++ Cargo.toml | 1 + bash-hash/CHANGELOG.md | 13 ++ bash-hash/Cargo.toml | 33 ++++ bash-hash/LICENSE-APACHE | 201 +++++++++++++++++++++++ bash-hash/LICENSE-MIT | 25 +++ bash-hash/README.md | 61 +++++++ bash-hash/benches/mod.rs | 30 ++++ bash-hash/src/block_api.rs | 263 +++++++++++++++++++++++++++++++ bash-hash/src/lib.rs | 35 ++++ bash-hash/tests/data/bash256.blb | Bin 0 -> 361 bytes bash-hash/tests/data/bash384.blb | Bin 0 -> 454 bytes bash-hash/tests/data/bash512.blb | Bin 0 -> 719 bytes bash-hash/tests/mod.rs | 47 ++++++ 15 files changed, 796 insertions(+) create mode 100644 .github/workflows/bash-hash.yml create mode 100644 bash-hash/CHANGELOG.md create mode 100644 bash-hash/Cargo.toml create mode 100644 bash-hash/LICENSE-APACHE create mode 100644 bash-hash/LICENSE-MIT create mode 100644 bash-hash/README.md create mode 100644 bash-hash/benches/mod.rs create mode 100644 bash-hash/src/block_api.rs create mode 100644 bash-hash/src/lib.rs create mode 100644 bash-hash/tests/data/bash256.blb create mode 100644 bash-hash/tests/data/bash384.blb create mode 100644 bash-hash/tests/data/bash512.blb create mode 100644 bash-hash/tests/mod.rs diff --git a/.github/workflows/bash-hash.yml b/.github/workflows/bash-hash.yml new file mode 100644 index 000000000..97149de5b --- /dev/null +++ b/.github/workflows/bash-hash.yml @@ -0,0 +1,72 @@ +name: belt-hash + +on: + pull_request: + paths: + - ".github/workflows/bash-hash.yml" + - "bash-hash/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: bash-hash + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + set-msrv: + uses: RustCrypto/actions/.github/workflows/set-msrv.yml@master + with: + msrv: 1.85.0 + + build: + needs: set-msrv + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - ${{needs.set-msrv.outputs.msrv}} + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v5 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack build --target ${{ matrix.target }} --each-feature --exclude-features default,std + + test: + needs: set-msrv + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - ${{needs.set-msrv.outputs.msrv}} + - stable + steps: + - uses: actions/checkout@v5 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} diff --git a/Cargo.lock b/Cargo.lock index 59b1ea3d5..72fd9eaf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,21 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" +[[package]] +name = "bash-f" +version = "0.1.0-rc.0" + +[[package]] +name = "bash-hash" +version = "0.2.0-rc.1" +dependencies = [ + "base16ct", + "bash-f", + "digest", + "hex-literal", + "subtle", +] + [[package]] name = "belt-block" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index 5ad5baf6e..1afe63c45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "3" members = [ "ascon-hash", + "bash-hash", "belt-hash", "blake2", "fsb", diff --git a/bash-hash/CHANGELOG.md b/bash-hash/CHANGELOG.md new file mode 100644 index 000000000..931f42425 --- /dev/null +++ b/bash-hash/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## 0.1.0 (UNRELEASED) +- Initial release ([#1]) + +# TODO: Insert link +[#1]: https://github.com/RustCrypto/hashes/pull/416 diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml new file mode 100644 index 000000000..a7a764327 --- /dev/null +++ b/bash-hash/Cargo.toml @@ -0,0 +1,33 @@ +[package] +name = "bash-hash" +version = "0.2.0-rc.1" +description = "bash hash function (STB 34.101.77-2020)" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +readme = "README.md" +edition = "2024" +rust-version = "1.85" +documentation = "https://docs.rs/belt-hash" +repository = "https://github.com/RustCrypto/hashes" +keywords = ["belt", "stb", "hash", "digest"] +categories = ["cryptography", "no-std"] + +[dependencies] +digest = "0.11.0-rc.3" +# TODO: Migrate to crate +bash-f = { path = "../../sponges/bash-f" } +subtle = { version = "2" } + +[dev-dependencies] +digest = { version = "0.11.0-rc.3", features = ["dev"] } +hex-literal = "1" +base16ct = { version = "0.3", features = ["alloc"] } + +[features] +default = ["alloc", "oid"] +alloc = ["digest/alloc"] +oid = ["digest/oid"] +zeroize = ["digest/zeroize"] + +[package.metadata.docs.rs] +all-features = true diff --git a/bash-hash/LICENSE-APACHE b/bash-hash/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/bash-hash/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/bash-hash/LICENSE-MIT b/bash-hash/LICENSE-MIT new file mode 100644 index 000000000..3d7cd6a74 --- /dev/null +++ b/bash-hash/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2022-2025 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/bash-hash/README.md b/bash-hash/README.md new file mode 100644 index 000000000..3fd56ed6e --- /dev/null +++ b/bash-hash/README.md @@ -0,0 +1,61 @@ +# RustCrypto: bash hash + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![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]. + +## Examples +```rust +use bash_hash::{BashHash256, Digest}; +use hex_literal::hex; + +let mut hasher = BashHash256::new(); +hasher.update(b"hello world"); +let hash = hasher.finalize(); + +assert_eq!(hash, hex!("2FC08EEC942378C0F8A6E5F1890D907B706BE393B0386E20A73D4D17A46BBD10")); + +// Hex-encode hash using https://docs.rs/base16ct +let hex_hash = base16ct::upper::encode_string(&hash); +assert_eq!(hex_hash, "2FC08EEC942378C0F8A6E5F1890D907B706BE393B0386E20A73D4D17A46BBD10"); +``` + +Also, see the [examples section] in the RustCrypto/hashes readme. + +## License + +The crate is licensed under either of: + +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/belt-hash.svg +[crate-link]: https://crates.io/crates/belt-hash +[docs-image]: https://docs.rs/belt-hash/badge.svg +[docs-link]: https://docs.rs/belt-hash +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260041-hashes +[build-image]: https://github.com/RustCrypto/hashes/actions/workflows/belt-hash.yml/badge.svg?branch=master +[build-link]: https://github.com/RustCrypto/hashes/actions/workflows/belt-hash.yml?query=branch:master + +[//]: # (general links) + +[STB 34.101.77-2020]: http://apmi.bsu.by/assets/files/std/bash-spec241.pdf +[examples section]: https://github.com/RustCrypto/hashes#Examples diff --git a/bash-hash/benches/mod.rs b/bash-hash/benches/mod.rs new file mode 100644 index 000000000..525edfb7e --- /dev/null +++ b/bash-hash/benches/mod.rs @@ -0,0 +1,30 @@ +#![feature(test)] +extern crate test; + +use bash_hash::{BashHash256, BashHash384, BashHash512}; +use digest::bench_update; +use test::Bencher; + +bench_update!( + BashHash256::default(); + bash_hash256_10 10; + bash_hash256_100 100; + bash_hash256_1000 1000; + bash_hash256_10000 10000; +); + +bench_update!( + BashHash384::default(); + bash_hash384_10 10; + bash_hash384_100 100; + bash_hash384_1000 1000; + bash_hash384_10000 10000; +); + +bench_update!( + BashHash512::default(); + bash_hash512_10 10; + bash_hash512_100 100; + bash_hash512_1000 1000; + bash_hash512_10000 10000; +); diff --git a/bash-hash/src/block_api.rs b/bash-hash/src/block_api.rs new file mode 100644 index 000000000..87c66efa9 --- /dev/null +++ b/bash-hash/src/block_api.rs @@ -0,0 +1,263 @@ +use core::fmt; +use digest::{ + HashMarker, Output, + array::{Array, ArraySize}, + block_api::{ + AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, FixedOutputCore, + OutputSizeUser, Reset, UpdateCore, + }, + crypto_common::{ + BlockSizes, + hazmat::{DeserializeStateError, SerializableState, SerializedState}, + }, + typenum::NonZero, + typenum::{U32, U48, U64, U96, U128, U192}, +}; + +use bash_f::{STATE_WORDS, bash_f}; + +/// Core Bash hasher state with generic security level. +/// +/// 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 +where + BlockSize: ArraySize, + OutputSize: ArraySize, +{ + state: [u64; STATE_WORDS], + _block_size: core::marker::PhantomData, + _output_size: core::marker::PhantomData, +} + +impl BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + /// Calculate security level ℓ + /// + /// According to section 5.3: ℓ = OutputSize * 8 / 2 = OutputSize * 4 + #[inline] + const fn get_level() -> usize { + // 3. ℓ ← OutSize * 8 / 2 + OS::USIZE * 4 + } + + /// Calculate buffer size r in bytes + #[inline] + const fn get_r_bytes() -> usize { + BS::USIZE + } + + /// Compress one data block + fn compress_block(&mut self, block: &Block) { + 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)) { + // `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()); + } + + // 4.2: S ← bash-f(S) + bash_f(&mut self.state); + } +} + +impl HashMarker for BashHashCore +where + BS: ArraySize, + OS: ArraySize, +{ +} + +impl BlockSizeUser for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + type BlockSize = BS; +} + +impl BufferKindUser for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + type BufferKind = Eager; +} + +impl OutputSizeUser for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + type OutputSize = OS; +} + +impl UpdateCore for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + #[inline] + fn update_blocks(&mut self, blocks: &[Block]) { + for block in blocks { + self.compress_block(block); + } + } +} + +impl FixedOutputCore for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { + 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::::default(); + let block = buffer.pad_with_zeros(); + padding_block.copy_from_slice(&block); + padding_block[pos] = 0x40; + + // 4. for i = 1, 2, ..., n, do: + self.compress_block(&padding_block); + + //5. Y ← S[...2ℓ) + self.state + .iter() + .flat_map(|w| w.to_le_bytes()) + .take(OS::USIZE) + .zip(out.iter_mut()) + .for_each(|(src, dst)| *dst = src); + } +} + +impl Default for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + #[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, + _block_size: core::marker::PhantomData, + _output_size: core::marker::PhantomData, + } + } +} + +impl Reset for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + #[inline] + fn reset(&mut self) { + *self = Default::default(); + } +} + +impl AlgorithmName for BashHashCore +where + BS: ArraySize + NonZero + BlockSizes, + OS: ArraySize, +{ + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + let level = Self::get_level(); + write!(f, "Bash{}", level * 2) + } +} + +impl fmt::Debug for BashHashCore +where + BS: ArraySize, + OS: ArraySize, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("BashHashCore { ... }") + } +} + +impl Drop for BashHashCore +where + BS: ArraySize, + OS: ArraySize, +{ + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use digest::zeroize::Zeroize; + self.state.zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for BashHashCore +where + BS: ArraySize, + OS: ArraySize, +{ +} + +impl SerializableState for BashHashCore +where + BS: ArraySize, + OS: ArraySize, +{ + type SerializedStateSize = U192; + + fn serialize(&self) -> SerializedState { + let mut dst = SerializedState::::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()); + } + + dst + } + + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + 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)?); + } + + Ok(Self { + state, + _block_size: core::marker::PhantomData, + _output_size: core::marker::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; +pub(crate) type Bash384Core = BashHashCore; +pub(crate) type Bash512Core = BashHashCore; diff --git a/bash-hash/src/lib.rs b/bash-hash/src/lib.rs new file mode 100644 index 000000000..3dc1013dd --- /dev/null +++ b/bash-hash/src/lib.rs @@ -0,0 +1,35 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, unreachable_pub)] +#![forbid(unsafe_code)] + +pub use digest::{self, Digest}; + +/// Block-level types +pub mod block_api; + +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; +); + +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; +); + +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; +); diff --git a/bash-hash/tests/data/bash256.blb b/bash-hash/tests/data/bash256.blb new file mode 100644 index 0000000000000000000000000000000000000000..d3cf4d1303003270816b7d3fea123f9e0e421eee GIT binary patch literal 361 zcmZQ$XsFvbW!DKVj<42cxeR>~Ua?PFS$@rx-gbPAs$S6{roZQSqaR-r65pBF_%FR} zZDDpNcg%(Z|BVla?JzyUy6(n<`k4>4??^pr3V%~zf9Au~cfB7Et}e5f|E%KF_hUa2 zuI`@W|Jf$BI=RLm>#pDO%6Xe?4ee%Z%~QX;=X|7`guGAGb_d&fzh4Y5cNzbCt2(nS ztoD3NuFzG1@Rb=K{n^g&Pl!xxWEfx~Ua?PFS$@rx-gbPAs$S6{roZQSqaR-r65pBF_%FR} zZDDpNcg%(Z|BVla?JzyUy6(n<`k4>4??^pr3V%~zf9Au~cfB7Et}e5f|E%KF_hUa2 zuI`?bkYeoh!{D-g^2;^%7q&cBRT8lOACr+F$fa{R^X(oMi<$u0{z>0G*ZDq-YGyqg ze9gDPfyOTKPq>ix`(XaH106j3k1ct8EovjFTHJT zVRk2X%!UL1jSq+IFg?P$?#6@q1_rIP6EzZZgdT+|Phk_XzIXfM%+OaK8oITvvp(`v z;&6S$@80%1J@vxYBb_Z9H>QTYmweSYX=m#bW!pKN0lmqV&l?yhbi&LA2KLqaTN|D~ zU99+Wr^C1Obqez|gU)>opWgM{`8G%PYLC|K9LhHKp^o>v)*nt3Y5MzQ#?x-&%4JEq zYt&Ls9+X&^)Ie!4%zUVQN9s{i_?rUzGashD>-~6eb(zKdXBDTuAN!GTb@v?q&o-&m z$u$O9cm0-E&f8>bXg6bPp8DlI=Og7LFLSk)zg-9?J{xsByv3Yrp>jB^UvSQ mv9&a8t?~bM=B(%5nXURSUoH2HJ9Bs1*1`vG&%6Jao&f-gHDy-- literal 0 HcmV?d00001 diff --git a/bash-hash/tests/mod.rs b/bash-hash/tests/mod.rs new file mode 100644 index 000000000..c82c24439 --- /dev/null +++ b/bash-hash/tests/mod.rs @@ -0,0 +1,47 @@ +use bash_hash::{BashHash256, BashHash384, BashHash512}; +use digest::Digest; +use digest::dev::fixed_reset_test; +use hex_literal::hex; + +// Test vectors from STB 34.101.77-2020 (Appendix A, Table A.3) +digest::new_test!(bash256, BashHash256, fixed_reset_test); +digest::new_test!(bash384, BashHash384, fixed_reset_test); +digest::new_test!(bash512, BashHash512, fixed_reset_test); + +macro_rules! test_bash_rand { + ($name:ident, $hasher:ty, $expected:expr) => { + #[test] + fn $name() { + let mut h = <$hasher>::new(); + digest::dev::feed_rand_16mib(&mut h); + assert_eq!(h.finalize(), $expected); + } + }; +} + +test_bash_rand!( + bash256_rand, + BashHash256, + hex!("03f23e09f2ab9ce3f228c21ab1861d2495fcaf81aae2d6bbefd525b95d0925d5") +); + +test_bash_rand!( + bash384_rand, + BashHash384, + hex!( + "3a2932e47780b88aab04c33e0df3c9f53035e4e47daa89e5f8dddf43f4b21c2073d36887684245b87042661c0a3bb8ce" + ) +); + +test_bash_rand!( + bash512_rand, + BashHash512, + hex!( + "f85aacf9fb6fe864d86604fb8d93485b533f29d874b49cd5521ad8afb1c11e8b710f8469b95c6af39147a132787801d194473d1bd7ce24fc23e97dc182bf8a9f" + ) +); + +// TODO: Implement the serialization tests (ask about the serialization data) +// digest::hash_serialization_test!(bash256_serialization, BashHash256); +// digest::hash_serialization_test!(bash384_serialization, BashHash384); +// digest::hash_serialization_test!(bash512_serialization, BashHash512); From ce3a2a9cc40a675c1f4a82f1c27fc39bd8249872 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 24 Oct 2025 12:49:20 +0300 Subject: [PATCH 02/13] `bash-hash`: review fixes + add tests --- .github/workflows/bash-hash.yml | 2 +- Cargo.lock | 2 +- bash-hash/CHANGELOG.md | 3 +-- bash-hash/Cargo.toml | 2 +- bash-hash/LICENSE-MIT | 2 +- bash-hash/tests/data/bash256_serialization.bin | Bin 0 -> 320 bytes bash-hash/tests/data/bash384_serialization.bin | Bin 0 -> 288 bytes bash-hash/tests/data/bash512_serialization.bin | Bin 0 -> 256 bytes bash-hash/tests/mod.rs | 13 +++++++------ 9 files changed, 12 insertions(+), 12 deletions(-) create mode 100644 bash-hash/tests/data/bash256_serialization.bin create mode 100644 bash-hash/tests/data/bash384_serialization.bin create mode 100644 bash-hash/tests/data/bash512_serialization.bin diff --git a/.github/workflows/bash-hash.yml b/.github/workflows/bash-hash.yml index 97149de5b..d7eca54a0 100644 --- a/.github/workflows/bash-hash.yml +++ b/.github/workflows/bash-hash.yml @@ -1,4 +1,4 @@ -name: belt-hash +name: bash-hash on: pull_request: diff --git a/Cargo.lock b/Cargo.lock index 72fd9eaf7..1e8882168 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,7 +35,7 @@ version = "0.1.0-rc.0" [[package]] name = "bash-hash" -version = "0.2.0-rc.1" +version = "0.1.0" dependencies = [ "base16ct", "bash-f", diff --git a/bash-hash/CHANGELOG.md b/bash-hash/CHANGELOG.md index 931f42425..c1ca6f923 100644 --- a/bash-hash/CHANGELOG.md +++ b/bash-hash/CHANGELOG.md @@ -9,5 +9,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 (UNRELEASED) - Initial release ([#1]) -# TODO: Insert link -[#1]: https://github.com/RustCrypto/hashes/pull/416 +[#1]: https://github.com/RustCrypto/hashes/pull/745 diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml index a7a764327..454456ead 100644 --- a/bash-hash/Cargo.toml +++ b/bash-hash/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bash-hash" -version = "0.2.0-rc.1" +version = "0.1.0" description = "bash hash function (STB 34.101.77-2020)" authors = ["RustCrypto Developers"] license = "MIT OR Apache-2.0" diff --git a/bash-hash/LICENSE-MIT b/bash-hash/LICENSE-MIT index 3d7cd6a74..82a58781e 100644 --- a/bash-hash/LICENSE-MIT +++ b/bash-hash/LICENSE-MIT @@ -1,4 +1,4 @@ -Copyright (c) 2022-2025 The RustCrypto Project Developers +Copyright (c) 2025 The RustCrypto Project Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated diff --git a/bash-hash/tests/data/bash256_serialization.bin b/bash-hash/tests/data/bash256_serialization.bin new file mode 100644 index 0000000000000000000000000000000000000000..21e6deadc01f90ae56af82d8f8d23d8430dfe583 GIT binary patch literal 320 zcmbR08S;?#w9Z`7f0YJFd$)Z0_>|>x+`ju7-WM2yLa&Oj9J)3=K0*6;%I-`q!SLAR zmvb)4TTeCU>Q&GUd@Y;o*6%r=IdJ##Gfc~cc13SWW}o_c*Uy)4?qtnTlr4B||8>#x z@=C`Uy#9seWpOuI*E?U8u#Ga9_%CARTAo<~*H4H9C%V*K>-}P}TearO)I0ZHP8XiK zadPu&{Xg@6@J~1V_UHcmsQ)*eQ(Rj1gdevH*mqBT!^%z3Z*?^MHm7Uf{U6`(#fMRt HVL$-@SDImq literal 0 HcmV?d00001 diff --git a/bash-hash/tests/data/bash384_serialization.bin b/bash-hash/tests/data/bash384_serialization.bin new file mode 100644 index 0000000000000000000000000000000000000000..3910629f862ff5a4041c8aa049d4d16eed495389 GIT binary patch literal 288 zcmeZml$+G-^J;NdSlTu21xIex{*t8Yw`Ie*+q zj=6$SJn`$+a|va<^Dkc1FFy6|Shrwl=;pJxa=Dk(eJ^9!zCK%;PoQ-BJXRT|+l;~t GGz9>sNnmgQ literal 0 HcmV?d00001 diff --git a/bash-hash/tests/data/bash512_serialization.bin b/bash-hash/tests/data/bash512_serialization.bin new file mode 100644 index 0000000000000000000000000000000000000000..3211131c75591e34b480d720e9c32d7682b2dca3 GIT binary patch literal 256 zcmdmShF#Nr=YD~w53(-#>n&QlE`Gy>{QiI~n=kkr$mr*CvW%U>^0_@FFv0uZ7TwDC zFU>3BCH0q^DBN82{1K~6(rm_tcmKp?LwL8Jt(+;mF{MTJ=Y+TZ;@oA^?w&Mu`kW&6 z<1LfvG38*dUH5#aeb!7gxWe+$&y^#2l8E%=gCgbzXPVb5*Ix4vNZn{>%~d%wrP8xM zNG)xqIokyd)w|cHA7$=+owImSRO`) Date: Fri, 24 Oct 2025 12:53:30 +0300 Subject: [PATCH 03/13] `bash-hash`: use `bash-f` from crates --- Cargo.lock | 4 +++- bash-hash/Cargo.toml | 3 +-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e8882168..82873ed8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -31,7 +31,9 @@ checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" [[package]] name = "bash-f" -version = "0.1.0-rc.0" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfed5cd32720841afa8cd58c89a317e9efb0c8083e1b349dae4098f022620353" [[package]] name = "bash-hash" diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml index 454456ead..e0ffd7143 100644 --- a/bash-hash/Cargo.toml +++ b/bash-hash/Cargo.toml @@ -14,8 +14,7 @@ categories = ["cryptography", "no-std"] [dependencies] digest = "0.11.0-rc.3" -# TODO: Migrate to crate -bash-f = { path = "../../sponges/bash-f" } +bash-f = { version = "0.1.0-rc.0" } subtle = { version = "2" } [dev-dependencies] From c34d490686a9c5015204553ae9f90126e78c9ab1 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 24 Oct 2025 13:30:57 +0300 Subject: [PATCH 04/13] `bash-hash`: add Variant trait for possible hash variants --- bash-hash/Cargo.toml | 2 +- bash-hash/src/block_api.rs | 119 ++++++++++++++----------------------- bash-hash/src/lib.rs | 1 + bash-hash/src/variants.rs | 43 ++++++++++++++ 4 files changed, 89 insertions(+), 76 deletions(-) create mode 100644 bash-hash/src/variants.rs diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml index e0ffd7143..4dd049d63 100644 --- a/bash-hash/Cargo.toml +++ b/bash-hash/Cargo.toml @@ -15,7 +15,7 @@ categories = ["cryptography", "no-std"] [dependencies] digest = "0.11.0-rc.3" bash-f = { version = "0.1.0-rc.0" } -subtle = { version = "2" } +subtle = { version = "2", default-features = false } [dev-dependencies] digest = { version = "0.11.0-rc.3", features = ["dev"] } diff --git a/bash-hash/src/block_api.rs b/bash-hash/src/block_api.rs index 87c66efa9..68e96d2a2 100644 --- a/bash-hash/src/block_api.rs +++ b/bash-hash/src/block_api.rs @@ -1,20 +1,18 @@ use core::fmt; use digest::{ HashMarker, Output, - array::{Array, ArraySize}, + array::Array, block_api::{ AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, FixedOutputCore, OutputSizeUser, Reset, UpdateCore, }, - crypto_common::{ - BlockSizes, - hazmat::{DeserializeStateError, SerializableState, SerializedState}, - }, - typenum::NonZero, - typenum::{U32, U48, U64, U96, U128, U192}, + crypto_common::hazmat::{DeserializeStateError, SerializableState, SerializedState}, + typenum::U192, }; +use crate::variants::{Bash256, Bash384, Bash512, Variant}; use bash_f::{STATE_WORDS, bash_f}; +use digest::typenum::Unsigned; /// Core Bash hasher state with generic security level. /// @@ -23,20 +21,14 @@ use bash_f::{STATE_WORDS, bash_f}; /// - BlockSize: block size r = (1536 - 4ℓ) / 8 bytes /// - OutputSize: output size 2ℓ / 8 bytes #[derive(Clone)] -pub struct BashHashCore -where - BlockSize: ArraySize, - OutputSize: ArraySize, -{ +pub struct BashHashCore { state: [u64; STATE_WORDS], - _block_size: core::marker::PhantomData, - _output_size: core::marker::PhantomData, + _variant: core::marker::PhantomData, } -impl BashHashCore +impl BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { /// Calculate security level ℓ /// @@ -44,13 +36,13 @@ where #[inline] const fn get_level() -> usize { // 3. ℓ ← OutSize * 8 / 2 - OS::USIZE * 4 + V::OutputSize::USIZE * 4 } /// Calculate buffer size r in bytes #[inline] const fn get_r_bytes() -> usize { - BS::USIZE + V::BlockSize::USIZE } /// Compress one data block @@ -69,41 +61,32 @@ where } } -impl HashMarker for BashHashCore -where - BS: ArraySize, - OS: ArraySize, -{ -} +impl HashMarker for BashHashCore where V: Variant {} -impl BlockSizeUser for BashHashCore +impl BlockSizeUser for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { - type BlockSize = BS; + type BlockSize = V::BlockSize; } -impl BufferKindUser for BashHashCore +impl BufferKindUser for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { type BufferKind = Eager; } -impl OutputSizeUser for BashHashCore +impl OutputSizeUser for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { - type OutputSize = OS; + type OutputSize = V::OutputSize; } -impl UpdateCore for BashHashCore +impl UpdateCore for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { #[inline] fn update_blocks(&mut self, blocks: &[Block]) { @@ -113,17 +96,16 @@ where } } -impl FixedOutputCore for BashHashCore +impl FixedOutputCore for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { 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::::default(); + let mut padding_block = Array::::default(); let block = buffer.pad_with_zeros(); padding_block.copy_from_slice(&block); padding_block[pos] = 0x40; @@ -135,16 +117,15 @@ where self.state .iter() .flat_map(|w| w.to_le_bytes()) - .take(OS::USIZE) + .take(V::OutputSize::USIZE) .zip(out.iter_mut()) .for_each(|(src, dst)| *dst = src); } } -impl Default for BashHashCore +impl Default for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { #[inline] fn default() -> Self { @@ -156,16 +137,14 @@ where Self { state, - _block_size: core::marker::PhantomData, - _output_size: core::marker::PhantomData, + _variant: core::marker::PhantomData, } } } -impl Reset for BashHashCore +impl Reset for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { #[inline] fn reset(&mut self) { @@ -173,10 +152,9 @@ where } } -impl AlgorithmName for BashHashCore +impl AlgorithmName for BashHashCore where - BS: ArraySize + NonZero + BlockSizes, - OS: ArraySize, + V: Variant, { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { let level = Self::get_level(); @@ -184,20 +162,18 @@ where } } -impl fmt::Debug for BashHashCore +impl fmt::Debug for BashHashCore where - BS: ArraySize, - OS: ArraySize, + V: Variant, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("BashHashCore { ... }") } } -impl Drop for BashHashCore +impl Drop for BashHashCore where - BS: ArraySize, - OS: ArraySize, + V: Variant, { fn drop(&mut self) { #[cfg(feature = "zeroize")] @@ -209,17 +185,11 @@ where } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for BashHashCore -where - BS: ArraySize, - OS: ArraySize, -{ -} +impl digest::zeroize::ZeroizeOnDrop for BashHashCore where V: Variant {} -impl SerializableState for BashHashCore +impl SerializableState for BashHashCore where - BS: ArraySize, - OS: ArraySize, + V: Variant, { type SerializedStateSize = U192; @@ -248,8 +218,7 @@ where Ok(Self { state, - _block_size: core::marker::PhantomData, - _output_size: core::marker::PhantomData, + _variant: core::marker::PhantomData, }) } } @@ -258,6 +227,6 @@ where // 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; -pub(crate) type Bash384Core = BashHashCore; -pub(crate) type Bash512Core = BashHashCore; +pub(crate) type Bash256Core = BashHashCore; +pub(crate) type Bash384Core = BashHashCore; +pub(crate) type Bash512Core = BashHashCore; diff --git a/bash-hash/src/lib.rs b/bash-hash/src/lib.rs index 3dc1013dd..88aa243fd 100644 --- a/bash-hash/src/lib.rs +++ b/bash-hash/src/lib.rs @@ -12,6 +12,7 @@ pub use digest::{self, Digest}; /// Block-level types pub mod block_api; +mod variants; digest::buffer_fixed!( /// BASH256 hasher state. diff --git a/bash-hash/src/variants.rs b/bash-hash/src/variants.rs new file mode 100644 index 000000000..53a68912f --- /dev/null +++ b/bash-hash/src/variants.rs @@ -0,0 +1,43 @@ +use digest::{ + array::ArraySize, + crypto_common::BlockSizes, + typenum::{U32, U48, U64, U96, U128}, +}; + +/// Sealed trait to prevent external implementations. +pub trait Sealed: Clone {} + +/// Trait for Bash hash variants. +pub trait Variant: Sealed { + type BlockSize: ArraySize + BlockSizes; + type OutputSize: ArraySize; +} + +#[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; + +impl Sealed for Bash256 {} +impl Sealed for Bash384 {} +impl Sealed for Bash512 {} + +impl Variant for Bash256 { + type BlockSize = U128; + type OutputSize = U32; +} + +impl Variant for Bash384 { + type BlockSize = U96; + type OutputSize = U48; +} + +impl Variant for Bash512 { + type BlockSize = U64; + type OutputSize = U64; +} From 1126d5a9e83c67e43544acf7365a39c4b55e0027 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 24 Oct 2025 14:15:19 +0300 Subject: [PATCH 05/13] `bash-hash`: fix/remove subtle --- Cargo.lock | 1 - bash-hash/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82873ed8a..4ff03c7b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -43,7 +43,6 @@ dependencies = [ "bash-f", "digest", "hex-literal", - "subtle", ] [[package]] diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml index 4dd049d63..99a0028df 100644 --- a/bash-hash/Cargo.toml +++ b/bash-hash/Cargo.toml @@ -15,7 +15,6 @@ categories = ["cryptography", "no-std"] [dependencies] digest = "0.11.0-rc.3" bash-f = { version = "0.1.0-rc.0" } -subtle = { version = "2", default-features = false } [dev-dependencies] digest = { version = "0.11.0-rc.3", features = ["dev"] } From b50e6f05d96b0dcad92e748fea6ce73b9e60fa70 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 24 Oct 2025 14:16:24 +0300 Subject: [PATCH 06/13] `bash-hash`: fix/split --- bash-hash/tests/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bash-hash/tests/mod.rs b/bash-hash/tests/mod.rs index 710dd0ddc..50126fee3 100644 --- a/bash-hash/tests/mod.rs +++ b/bash-hash/tests/mod.rs @@ -29,8 +29,8 @@ test_bash_rand!( bash384_rand, BashHash384, hex!( - "3a2932e47780b88aab04c33e0df3c9f53035e4e47daa89e5f8dddf43f4b21c20 - 73d36887684245b87042661c0a3bb8ce" + "3a2932e47780b88aab04c33e0df3c9f53035e4e47daa89e5f8dddf43f4b21c20" + "73d36887684245b87042661c0a3bb8ce" ) ); @@ -38,8 +38,8 @@ test_bash_rand!( bash512_rand, BashHash512, hex!( - "f85aacf9fb6fe864d86604fb8d93485b533f29d874b49cd5521ad8afb1c11e8b - 710f8469b95c6af39147a132787801d194473d1bd7ce24fc23e97dc182bf8a9f" + "f85aacf9fb6fe864d86604fb8d93485b533f29d874b49cd5521ad8afb1c11e8b" + "710f8469b95c6af39147a132787801d194473d1bd7ce24fc23e97dc182bf8a9f" ) ); From f0a3d55be1e5ae94675550b8389782c6b6520462 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Fri, 24 Oct 2025 14:26:23 +0300 Subject: [PATCH 07/13] `bash-hash`: fix `bash-f` version, update PR link in changelog --- bash-hash/CHANGELOG.md | 4 ++-- bash-hash/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bash-hash/CHANGELOG.md b/bash-hash/CHANGELOG.md index c1ca6f923..a3a56b13e 100644 --- a/bash-hash/CHANGELOG.md +++ b/bash-hash/CHANGELOG.md @@ -7,6 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 (UNRELEASED) -- Initial release ([#1]) +- Initial release ([#745]) -[#1]: https://github.com/RustCrypto/hashes/pull/745 +[#745]: https://github.com/RustCrypto/hashes/pull/745 diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml index 99a0028df..edfe02b6f 100644 --- a/bash-hash/Cargo.toml +++ b/bash-hash/Cargo.toml @@ -14,7 +14,7 @@ categories = ["cryptography", "no-std"] [dependencies] digest = "0.11.0-rc.3" -bash-f = { version = "0.1.0-rc.0" } +bash-f = { version = "0.1.0" } [dev-dependencies] digest = { version = "0.11.0-rc.3", features = ["dev"] } From 0cf1a43181af0fab3d785ec56df93875a87ea91d Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Fri, 24 Oct 2025 16:51:50 +0300 Subject: [PATCH 08/13] bash-hash: minor refactor (#746) --- README.md | 3 + bash-hash/Cargo.toml | 2 +- bash-hash/README.md | 2 +- bash-hash/src/block_api.rs | 176 ++++++++++++------------------------- bash-hash/src/lib.rs | 33 +++---- bash-hash/src/oids.rs | 13 +++ bash-hash/src/serialize.rs | 40 +++++++++ bash-hash/src/variants.rs | 70 +++++++-------- 8 files changed, 164 insertions(+), 175 deletions(-) create mode 100644 bash-hash/src/oids.rs create mode 100644 bash-hash/src/serialize.rs diff --git a/README.md b/README.md index abf30598b..3a7735d70 100644 --- a/README.md +++ b/README.md @@ -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 | [`ascon‑hash`] | [![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] | [`belt‑hash`] | [![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 | [`belt‑hash`] | [![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) [`ascon‑hash`]: ./ascon-hash +[`bash‑hash`]: ./bash-hash [`belt‑hash`]: ./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 diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml index edfe02b6f..984d54a71 100644 --- a/bash-hash/Cargo.toml +++ b/bash-hash/Cargo.toml @@ -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"] } diff --git a/bash-hash/README.md b/bash-hash/README.md index 3fd56ed6e..c2b429964 100644 --- a/bash-hash/README.md +++ b/bash-hash/README.md @@ -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 diff --git a/bash-hash/src/block_api.rs b/bash-hash/src/block_api.rs index 68e96d2a2..6f7c6453a 100644 --- a/bash-hash/src/block_api.rs +++ b/bash-hash/src/block_api.rs @@ -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 { +/// Specified in Section 7 of STB 34.101.77-2020. +pub struct BashHashCore { state: [u64; STATE_WORDS], - _variant: core::marker::PhantomData, + _pd: PhantomData, } -impl BashHashCore -where - V: Variant, -{ - /// Calculate security level ℓ - /// - /// According to section 5.3: ℓ = OutputSize * 8 / 2 = OutputSize * 4 +impl Clone for BashHashCore { #[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 BashHashCore { /// Compress one data block fn compress_block(&mut self, block: &Block) { - 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 HashMarker for BashHashCore where V: Variant {} +impl HashMarker for BashHashCore {} -impl BlockSizeUser for BashHashCore -where - V: Variant, -{ - type BlockSize = V::BlockSize; +impl BlockSizeUser for BashHashCore { + type BlockSize = OS::BlockSize; } -impl BufferKindUser for BashHashCore -where - V: Variant, -{ +impl BufferKindUser for BashHashCore { type BufferKind = Eager; } -impl OutputSizeUser for BashHashCore -where - V: Variant, -{ - type OutputSize = V::OutputSize; +impl OutputSizeUser for BashHashCore { + type OutputSize = OS; } -impl UpdateCore for BashHashCore -where - V: Variant, -{ +impl UpdateCore for BashHashCore { #[inline] fn update_blocks(&mut self, blocks: &[Block]) { for block in blocks { @@ -96,85 +68,62 @@ where } } -impl FixedOutputCore for BashHashCore -where - V: Variant, -{ +impl FixedOutputCore for BashHashCore { fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - 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::::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); - - //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); + self.compress_block(&block); + + // 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 Default for BashHashCore -where - V: Variant, -{ +impl Default for BashHashCore { #[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 - let level = Self::get_level(); state[23] = (level / 4) as u64; Self { state, - _variant: core::marker::PhantomData, + _pd: PhantomData, } } } -impl Reset for BashHashCore -where - V: Variant, -{ +impl Reset for BashHashCore { #[inline] fn reset(&mut self) { *self = Default::default(); } } -impl AlgorithmName for BashHashCore -where - V: Variant, -{ +impl AlgorithmName for BashHashCore { 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 fmt::Debug for BashHashCore -where - V: Variant, -{ +impl fmt::Debug for BashHashCore { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("BashHashCore { ... }") } } -impl Drop for BashHashCore -where - V: Variant, -{ +impl Drop for BashHashCore { fn drop(&mut self) { #[cfg(feature = "zeroize")] { @@ -185,48 +134,31 @@ where } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for BashHashCore where V: Variant {} +impl digest::zeroize::ZeroizeOnDrop for BashHashCore {} -impl SerializableState for BashHashCore -where - V: Variant, -{ +impl SerializableState for BashHashCore { type SerializedStateSize = U192; fn serialize(&self) -> SerializedState { - let mut dst = SerializedState::::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::::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, ) -> Result { 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; -pub(crate) type Bash384Core = BashHashCore; -pub(crate) type Bash512Core = BashHashCore; diff --git a/bash-hash/src/lib.rs b/bash-hash/src/lib.rs index 88aa243fd..ade823ca4 100644 --- a/bash-hash/src/lib.rs +++ b/bash-hash/src/lib.rs @@ -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(block_api::BashHashCore); + // 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; +/// `bash-hash-384` hasher state. +pub type BashHash384 = BashHash; +/// `bash-hash-512` hasher state. +pub type BashHash512 = BashHash; diff --git a/bash-hash/src/oids.rs b/bash-hash/src/oids.rs new file mode 100644 index 000000000..80b1a7470 --- /dev/null +++ b/bash-hash/src/oids.rs @@ -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"); +} diff --git a/bash-hash/src/serialize.rs b/bash-hash/src/serialize.rs new file mode 100644 index 000000000..0b8292233 --- /dev/null +++ b/bash-hash/src/serialize.rs @@ -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 SerializableState for BashHash +where + U192: Add, + OS::BlockSize: Add, + Sum: ArraySize, + Sum: ArraySize, +{ + type SerializedStateSize = Sum; + + #[inline] + fn serialize(&self) -> SerializedState { + let mut res = SerializedState::::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, + ) -> Result { + 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 }) + } +} diff --git a/bash-hash/src/variants.rs b/bash-hash/src/variants.rs index 53a68912f..4b4bd1dcf 100644 --- a/bash-hash/src/variants.rs +++ b/bash-hash/src/variants.rs @@ -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 Variant for Bash384 { - type BlockSize = U96; - type OutputSize = U48; + impl OutputSize for typenum::$variant { + type BlockSize = typenum::$block_size; + } + )* + }; } -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; +); From d1f4ff4184a9bff3f8bf54e6f7149593b97dc583 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Fri, 24 Oct 2025 17:20:34 +0300 Subject: [PATCH 09/13] Remove implementations of the `VariableOutput` trait (#744) --- Cargo.lock | 55 +++++++++++++++++++------------------------- Cargo.toml | 4 +--- blake2/CHANGELOG.md | 2 ++ blake2/README.md | 16 +------------ blake2/src/lib.rs | 15 +----------- blake2/src/macros.rs | 2 +- blake2/tests/mod.rs | 6 ++--- groestl/CHANGELOG.md | 2 ++ groestl/src/lib.rs | 8 ------- kupyna/src/lib.rs | 8 ------- 10 files changed, 34 insertions(+), 84 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ff03c7b0..b00981498 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "blobby" -version = "0.4.0-pre.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4db6eec520c1f3d3b060c5b67527c3d1d4122d9f0ff47edf0149a7ffa34cebb" +checksum = "89af0b093cc13baa4e51e64e65ec2422f7e73aea0e612e5ad3872986671622f1" [[package]] name = "block-buffer" @@ -88,9 +88,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "const-oid" @@ -110,8 +110,7 @@ dependencies = [ [[package]] name = "crypto-common" version = "0.2.0-rc.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" +source = "git+https://github.com/RustCrypto/traits#b0d40cd1ae3f4adf07769436e661434f6333816c" dependencies = [ "hybrid-array", ] @@ -119,8 +118,7 @@ dependencies = [ [[package]] name = "digest" version = "0.11.0-rc.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d" +source = "git+https://github.com/RustCrypto/traits#b0d40cd1ae3f4adf07769436e661434f6333816c" dependencies = [ "blobby", "block-buffer", @@ -172,9 +170,9 @@ checksum = "bcaaec4551594c969335c98c903c1397853d4198408ea609190f420500f6be71" [[package]] name = "hybrid-array" -version = "0.4.0" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe39a812f039072707ce38020acbab2f769087952eddd9e2b890f37654b2349" +checksum = "f471e0a81b2f90ffc0cb2f951ae04da57de8baa46fa99112b062a5173a5088d0" dependencies = [ "typenum", ] @@ -218,9 +216,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "md-5" @@ -261,18 +259,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -380,9 +378,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" dependencies = [ "proc-macro2", "quote", @@ -409,15 +407,15 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "whirlpool" @@ -430,18 +428,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", @@ -453,8 +451,3 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[patch.unused]] -name = "blobby" -version = "0.4.0-pre.0" -source = "git+https://github.com/RustCrypto/utils#8fd37074861e2d8e400a53d68e10ce713944fa65" diff --git a/Cargo.toml b/Cargo.toml index 1afe63c45..bf29f817f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,4 @@ sha1 = { path = "sha1" } sha3 = { path = "sha3" } whirlpool = { path = "whirlpool" } -# https://github.com/RustCrypto/utils/pull/1187 -# https://github.com/RustCrypto/utils/pull/1207 -blobby = { git = "https://github.com/RustCrypto/utils" } +digest = { git = "https://github.com/RustCrypto/traits" } diff --git a/blake2/CHANGELOG.md b/blake2/CHANGELOG.md index d3a61634e..bd478a3f5 100644 --- a/blake2/CHANGELOG.md +++ b/blake2/CHANGELOG.md @@ -17,9 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `std` crate feature ([#678]) +- `Blake2bVar` and `Blake2sVar` types ([#744]) [#652]: https://github.com/RustCrypto/hashes/pull/652 [#678]: https://github.com/RustCrypto/hashes/pull/678 +[#744]: https://github.com/RustCrypto/hashes/pull/744 ## 0.10.6 (2022-12-16) ### Added diff --git a/blake2/README.md b/blake2/README.md index b6b2e14ca..8e4c3e9e4 100644 --- a/blake2/README.md +++ b/blake2/README.md @@ -45,22 +45,8 @@ Also, see the [examples section] in the RustCrypto/hashes readme. ### Variable output size -This implementation supports run and compile time variable sizes. +This implementation supports output sizes variable at compile time: -Output size set at run time: -```rust -use blake2::Blake2bVar; -use blake2::digest::{Update, VariableOutput}; -use hex_literal::hex; - -let mut hasher = Blake2bVar::new(10).unwrap(); -hasher.update(b"my_input"); -let mut buf = [0u8; 10]; -hasher.finalize_variable(&mut buf).unwrap(); -assert_eq!(buf, hex!("2cc55c84e416924e6400")); -``` - -Output size set at compile time: ```rust use blake2::{Blake2b, Digest, digest::consts::U10}; use hex_literal::hex; diff --git a/blake2/src/lib.rs b/blake2/src/lib.rs index 0a352f192..54b621608 100644 --- a/blake2/src/lib.rs +++ b/blake2/src/lib.rs @@ -15,11 +15,10 @@ pub use digest::{self, Digest}; use core::{fmt, marker::PhantomData, ops::Div}; use digest::{ CustomizedInit, FixedOutput, HashMarker, InvalidOutputSize, MacMarker, Output, Update, - VarOutputCustomized, array::{Array, ArraySize}, block_api::{ AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, OutputSizeUser, TruncSide, - UpdateCore, VariableOutputCore, + UpdateCore, VariableOutputCore, VariableOutputCoreCustomized, }, block_buffer::{Lazy, LazyBuffer}, consts::{U4, U16, U32, U64, U128}, @@ -80,12 +79,6 @@ where } } -digest::buffer_rt_variable!( - /// BLAKE2b which allows to choose output size at runtime. - pub struct Blake2bVar(Blake2bVarCore); - exclude: SerializableState; -); - /// BLAKE2b-128 hasher state. pub type Blake2b128 = Blake2b; /// BLAKE2b-256 hasher state. @@ -134,12 +127,6 @@ where } } -digest::buffer_rt_variable!( - /// BLAKE2s which allows to choose output size at runtime. - pub struct Blake2sVar(Blake2sVarCore); - exclude: SerializableState; -); - /// BLAKE2s-128 hasher state. pub type Blake2s128 = Blake2s; /// BLAKE2s-256 hasher state. diff --git a/blake2/src/macros.rs b/blake2/src/macros.rs index a29fcdd2c..a5d0af50c 100644 --- a/blake2/src/macros.rs +++ b/blake2/src/macros.rs @@ -257,7 +257,7 @@ macro_rules! blake2_impl { } } - impl VarOutputCustomized for $name { + impl VariableOutputCoreCustomized for $name { #[inline] fn new_customized(customization: &[u8], output_size: usize) -> Self { Self::new_with_params(&[], customization, 0, output_size) diff --git a/blake2/tests/mod.rs b/blake2/tests/mod.rs index 2a804b396..e3988bdcb 100644 --- a/blake2/tests/mod.rs +++ b/blake2/tests/mod.rs @@ -1,9 +1,7 @@ #[cfg(feature = "reset")] -use digest::dev::{fixed_reset_test as fixed_test, variable_reset_test as variable_test}; +use digest::dev::fixed_reset_test as fixed_test; #[cfg(not(feature = "reset"))] -use digest::dev::{fixed_test, variable_test}; +use digest::dev::fixed_test; use digest::new_test; new_test!(blake2b_kat, blake2::Blake2b512, fixed_test); -new_test!(blake2b_variable_kat, blake2::Blake2bVar, variable_test); -new_test!(blake2s_variable_kat, blake2::Blake2sVar, variable_test); diff --git a/groestl/CHANGELOG.md b/groestl/CHANGELOG.md index 402f3f318..fd9da0a95 100644 --- a/groestl/CHANGELOG.md +++ b/groestl/CHANGELOG.md @@ -18,10 +18,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - `std` crate feature ([#678]) +- `GroestlShortVar` and `GroestlLongVar` types ([#744]) [#652]: https://github.com/RustCrypto/hashes/pull/652 [#678]: https://github.com/RustCrypto/hashes/pull/678 [#716]: https://github.com/RustCrypto/hashes/pull/716 +[#744]: https://github.com/RustCrypto/hashes/pull/744 ## 0.10.1 (2022-02-17) ### Fixed diff --git a/groestl/src/lib.rs b/groestl/src/lib.rs index 8d6aad8e2..95f5a6160 100644 --- a/groestl/src/lib.rs +++ b/groestl/src/lib.rs @@ -24,19 +24,11 @@ digest::buffer_ct_variable!( pub struct GroestlShort(block_api::GroestlShortVarCore); max_size: U32; ); -digest::buffer_rt_variable!( - /// Long Groestl variant which allows to select output size at runtime. - pub struct GroestlShortVar(block_api::GroestlShortVarCore); -); digest::buffer_ct_variable!( /// Long Groestl variant generic over output size. pub struct GroestlLong(block_api::GroestlLongVarCore); max_size: U64; ); -digest::buffer_rt_variable!( - /// Long Groestl variant which allows to select output size at runtime. - pub struct GroestlLongVar(block_api::GroestlLongVarCore); -); /// Groestl-224 hasher. pub type Groestl224 = GroestlShort; diff --git a/kupyna/src/lib.rs b/kupyna/src/lib.rs index d605de300..f370ce553 100644 --- a/kupyna/src/lib.rs +++ b/kupyna/src/lib.rs @@ -24,19 +24,11 @@ digest::buffer_ct_variable!( pub struct KupynaShort(block_api::KupynaShortVarCore); max_size: U32; ); -digest::buffer_rt_variable!( - /// Short Kupyna variant which allows to select output size at runtime. - pub struct KupynaShortVar(block_api::KupynaShortVarCore); -); digest::buffer_ct_variable!( /// Long Kupyna variant generic over output size. pub struct KupynaLong(block_api::KupynaLongVarCore); max_size: U64; ); -digest::buffer_rt_variable!( - /// Long Kupyna variant which allows to select output size at runtime. - pub struct KupynaLongVar(block_api::KupynaLongVarCore); -); /// Kupyna-224 hasher. pub type Kupyna224 = KupynaShort; From d10162988104b0be20fa66c8a4ac4ecb59a33451 Mon Sep 17 00:00:00 2001 From: Artyom Pavlov Date: Fri, 24 Oct 2025 17:23:23 +0300 Subject: [PATCH 10/13] Fix bash-hash entry in the supported algorithms table (#748) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3a7735d70..27c4abbec 100644 --- a/README.md +++ b/README.md @@ -14,7 +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 | [`ascon‑hash`] | [![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] | [`belt‑hash`] | [![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: | +| [Bash] hash | [`bash‑hash`] | [![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 | [`belt‑hash`] | [![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: | @@ -280,7 +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 +[Bash]: 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 From 7f2e420ddff4ecf188e2814381eafde45fbb2be5 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Tue, 4 Nov 2025 16:16:16 +0300 Subject: [PATCH 11/13] `bash-prg-hash`: initial implementation --- Cargo.lock | 10 + Cargo.toml | 1 + bash-prg-hash/CHANGELOG.md | 12 ++ bash-prg-hash/Cargo.toml | 31 +++ bash-prg-hash/LICENSE-APACHE | 201 ++++++++++++++++++ bash-prg-hash/LICENSE-MIT | 25 +++ bash-prg-hash/README.md | 63 ++++++ bash-prg-hash/benches/mod.rs | 30 +++ bash-prg-hash/src/block_api.rs | 260 +++++++++++++++++++++++ bash-prg-hash/src/lib.rs | 138 ++++++++++++ bash-prg-hash/src/oids.rs | 30 +++ bash-prg-hash/src/variants.rs | 72 +++++++ bash-prg-hash/tests/data/bashprg1282.blb | Bin 0 -> 546 bytes bash-prg-hash/tests/data/bashprg1921.blb | Bin 0 -> 592 bytes bash-prg-hash/tests/data/bashprg2562.blb | Bin 0 -> 643 bytes bash-prg-hash/tests/mod.rs | 48 +++++ 16 files changed, 921 insertions(+) create mode 100644 bash-prg-hash/CHANGELOG.md create mode 100644 bash-prg-hash/Cargo.toml create mode 100644 bash-prg-hash/LICENSE-APACHE create mode 100644 bash-prg-hash/LICENSE-MIT create mode 100644 bash-prg-hash/README.md create mode 100644 bash-prg-hash/benches/mod.rs create mode 100644 bash-prg-hash/src/block_api.rs create mode 100644 bash-prg-hash/src/lib.rs create mode 100644 bash-prg-hash/src/oids.rs create mode 100644 bash-prg-hash/src/variants.rs create mode 100644 bash-prg-hash/tests/data/bashprg1282.blb create mode 100644 bash-prg-hash/tests/data/bashprg1921.blb create mode 100644 bash-prg-hash/tests/data/bashprg2562.blb create mode 100644 bash-prg-hash/tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index b00981498..5098fc039 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,16 @@ dependencies = [ "hex-literal", ] +[[package]] +name = "bash-prg-hash" +version = "0.1.0" +dependencies = [ + "base16ct", + "bash-f", + "digest", + "hex-literal", +] + [[package]] name = "belt-block" version = "0.1.2" diff --git a/Cargo.toml b/Cargo.toml index bf29f817f..77aa508c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "3" members = [ "ascon-hash", "bash-hash", + "bash-prg-hash", "belt-hash", "blake2", "fsb", diff --git a/bash-prg-hash/CHANGELOG.md b/bash-prg-hash/CHANGELOG.md new file mode 100644 index 000000000..a3a56b13e --- /dev/null +++ b/bash-prg-hash/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## 0.1.0 (UNRELEASED) +- Initial release ([#745]) + +[#745]: https://github.com/RustCrypto/hashes/pull/745 diff --git a/bash-prg-hash/Cargo.toml b/bash-prg-hash/Cargo.toml new file mode 100644 index 000000000..55ff76ceb --- /dev/null +++ b/bash-prg-hash/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "bash-prg-hash" +version = "0.1.0" +description = "bash hash prg function (STB 34.101.77-2020)" +authors = ["RustCrypto Developers"] +license = "MIT OR Apache-2.0" +readme = "README.md" +edition = "2024" +rust-version = "1.85" +documentation = "https://docs.rs/belt-hash-prg" +repository = "https://github.com/RustCrypto/hashes" +keywords = ["belt", "stb", "hash", "digest"] +categories = ["cryptography", "no-std"] + +[dependencies] +digest = "0.11.0-rc.3" +bash-f = "0.1" + +[dev-dependencies] +digest = { version = "0.11.0-rc.3", features = ["dev"] } +hex-literal = "1" +base16ct = { version = "0.3", features = ["alloc"] } + +[features] +default = ["alloc", "oid"] +alloc = ["digest/alloc"] +oid = ["digest/oid"] +zeroize = ["digest/zeroize"] + +[package.metadata.docs.rs] +all-features = true diff --git a/bash-prg-hash/LICENSE-APACHE b/bash-prg-hash/LICENSE-APACHE new file mode 100644 index 000000000..78173fa2e --- /dev/null +++ b/bash-prg-hash/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/bash-prg-hash/LICENSE-MIT b/bash-prg-hash/LICENSE-MIT new file mode 100644 index 000000000..82a58781e --- /dev/null +++ b/bash-prg-hash/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2025 The RustCrypto Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/bash-prg-hash/README.md b/bash-prg-hash/README.md new file mode 100644 index 000000000..02d8ee030 --- /dev/null +++ b/bash-prg-hash/README.md @@ -0,0 +1,63 @@ +# RustCrypto: bash prg hash + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +[![Build Status][build-image]][build-link] +![Apache2/MIT licensed][license-image] +![Rust Version][rustc-image] +[![Project Chat][chat-image]][chat-link] + +Pure Rust implementation of the bash prg hash function specified in [STB 34.101.77-2020]. + +## Examples +```rust +use hex_literal::hex; +use bash_prg_hash::{BashPrgHash2561, Digest}; +use digest::{Update, ExtendableOutput}; +let mut hasher = BashPrgHash2561::default(); +hasher.update(b"hello world!"); + +let mut hash = [0u8; 32]; +hasher.finalize_xof_into(&mut hash); + +assert_eq!(hash, hex!("0C6B82907AE77386DDF0BA2D7CFDDD99F79A9B0094E545AEF8968A99440F5185")); + +// Hex-encode hash using https://docs.rs/base16ct +let hex_hash = base16ct::upper::encode_string(&hash); +assert_eq!(hex_hash, "0C6B82907AE77386DDF0BA2D7CFDDD99F79A9B0094E545AEF8968A99440F5185"); +``` + +Also, see the [examples section] in the RustCrypto/hashes readme. + +## License + +The crate is licensed under either of: + +* [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) +* [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/belt-hash.svg +[crate-link]: https://crates.io/crates/belt-hash +[docs-image]: https://docs.rs/belt-hash/badge.svg +[docs-link]: https://docs.rs/belt-hash +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.85+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260041-hashes +[build-image]: https://github.com/RustCrypto/hashes/actions/workflows/belt-hash.yml/badge.svg?branch=master +[build-link]: https://github.com/RustCrypto/hashes/actions/workflows/belt-hash.yml?query=branch:master + +[//]: # (general links) + +[STB 34.101.77-2020]: http://apmi.bsu.by/assets/files/std/bash-spec241.pdf +[examples section]: https://github.com/RustCrypto/hashes#Examples diff --git a/bash-prg-hash/benches/mod.rs b/bash-prg-hash/benches/mod.rs new file mode 100644 index 000000000..4659e417b --- /dev/null +++ b/bash-prg-hash/benches/mod.rs @@ -0,0 +1,30 @@ +#![feature(test)] +extern crate test; + +use bash_prg_hash::{BashPrgHash1282, BashPrgHash1921, BashPrgHash2562}; +use digest::bench_update; +use test::Bencher; + +bench_update!( + BashPrgHash1282::default(); + bash_prg_hash1282_10 10; + bash_prg_hash1282_100 100; + bash_prg_hash1282_1000 1000; + bash_prg_hash1282_10000 10000; +); + +bench_update!( + BashPrgHash1921::default(); + bash_prg_hash1921_10 10; + bash_prg_hash1921_100 100; + bash_prg_hash1921_1000 1000; + bash_prg_hash1921_10000 10000; +); + +bench_update!( + BashPrgHash2562::default(); + bash_prg_hash2562_10 10; + bash_prg_hash2562_100 100; + bash_prg_hash2562_1000 1000; + bash_prg_hash2562_10000 10000; +); diff --git a/bash-prg-hash/src/block_api.rs b/bash-prg-hash/src/block_api.rs new file mode 100644 index 000000000..73774ce8c --- /dev/null +++ b/bash-prg-hash/src/block_api.rs @@ -0,0 +1,260 @@ +use bash_f::{STATE_WORDS, bash_f}; +use core::{fmt, marker::PhantomData}; +use digest::block_api::BlockSizeUser; +use digest::core_api::AlgorithmName; + +use crate::variants::{Cap1, Cap2, Capacity, Level128, Level192, Level256, SecurityLevel}; + +/// A constant representing the maximum size of a header in bytes. +pub const MAX_HEADER_LEN: usize = 60; + +/// Data type codes from Table 3 of STB 34.101.77-2020 +const DATA: u8 = 0b000010; +/// Data type codes from Table 3 of STB 34.101.77-2020 +const OUT: u8 = 0b000100; + +/// Core bash-prg-hash hasher state generic over security level and capacity. +/// +/// Specified in Section 8.12 of STB 34.101.77-2020. +pub struct BashPrgHashCore { + state: [u64; STATE_WORDS], + rate_bytes: usize, // r/8 - buffer size in bytes + offset: usize, // current offset in bytes + header: [u8; MAX_HEADER_LEN], // max header size (480 bits = 60 bytes) + header_len: usize, // header length in bytes + data_committed: bool, // whether commit(DATA) was called in + _level: PhantomData, + _capacity: PhantomData, +} + +macro_rules! impl_block_sizes { + ($($level:ty, $cap:ty),* $(,)?) => { + $( + impl BlockSizeUser for BashPrgHashCore<$level, $cap> { + type BlockSize = digest::typenum::U<{ + (1536 - 2 * <$cap as Capacity>::CAPACITY * <$level as SecurityLevel>::LEVEL) / 8 + }>; + } + )* + }; +} + +impl_block_sizes! { + Level128, Cap1, + Level192, Cap1, + Level256, Cap1, + Level128, Cap2, + Level192, Cap2, + Level256, Cap2, +} + +impl BashPrgHashCore { + /// Calculate buffer size r = 1536 - 2dℓ (in bytes) + const fn calculate_rate_bytes() -> usize { + (1536 - 2 * D::CAPACITY * L::LEVEL) / 8 + } + + /// Create a new hasher with an announcement (header). + pub fn new(header: &[u8]) -> Self { + assert!( + header.len() <= MAX_HEADER_LEN, + "Header length must not exceed 480 bits (60 bytes)" + ); + assert_eq!( + header.len() % 4, + 0, + "Header length must be multiple of 32 bits (4 bytes)" + ); + + let mut header_buf = [0u8; 60]; + header_buf[..header.len()].copy_from_slice(header); + + Self { + state: [0u64; STATE_WORDS], + rate_bytes: Self::calculate_rate_bytes(), + offset: 0, + header: header_buf, + header_len: header.len(), + data_committed: false, + _level: PhantomData, + _capacity: PhantomData, + } + } + + /// Helper: modify byte at position in state + fn modify_byte(&mut self, pos: usize, f: F) { + let word_idx = pos / 8; + let byte_in_word = pos % 8; + let mut bytes = self.state[word_idx].to_le_bytes(); + f(&mut bytes[byte_in_word]); + self.state[word_idx] = u64::from_le_bytes(bytes); + } + + /// Helper: get byte at position in state + fn get_byte(&self, pos: usize) -> u8 { + let word_idx = pos / 8; + let byte_in_word = pos % 8; + self.state[word_idx].to_le_bytes()[byte_in_word] + } + + /// XOR input bytes into state at current offset + fn xor_in(&mut self, input: &[u8]) { + for (i, &byte) in input.iter().enumerate() { + self.modify_byte(self.offset + i, |b| *b ^= byte); + } + self.offset += input.len(); + } + + /// Extract bytes from state at current offset + fn extract_bytes(&mut self, output: &mut [u8]) { + for (i, out_byte) in output.iter_mut().enumerate() { + *out_byte = self.get_byte(self.offset + i); + } + self.offset += output.len(); + } + + /// Execute start command (Section 8.3) + fn start(&mut self) { + // Step 3: pos ← 8 + |A| + |K| (in bits) = 1 + header_len (in bytes) + let header_len = self.header_len; + self.offset = 1 + header_len; + + // Step 4: S[...pos) ← ⟨|A|/2 + |K|/32⟩_8 || A || K + // First byte: |A|/2 where |A| is in bits + let first_byte = ((header_len * 8) / 2) as u8; + self.modify_byte(0, |b| *b = first_byte); + + // Copy header bytes + for i in 0..header_len { + let byte = self.header[i]; + self.modify_byte(1 + i, |b| *b = byte); + } + + // Step 6: S[1472...) ← ⟨ℓ/4 + d⟩_64 + self.state[23] = (L::LEVEL / 4 + D::CAPACITY) as u64; + } + + /// Execute commit command (Section 8.4) + fn commit(&mut self, t: u8) { + // Step 1: S[pos...pos+8) ← S[pos...pos+8) ⊕ (t||01) + let tag = (t << 2) | 0x01; + self.modify_byte(self.offset, |b| *b ^= tag); + + // Step 2: S[r] ← S[r] ⊕ 1 (flip bit at position r in bits) + let r_bit_in_byte = (self.rate_bytes * 8) % 8; + // MSB-first within a byte: bit index i maps to (7 - i) + self.modify_byte(self.rate_bytes, |b| *b ^= 1u8 << (7 - r_bit_in_byte)); + + // Step 3: S ← bash-f(S) + bash_f(&mut self.state); + + // Step 4: pos ← 0 + self.offset = 0; + } + + /// Execute absorb command (Section 8.6) + pub(crate) fn absorb(&mut self, data: &[u8]) { + // Check if initialized: state[23] == 0 means not initialized + if self.state[23] == 0 { + self.start(); + } + + // Step 1: commit(DATA) - only once per absorption session + // We need data_committed because offset == 0 can happen multiple times: + // - After finalize() (need commit(DATA)) + // - After commit(DATA) but before absorbing (already did commit) + // - After full block during absorption (offset resets to 0) + // - After empty data calls (offset stays 0) + if !self.data_committed { + self.commit(DATA); + self.data_committed = true; + } + + // Steps 2-3: Process blocks + let mut input = data; + + while !input.is_empty() { + let to_absorb = input.len().min(self.rate_bytes - self.offset); + + self.xor_in(&input[..to_absorb]); + input = &input[to_absorb..]; + + if self.offset == self.rate_bytes { + bash_f(&mut self.state); + self.offset = 0; + } + } + } + + /// Prepare for reading output (squeeze) + pub(crate) fn finalize(&mut self) { + if self.state[23] == 0 { + self.start(); + } + + self.commit(OUT); + self.data_committed = false; // Reset for next absorption session + } + + /// Execute squeeze command (Section 8.7) + pub(crate) fn squeeze(&mut self, output: &mut [u8]) { + let mut remaining = output; + + while !remaining.is_empty() { + if self.offset == self.rate_bytes { + bash_f(&mut self.state); + self.offset = 0; + } + + let to_squeeze = remaining.len().min(self.rate_bytes - self.offset); + self.extract_bytes(&mut remaining[..to_squeeze]); + remaining = &mut remaining[to_squeeze..]; + } + } +} + +impl Clone for BashPrgHashCore { + fn clone(&self) -> Self { + Self { + state: self.state, + rate_bytes: self.rate_bytes, + offset: self.offset, + header: self.header, + header_len: self.header_len, + data_committed: self.data_committed, + _level: PhantomData, + _capacity: PhantomData, + } + } +} + +impl Default for BashPrgHashCore { + fn default() -> Self { + Self::new(&[]) + } +} + +impl AlgorithmName for BashPrgHashCore { + fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "BashPrgHash{}-{}", L::LEVEL, D::CAPACITY) + } +} + +impl fmt::Debug for BashPrgHashCore { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("BashPrgHashCore { ... }") + } +} + +impl Drop for BashPrgHashCore { + fn drop(&mut self) { + #[cfg(feature = "zeroize")] + { + use digest::zeroize::Zeroize; + self.state.zeroize(); + } + } +} + +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for BashPrgHashCore {} diff --git a/bash-prg-hash/src/lib.rs b/bash-prg-hash/src/lib.rs new file mode 100644 index 000000000..e65788e3c --- /dev/null +++ b/bash-prg-hash/src/lib.rs @@ -0,0 +1,138 @@ +#![no_std] +#![doc = include_str!("../README.md")] +#![doc( + html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg", + html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/6ee8e381/logo.svg" +)] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn(missing_docs, rust_2018_idioms)] +#![forbid(unsafe_code)] + +pub use digest::{self, Digest}; + +/// Block-level types +pub mod block_api; +#[cfg(feature = "oid")] +mod oids; +mod variants; + +use core::fmt; +use digest::{ExtendableOutput, ExtendableOutputReset, Reset, Update, XofReader}; + +pub use block_api::BashPrgHashCore; +pub use variants::{Cap1, Cap2, Capacity, Level128, Level192, Level256, SecurityLevel}; + +/// bash-prg-hash hasher generic over security level and capacity. +#[derive(Clone)] +pub struct BashPrgHash { + core: BashPrgHashCore, + finalized: bool, +} + +/// Helper trait to extract security level from hash type +pub trait HashLevel { + /// Security level from specification + type Level: SecurityLevel; +} + +impl HashLevel for BashPrgHash { + type Level = L; +} + +impl BashPrgHash { + /// Create a new hasher with an announcement (header). + pub fn new(header: &[u8]) -> Self { + Self { + core: BashPrgHashCore::new(header), + finalized: false, + } + } + + /// Create a new hasher with an empty announcement. + pub fn new_with_empty_header() -> Self { + Self::new(&[]) + } +} + +impl Default for BashPrgHash { + fn default() -> Self { + Self::new_with_empty_header() + } +} + +impl Update for BashPrgHash { + fn update(&mut self, data: &[u8]) { + assert!(!self.finalized, "Cannot update after finalization"); + self.core.absorb(data); + } +} + +impl ExtendableOutput for BashPrgHash { + type Reader = BashPrgHashReader; + + fn finalize_xof(mut self) -> Self::Reader { + self.core.finalize(); + self.finalized = true; + BashPrgHashReader { core: self.core } + } +} + +impl ExtendableOutputReset for BashPrgHash { + fn finalize_xof_reset(&mut self) -> Self::Reader { + let mut core_clone = self.core.clone(); + core_clone.finalize(); + self.reset(); + BashPrgHashReader { core: core_clone } + } +} + +impl Reset for BashPrgHash { + fn reset(&mut self) { + self.core = BashPrgHashCore::default(); + self.finalized = false; + } +} + +impl fmt::Debug for BashPrgHash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("BashPrgHash { ... }") + } +} + +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for BashPrgHash {} + +/// Reader for bash-prg-hash XOF output. +pub struct BashPrgHashReader { + core: BashPrgHashCore, +} + +impl XofReader for BashPrgHashReader { + fn read(&mut self, buffer: &mut [u8]) { + self.core.squeeze(buffer); + } +} + +impl Clone for BashPrgHashReader { + fn clone(&self) -> Self { + Self { + core: self.core.clone(), + } + } +} + +#[cfg(feature = "zeroize")] +impl digest::zeroize::ZeroizeOnDrop for BashPrgHashReader {} + +/// bash-prg-hash with ℓ = 128 and 𝑑 = 1 +pub type BashPrgHash1281 = BashPrgHash; +/// bash-prg-hash with ℓ = 128 and 𝑑 = 2 +pub type BashPrgHash1282 = BashPrgHash; +/// bash-prg-hash with ℓ = 192 and 𝑑 = 1 +pub type BashPrgHash1921 = BashPrgHash; +/// bash-prg-hash with ℓ = 192 and 𝑑 = 2 +pub type BashPrgHash1922 = BashPrgHash; +/// bash-prg-hash with ℓ = 256 and 𝑑 = 1 +pub type BashPrgHash2561 = BashPrgHash; +/// bash-prg-hash with ℓ = 256 and 𝑑 = 2 +pub type BashPrgHash2562 = BashPrgHash; diff --git a/bash-prg-hash/src/oids.rs b/bash-prg-hash/src/oids.rs new file mode 100644 index 000000000..69f95b799 --- /dev/null +++ b/bash-prg-hash/src/oids.rs @@ -0,0 +1,30 @@ +use digest::const_oid::{AssociatedOid, ObjectIdentifier}; + +use crate::{ + block_api::BashPrgHashCore, + variants::{Cap1, Cap2, Level128, Level192, Level256}, +}; + +impl AssociatedOid for BashPrgHashCore { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.21"); +} + +impl AssociatedOid for BashPrgHashCore { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.22"); +} + +impl AssociatedOid for BashPrgHashCore { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.23"); +} + +impl AssociatedOid for BashPrgHashCore { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.24"); +} + +impl AssociatedOid for BashPrgHashCore { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.25"); +} + +impl AssociatedOid for BashPrgHashCore { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.26"); +} diff --git a/bash-prg-hash/src/variants.rs b/bash-prg-hash/src/variants.rs new file mode 100644 index 000000000..f8db25e37 --- /dev/null +++ b/bash-prg-hash/src/variants.rs @@ -0,0 +1,72 @@ +use digest::typenum::{U1, U2, U128, U192, U256, Unsigned}; + +/// Sealed trait to prevent external implementations. +pub trait Sealed {} + +/// Security level trait for programmable algorithms. +/// +/// Specified in Section 5.3 of STB 34.101.77-2020. +/// +/// Standard levels: ℓ ∈ {128, 192, 256}. +pub trait SecurityLevel: Sealed { + /// Type-level representation of ℓ + type TypeLevel: Unsigned; + /// Security level ℓ in bits + const LEVEL: usize = ::USIZE; +} + +/// Capacity parameter for programmable algorithms. +/// +/// Specified in Section 5.4 of STB 34.101.77-2020. +/// +/// Capacity d ∈ {1, 2}. +pub trait Capacity: Sealed { + /// Type-level representation of d + type TypeCapacity: Unsigned; + /// Capacity d + const CAPACITY: usize = ::USIZE; +} + +macro_rules! impl_type_with_sealed { + ( + $(#[$meta:meta])* + $name:ident: $trait:ident { + $assoc_type:ident = $type_val:ty + } + ) => { + $(#[$meta])* + #[derive(Clone, Copy, Debug)] + pub struct $name; + + impl Sealed for $name {} + + impl $trait for $name { + type $assoc_type = $type_val; + } + }; +} + +impl_type_with_sealed! { + /// Security level ℓ = 128 + Level128: SecurityLevel { TypeLevel = U128 } +} + +impl_type_with_sealed! { + /// Security level ℓ = 192 + Level192: SecurityLevel { TypeLevel = U192 } +} + +impl_type_with_sealed! { + /// Security level ℓ = 256 + Level256: SecurityLevel { TypeLevel = U256 } +} + +impl_type_with_sealed! { + /// Capacity d = 1 + Cap1: Capacity { TypeCapacity = U1 } +} + +impl_type_with_sealed! { + /// Capacity d = 2 + Cap2: Capacity { TypeCapacity = U2 } +} diff --git a/bash-prg-hash/tests/data/bashprg1282.blb b/bash-prg-hash/tests/data/bashprg1282.blb new file mode 100644 index 0000000000000000000000000000000000000000..c1cf972a5baf9fa03c7a4ffaeb4177d80dd4283e GIT binary patch literal 546 zcmd;JU~n+|#U6JsT=7%D&Sj1(PS0Fyyz*W3iilh`x6^CP+78%$Zm8QhW!DKVj<42c zxeR>~Ua?PFS$@rx-gbPAs$S6{roZQSqaR-r65pBF_%FR}ZDDpNcg%(Z|BVla?JzyU zy6(n<`k4>4??^pr3V%~zf9Au~cfB7Et}e5f|E%KF_hUa2uI`@W|Jf$BI=RLm>#pDO z%6Xe?4ee%Z%~QX;=X|7`guGAGc88M&|DB52op_f$kBAY3^+7!D4S!96W9(HtQ^8n83V;xBFgtr`6nJ%}2VT4_TdPdF>{= HrC}KW#y1ne literal 0 HcmV?d00001 diff --git a/bash-prg-hash/tests/data/bashprg1921.blb b/bash-prg-hash/tests/data/bashprg1921.blb new file mode 100644 index 0000000000000000000000000000000000000000..9f377535020d4bf09952d2dc63586ee3c9871c4c GIT binary patch literal 592 zcmZQ$Xq4MHW!DKVj<42cxeR>~Ua?PFS$@rx-gbPAs$S6{roZQSqaR-r65pBF_%FR} zZDDpNcg%(Z|BVla?JzyUy6(n<`k4>4??^pr3V%~zf9Au~cfB7Et}e5f|E%KF_hUa2 zuI`@W|Jf$BI=RLm>#pDO%6Xe?4ee%Z%~QX;=X|7`guGAG_Q@~rJ=f{y;a{eE+w)6) z@w9}*G-lm&;VX>G_GHwY|F4%|)GlJxa=ce3>9}a%gGax$zHOiR&Uov+v-L?ICv5z^ zA*fMdkomwpp||CP-dV0~Zaqf=&mVoadDD#hmm4oJ+{*m@HtJce+ODg|)gPs5DMf!t zooV3AJkPkhQD?AufsH*_?5#?Id(iy3p0l>C?z*lm9&(b$^7$?0>epgt|1kV(pX?v? XN06~i_g6p|Kbw~Cn$|f7ZTA2G1OG6k literal 0 HcmV?d00001 diff --git a/bash-prg-hash/tests/data/bashprg2562.blb b/bash-prg-hash/tests/data/bashprg2562.blb new file mode 100644 index 0000000000000000000000000000000000000000..6c2765aa6d3022e212be5f45b2df2eee8f0abb6b GIT binary patch literal 643 zcmZQ$Xq4MHW!DKVj<42cxeR>~Ua?PFS$@rx-gbPAs$S6{roZQSqaR-r65pBF_%FR} zZDDpNcg%(Z|BVla?JzyUy6(n<`k4>4??^pr3V%~zf9Au~cfB7Et}e5f|E%KF_hUa2 zuI`@W|Jf$BI=RLm>#pDO%6Xe?4ee%Z%~QX;=X|7`guGAG_Q@~rJ=f{y;a{eE+w)6) z@w5hpgqB%7v4&6fzVteH?$w%9?ME-Y1S|RXW(K+KDcDqVE`%lUX@qi6xqBs3|I)@? z|MJhAO}_ppAynyc<(W^#DsKuK6$Y7?+#49)REH_5c76IUdi%)3DyI!Xdkk;XXxxb@ zyR_<=ivW|7k95h-ZM*-lM(vpKri_R0);!r`(;nQ8U*)`9^o^0IRjFd5&S3Ks8+)+W zTa^Ze1^*R=ms<%aDSS^~jp(>5LNq$5uxy literal 0 HcmV?d00001 diff --git a/bash-prg-hash/tests/mod.rs b/bash-prg-hash/tests/mod.rs new file mode 100644 index 000000000..101ed0015 --- /dev/null +++ b/bash-prg-hash/tests/mod.rs @@ -0,0 +1,48 @@ +use bash_prg_hash::{BashPrgHash1282, BashPrgHash1921, BashPrgHash2562}; +use digest::ExtendableOutput; +use digest::dev::xof_reset_test; +use hex_literal::hex; + +// Test vectors from STB 34.101.77-2020 (Appendix A, Table A.5) +digest::new_test!(bashprg1282, BashPrgHash1282, xof_reset_test); +digest::new_test!(bashprg1921, BashPrgHash1921, xof_reset_test); +// Not in STB 34.101.77-2020, but included for completeness +digest::new_test!(bashprg2562, BashPrgHash2562, xof_reset_test); + +macro_rules! test_bash_prg_rand { + ($name:ident, $hasher:ty, $expected:expr) => { + #[test] + fn $name() { + use bash_prg_hash::{HashLevel, SecurityLevel}; + let mut h = <$hasher>::default(); + digest::dev::feed_rand_16mib(&mut h); + let mut output = vec![0u8; <<$hasher as HashLevel>::Level as SecurityLevel>::LEVEL / 4]; + h.finalize_xof_into(&mut output); + assert_eq!(&output[..], $expected); + } + }; +} + +test_bash_prg_rand!( + bashprg1282_rand, + BashPrgHash1282, + hex!("BF15805CDEAE220A9DD50C325A4A0BDF326C6ED853CFA89592A9E2BEB4D0585C") +); + +test_bash_prg_rand!( + bashprg1921_rand, + BashPrgHash1921, + hex!( + "82176D6DAF4F631E251CA41A7688FEB643B954383186C7902AB09D80EB5AB17C + BA286D16912EBBACEC3D8143966107F6" + ) +); + +test_bash_prg_rand!( + bashprg2562_rand, + BashPrgHash2562, + hex!( + "AD07A8D61928296F4115F9E51AAA5FA986899BFDA8443F139D969600064EBCE2 + D591F583FA27F6B0F7E73DA2B29AF382AC2374C04463B91A27F1C48FEE8AAB2C" + ) +); From 59c894aa98d30e1fcd422ad490310bf9b371eec7 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Tue, 4 Nov 2025 16:17:09 +0300 Subject: [PATCH 12/13] `bash-prg-hash`: add github workflow --- .github/workflows/bash-prg-hash.yml | 72 +++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 .github/workflows/bash-prg-hash.yml diff --git a/.github/workflows/bash-prg-hash.yml b/.github/workflows/bash-prg-hash.yml new file mode 100644 index 000000000..10736e8c0 --- /dev/null +++ b/.github/workflows/bash-prg-hash.yml @@ -0,0 +1,72 @@ +name: bash-prg-hash + +on: + pull_request: + paths: + - ".github/workflows/bash-prg-hash.yml" + - "bash-prg-hash/**" + - "Cargo.*" + push: + branches: master + +defaults: + run: + working-directory: bash-prg-hash + +env: + CARGO_INCREMENTAL: 0 + RUSTFLAGS: "-Dwarnings" + +# Cancels CI jobs when new commits are pushed to a PR branch +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + set-msrv: + uses: RustCrypto/actions/.github/workflows/set-msrv.yml@master + with: + msrv: 1.85.0 + + build: + needs: set-msrv + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - ${{needs.set-msrv.outputs.msrv}} + - stable + target: + - thumbv7em-none-eabi + - wasm32-unknown-unknown + steps: + - uses: actions/checkout@v5 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + targets: ${{ matrix.target }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack build --target ${{ matrix.target }} --each-feature --exclude-features default,std + + test: + needs: set-msrv + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - ${{needs.set-msrv.outputs.msrv}} + - stable + steps: + - uses: actions/checkout@v5 + - uses: RustCrypto/actions/cargo-cache@master + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - uses: RustCrypto/actions/cargo-hack-install@master + - run: cargo hack test --feature-powerset + + minimal-versions: + uses: RustCrypto/actions/.github/workflows/minimal-versions.yml@master + with: + working-directory: ${{ github.workflow }} From 864df0805d19b2652919b8b1c8c6b47f95036ab9 Mon Sep 17 00:00:00 2001 From: Alexandr Kitaev Date: Tue, 4 Nov 2025 16:19:14 +0300 Subject: [PATCH 13/13] `bash-prg-hash`: fix entry in CHANGELOG.md --- bash-prg-hash/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bash-prg-hash/CHANGELOG.md b/bash-prg-hash/CHANGELOG.md index a3a56b13e..f09fc589e 100644 --- a/bash-prg-hash/CHANGELOG.md +++ b/bash-prg-hash/CHANGELOG.md @@ -7,6 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 (UNRELEASED) -- Initial release ([#745]) +- Initial release ([#751]) -[#745]: https://github.com/RustCrypto/hashes/pull/745 +[#745]: https://github.com/RustCrypto/hashes/pull/751 \ No newline at end of file