├── .gitmodules ├── benchcmp_with_xkcp_turboshake128_on_x86_64.png ├── tests ├── test_conf.hpp ├── test_utils.hpp ├── test_sha3_224.cpp ├── test_sha3_256.cpp ├── test_sha3_384.cpp ├── test_sha3_512.cpp ├── test.mk ├── test_shake128.cpp ├── test_shake256.cpp ├── test_turboshake128.cpp └── test_turboshake256.cpp ├── .github ├── dependabot.yml └── workflows │ └── test_ci.yml ├── .gitignore ├── examples ├── example_helper.hpp ├── example.mk ├── sha3_224.cpp ├── sha3_256.cpp ├── sha3_384.cpp ├── sha3_512.cpp ├── shake128.cpp ├── shake256.cpp ├── turboshake128.cpp └── turboshake256.cpp ├── benches ├── bench_common.hpp ├── bench_keccak.cpp ├── bench.mk ├── bench_hashing.cpp └── bench_xof.cpp ├── include └── sha3 │ ├── internals │ ├── force_inline.hpp │ ├── utils.hpp │ ├── sponge.hpp │ └── keccak.hpp │ ├── sha3_512.hpp │ ├── sha3_256.hpp │ ├── sha3_384.hpp │ ├── sha3_224.hpp │ ├── shake256.hpp │ ├── turboshake128.hpp │ ├── turboshake256.hpp │ └── shake128.hpp ├── Makefile ├── LICENSE ├── .clang-format └── README.md /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gtest-parallel"] 2 | path = gtest-parallel 3 | url = https://github.com/google/gtest-parallel 4 | -------------------------------------------------------------------------------- /benchcmp_with_xkcp_turboshake128_on_x86_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itzmeanjan/sha3/HEAD/benchcmp_with_xkcp_turboshake128_on_x86_64.png -------------------------------------------------------------------------------- /tests/test_conf.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | static constexpr size_t MIN_MSG_LEN = 0; 5 | static constexpr size_t MAX_MSG_LEN = 513; 6 | static constexpr size_t MIN_OUT_LEN = 0; 7 | static constexpr size_t MAX_OUT_LEN = 513; 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: weekly 7 | - package-ecosystem: gitsubmodule 8 | directory: / 9 | schedule: 10 | interval: weekly 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Project Development 35 | .vscode 36 | __pycache__ 37 | 38 | # For Clangd 39 | compile_commands.json 40 | .cache 41 | -------------------------------------------------------------------------------- /examples/example_helper.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | // Given a bytearray of length N, this function converts it to human readable hex string of length N << 1 | N >= 0 8 | static inline std::string 9 | to_hex(std::span bytes) 10 | { 11 | std::stringstream ss; 12 | ss << std::hex; 13 | 14 | for (size_t i = 0; i < bytes.size(); i++) { 15 | ss << std::setw(2) << std::setfill('0') << static_cast(bytes[i]); 16 | } 17 | 18 | return ss.str(); 19 | } 20 | -------------------------------------------------------------------------------- /examples/example.mk: -------------------------------------------------------------------------------- 1 | EXAMPLE_BUILD_DIR := $(BUILD_DIR)/example 2 | 3 | EXAMPLE_DIR := examples 4 | EXAMPLE_SOURCES := $(wildcard $(EXAMPLE_DIR)/*.cpp) 5 | EXAMPLE_HEADERS := $(wildcard $(EXAMPLE_DIR)/*.hpp) 6 | EXAMPLE_EXECS := $(addprefix $(EXAMPLE_BUILD_DIR)/, $(notdir $(EXAMPLE_SOURCES:.cpp=.exe))) 7 | 8 | $(EXAMPLE_BUILD_DIR): 9 | mkdir -p $@ 10 | 11 | $(EXAMPLE_BUILD_DIR)/%.exe: $(EXAMPLE_DIR)/%.cpp $(EXAMPLE_BUILD_DIR) 12 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(RELEASE_FLAGS) $(I_FLAGS) $< -o $@ 13 | 14 | example: $(EXAMPLE_EXECS) ## Build and run all example programs, demonstrating usage of SHA3 API 15 | $(foreach exec,$^,./$(exec); echo "--- --- ---";) 16 | -------------------------------------------------------------------------------- /benches/bench_common.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | const auto compute_min = [](const std::vector& v) -> double { return *std::min_element(v.begin(), v.end()); }; 8 | const auto compute_max = [](const std::vector& v) -> double { return *std::max_element(v.begin(), v.end()); }; 9 | 10 | // Generates N -many random values of type T | N >= 0 11 | template 12 | static inline void 13 | random_data(std::span data) 14 | requires(std::is_unsigned_v) 15 | { 16 | std::random_device rd; 17 | std::mt19937_64 gen(rd()); 18 | std::uniform_int_distribution dis; 19 | 20 | const size_t len = data.size(); 21 | for (size_t i = 0; i < len; i++) { 22 | data[i] = dis(gen); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /include/sha3/internals/force_inline.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /** 4 | * Following content is taken from 5 | * https://github.com/itzmeanjan/ml-kem/blob/b43b819e880a6b1e4e165211060ef8fc030b9b6d/include/ml_kem/internals/utility/force_inline.hpp 6 | */ 7 | 8 | #ifdef _MSC_VER 9 | // MSVC 10 | #define forceinline __forceinline 11 | 12 | #elif defined(__GNUC__) 13 | // GCC 14 | #if defined(__cplusplus) && __cplusplus >= 201103L 15 | #define forceinline inline __attribute__((__always_inline__)) 16 | #else 17 | #define forceinline inline 18 | #endif 19 | 20 | #elif defined(__CLANG__) 21 | // Clang 22 | #if __has_attribute(__always_inline__) 23 | #define forceinline inline __attribute__((__always_inline__)) 24 | #else 25 | #define forceinline inline 26 | #endif 27 | 28 | #else 29 | // Others 30 | #define forceinline inline 31 | 32 | #endif 33 | -------------------------------------------------------------------------------- /examples/sha3_224.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_224.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/sha3_224.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | 15 | std::vector msg(msg_len, 0); 16 | std::iota(msg.begin(), msg.end(), 0); 17 | 18 | auto md = sha3_224::sha3_224_t::hash(msg); 19 | 20 | // Or do following, if you want to absorb message in multiple calls. 21 | // 22 | // std::array md{}; 23 | // sha3_224::sha3_224_t hasher; 24 | // 25 | // hasher.absorb(msg); 26 | // hasher.finalize(); 27 | // hasher.digest(md); 28 | 29 | std::cout << "SHA3-224" << std::endl << std::endl; 30 | std::cout << "Message : " << to_hex(msg) << "\n"; 31 | std::cout << "Message Digest : " << to_hex(md) << "\n"; 32 | 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /examples/sha3_256.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_256.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/sha3_256.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | 15 | std::vector msg(msg_len, 0); 16 | std::iota(msg.begin(), msg.end(), 0); 17 | 18 | auto md = sha3_256::sha3_256_t::hash(msg); 19 | 20 | // Or do following, if you want to absorb message in multiple calls. 21 | // 22 | // std::array md{}; 23 | // sha3_256::sha3_256_t hasher; 24 | // 25 | // hasher.absorb(msg); 26 | // hasher.finalize(); 27 | // hasher.digest(md); 28 | 29 | std::cout << "SHA3-256" << std::endl << std::endl; 30 | std::cout << "Message : " << to_hex(msg) << "\n"; 31 | std::cout << "Message Digest : " << to_hex(md) << "\n"; 32 | 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /examples/sha3_384.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_384.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/sha3_384.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | 15 | std::vector msg(msg_len, 0); 16 | std::iota(msg.begin(), msg.end(), 0); 17 | 18 | auto md = sha3_384::sha3_384_t::hash(msg); 19 | 20 | // Or do following, if you want to absorb message in multiple calls. 21 | // 22 | // std::array md{}; 23 | // sha3_384::sha3_384_t hasher; 24 | // 25 | // hasher.absorb(msg); 26 | // hasher.finalize(); 27 | // hasher.digest(md); 28 | 29 | std::cout << "SHA3-384" << std::endl << std::endl; 30 | std::cout << "Message : " << to_hex(msg) << "\n"; 31 | std::cout << "Message Digest : " << to_hex(md) << "\n"; 32 | 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /examples/sha3_512.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_512.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/sha3_512.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | 15 | std::vector msg(msg_len, 0); 16 | std::iota(msg.begin(), msg.end(), 0); 17 | 18 | auto md = sha3_512::sha3_512_t::hash(msg); 19 | 20 | // Or do following, if you want to absorb message in multiple calls. 21 | // 22 | // std::array md{}; 23 | // sha3_512::sha3_512_t hasher; 24 | // 25 | // hasher.absorb(msg); 26 | // hasher.finalize(); 27 | // hasher.digest(md); 28 | 29 | std::cout << "SHA3-512" << std::endl << std::endl; 30 | std::cout << "Message : " << to_hex(msg) << "\n"; 31 | std::cout << "Message Digest : " << to_hex(md) << "\n"; 32 | 33 | return EXIT_SUCCESS; 34 | } 35 | -------------------------------------------------------------------------------- /benches/bench_keccak.cpp: -------------------------------------------------------------------------------- 1 | #include "bench_common.hpp" 2 | #include "sha3/internals/keccak.hpp" 3 | #include 4 | 5 | // Benchmarks Keccak-p[1600, 12] or Keccak-p[1600, 24] permutation. 6 | template 7 | void 8 | bench_keccak_permutation(benchmark::State& state) 9 | { 10 | uint64_t st[keccak::LANE_CNT]{}; 11 | random_data(st); 12 | 13 | for (auto _ : state) { 14 | keccak::permute(st); 15 | 16 | benchmark::DoNotOptimize(st); 17 | benchmark::ClobberMemory(); 18 | } 19 | 20 | const size_t bytes_processed = state.iterations() * sizeof(st); 21 | state.SetBytesProcessed(bytes_processed); 22 | 23 | #ifdef CYCLES_PER_BYTE 24 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 25 | #endif 26 | } 27 | 28 | BENCHMARK(bench_keccak_permutation<12>)->Name("keccak-p[1600, 12]")->ComputeStatistics("min", compute_min)->ComputeStatistics("max", compute_max); 29 | BENCHMARK(bench_keccak_permutation<24>)->Name("keccak-p[1600, 24]")->ComputeStatistics("min", compute_min)->ComputeStatistics("max", compute_max); 30 | -------------------------------------------------------------------------------- /examples/shake128.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/shake128.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/shake128.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | constexpr size_t out_len = 40; 15 | 16 | std::vector msg(msg_len, 0); 17 | std::iota(msg.begin(), msg.end(), 0); 18 | 19 | std::vector out(out_len, 0); 20 | auto out_span = std::span(out); 21 | 22 | // Create SHAKE128 hasher 23 | shake128::shake128_t hasher; 24 | 25 | // Absorb message bytes into sponge state 26 | hasher.absorb(msg); 27 | // Finalize sponge state 28 | hasher.finalize(); 29 | 30 | // Squeeze total `out_len` -bytes out of sponge, a single byte at a time. 31 | // One can request arbitrary many bytes of output, by calling `squeeze` arbitrary 32 | // many times, after it has been finalized. 33 | for (size_t i = 0; i < out_len; i++) { 34 | hasher.squeeze(out_span.subspan(i, 1)); 35 | } 36 | 37 | std::cout << "SHAKE128" << std::endl << std::endl; 38 | std::cout << "Message : " << to_hex(msg) << "\n"; 39 | std::cout << "Output : " << to_hex(out) << "\n"; 40 | 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /examples/shake256.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/shake256.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/shake256.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | constexpr size_t out_len = 40; 15 | 16 | std::vector msg(msg_len, 0); 17 | std::iota(msg.begin(), msg.end(), 0); 18 | 19 | std::vector out(out_len, 0); 20 | auto out_span = std::span(out); 21 | 22 | // Create SHAKE256 hasher 23 | shake256::shake256_t hasher; 24 | 25 | // Absorb message bytes into sponge state 26 | hasher.absorb(msg); 27 | // Finalize sponge state 28 | hasher.finalize(); 29 | 30 | // Squeeze total `out_len` -bytes out of sponge, a single byte at a time. 31 | // One can request arbitrary many bytes of output, by calling `squeeze` arbitrary 32 | // many times, after it has been finalized. 33 | for (size_t i = 0; i < out_len; i++) { 34 | hasher.squeeze(out_span.subspan(i, 1)); 35 | } 36 | 37 | std::cout << "SHAKE256" << std::endl << std::endl; 38 | std::cout << "Message : " << to_hex(msg) << "\n"; 39 | std::cout << "Output : " << to_hex(out) << "\n"; 40 | 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /examples/turboshake128.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/turboshake128.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/turboshake128.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | constexpr size_t out_len = 40; 15 | 16 | std::vector msg(msg_len, 0); 17 | std::iota(msg.begin(), msg.end(), 0); 18 | 19 | std::vector out(out_len, 0); 20 | auto out_span = std::span(out); 21 | 22 | // Create TurboSHAKE128 hasher 23 | turboshake128::turboshake128_t hasher; 24 | 25 | // Absorb message bytes into sponge state 26 | hasher.absorb(msg); 27 | // Finalize sponge state 28 | hasher.finalize(); 29 | 30 | // Squeeze total `out_len` -bytes out of sponge, a single byte at a time. 31 | // One can request arbitrary many bytes of output, by calling `squeeze` arbitrary 32 | // many times, after it has been finalized. 33 | for (size_t i = 0; i < out_len; i++) { 34 | hasher.squeeze(out_span.subspan(i, 1)); 35 | } 36 | 37 | std::cout << "TurboSHAKE128" << std::endl << std::endl; 38 | std::cout << "Message : " << to_hex(msg) << "\n"; 39 | std::cout << "Output : " << to_hex(out) << "\n"; 40 | 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /examples/turboshake256.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/turboshake256.hpp" 2 | #include "example_helper.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Compile it using 8 | // 9 | // g++ -std=c++20 -Wall -O3 -march=native -I include example/turboshake256.cpp 10 | int 11 | main() 12 | { 13 | constexpr size_t msg_len = 32; 14 | constexpr size_t out_len = 40; 15 | 16 | std::vector msg(msg_len, 0); 17 | std::iota(msg.begin(), msg.end(), 0); 18 | 19 | std::vector out(out_len, 0); 20 | auto out_span = std::span(out); 21 | 22 | // Create TurboSHAKE256 hasher 23 | turboshake256::turboshake256_t hasher; 24 | 25 | // Absorb message bytes into sponge state 26 | hasher.absorb(msg); 27 | // Finalize sponge state 28 | hasher.finalize(); 29 | 30 | // Squeeze total `out_len` -bytes out of sponge, a single byte at a time. 31 | // One can request arbitrary many bytes of output, by calling `squeeze` arbitrary 32 | // many times, after it has been finalized. 33 | for (size_t i = 0; i < out_len; i++) { 34 | hasher.squeeze(out_span.subspan(i, 1)); 35 | } 36 | 37 | std::cout << "TurboSHAKE256" << std::endl << std::endl; 38 | std::cout << "Message : " << to_hex(msg) << "\n"; 39 | std::cout << "Output : " << to_hex(out) << "\n"; 40 | 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := help 2 | 3 | # Collects inspiration from https://github.com/0xPolygonMiden/crypto/blob/3909b0199368b13fdfa934a324f984572d521e39/Makefile#L1-L5 4 | # and https://github.com/gtramontina/sourcing/blob/853252ee184c16bc69dd53e8457107d718aca04f/Makefile#L68-L72 5 | .PHONY: help 6 | help: 7 | @for file in $(MAKEFILE_LIST); do \ 8 | grep -E '^[a-zA-Z_-]+:.*?## .*$$' $${file} | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}';\ 9 | done 10 | 11 | 12 | CXX ?= clang++ 13 | CXX_DEFS += 14 | CXX_FLAGS := -std=c++20 15 | WARN_FLAGS := -Wall -Wextra -Wpedantic 16 | DEBUG_FLAGS := -O1 -g 17 | RELEASE_FLAGS := -O3 -march=native 18 | LINK_OPT_FLAGS := -flto 19 | 20 | I_FLAGS := -I ./include 21 | PERF_DEFS = -DCYCLES_PER_BYTE 22 | 23 | SRC_DIR := include 24 | SHA3_SOURCES := $(shell find $(SRC_DIR) -name '*.hpp') 25 | BUILD_DIR := build 26 | 27 | include tests/test.mk 28 | include benches/bench.mk 29 | include examples/example.mk 30 | 31 | $(GTEST_PARALLEL): 32 | git submodule update --init gtest-parallel 33 | 34 | .PHONY: clean 35 | clean: ## Remove build directory 36 | rm -rf $(BUILD_DIR) 37 | 38 | .PHONY: format 39 | format: $(SHA3_SOURCES) $(TEST_SOURCES) $(TEST_HEADERS) $(BENCHMARK_SOURCES) $(BENCHMARK_HEADERS) $(EXAMPLE_SOURCES) $(EXAMPLE_HEADERS) ## Format source code 40 | clang-format -i $^ 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2024, Anjan Roy 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /.github/workflows/test_ci.yml: -------------------------------------------------------------------------------- 1 | # Collects inspiration from https://github.com/itzmeanjan/frodoPIR/blob/aa654db3d11384fce73086cbbd37c63e0cb30e33/.github/workflows/test_ci.yml 2 | name: Test SHA3 Hash and Extendable Output Functions 3 | 4 | on: 5 | push: 6 | branches: ["master"] 7 | pull_request: 8 | branches: ["master"] 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{matrix.os}} 13 | strategy: 14 | matrix: 15 | os: [ 16 | ubuntu-latest, # x86_64 17 | macos-latest, # aarch64 18 | ] 19 | compiler: [g++, clang++] 20 | build_type: [debug, release] 21 | test_type: [standard, asan, ubsan] 22 | 23 | steps: 24 | - uses: actions/checkout@v6 25 | 26 | - name: Setup Google Test 27 | uses: Bacondish2023/setup-googletest@v1 28 | with: 29 | tag: v1.17.0 30 | 31 | - name: Execute Tests on ${{matrix.os}}, compiled with ${{matrix.compiler}} 32 | if: ${{matrix.test_type == 'standard'}} 33 | run: | 34 | CXX=${{matrix.compiler}} make test -j 35 | make clean 36 | 37 | - name: Execute Tests with ${{matrix.test_type}}, in ${{matrix.build_type}} mode, on ${{matrix.os}}, compiled with ${{matrix.compiler}} 38 | if: ${{matrix.test_type != 'standard'}} 39 | run: | 40 | CXX=${{matrix.compiler}} make ${{matrix.build_type}}_${{matrix.test_type}}_test -j 41 | make clean 42 | 43 | - name: Build and run examples 44 | run: | 45 | make example -j 46 | make clean 47 | -------------------------------------------------------------------------------- /include/sha3/internals/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/force_inline.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // Commonly used functions for SHA3 implementation 8 | namespace sha3_utils { 9 | 10 | // Given a byte array of length 8, this routine can be used for interpreting those 8 -bytes in little-endian order, as a 64 -bit unsigned integer. 11 | static forceinline constexpr uint64_t 12 | le_bytes_to_u64(std::span bytes) 13 | { 14 | static_assert(std::endian::native == std::endian::little); 15 | 16 | return (static_cast(bytes[7]) << 56u) | (static_cast(bytes[6]) << 48u) | (static_cast(bytes[5]) << 40u) | 17 | (static_cast(bytes[4]) << 32u) | (static_cast(bytes[3]) << 24u) | (static_cast(bytes[2]) << 16u) | 18 | (static_cast(bytes[1]) << 8u) | (static_cast(bytes[0]) << 0u); 19 | } 20 | 21 | // Given a 64 -bit unsigned integer as input, this routine can be used for interpreting those 8 -bytes in little-endian byte order. 22 | static forceinline constexpr void 23 | u64_to_le_bytes(uint64_t word, std::span bytes) 24 | { 25 | static_assert(std::endian::native == std::endian::little); 26 | 27 | bytes[0] = static_cast(word >> 0u); 28 | bytes[1] = static_cast(word >> 8u); 29 | bytes[2] = static_cast(word >> 16u); 30 | bytes[3] = static_cast(word >> 24u); 31 | bytes[4] = static_cast(word >> 32u); 32 | bytes[5] = static_cast(word >> 40u); 33 | bytes[6] = static_cast(word >> 48u); 34 | bytes[7] = static_cast(word >> 56u); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /benches/bench.mk: -------------------------------------------------------------------------------- 1 | BENCHMARK_BUILD_DIR := $(BUILD_DIR)/benchmark 2 | PERF_BUILD_DIR := $(BUILD_DIR)/perf 3 | 4 | BENCHMARK_DIR := benches 5 | BENCHMARK_SOURCES := $(wildcard $(BENCHMARK_DIR)/*.cpp) 6 | BENCHMARK_HEADERS := $(wildcard $(BENCHMARK_DIR)/*.hpp) 7 | BENCHMARK_OBJECTS := $(addprefix $(BENCHMARK_BUILD_DIR)/, $(notdir $(BENCHMARK_SOURCES:.cpp=.o))) 8 | BENCHMARK_LINK_FLAGS := -lbenchmark -lbenchmark_main -lpthread 9 | BENCHMARK_BINARY := $(BENCHMARK_BUILD_DIR)/bench.out 10 | PERF_OBJECTS := $(addprefix $(PERF_BUILD_DIR)/, $(notdir $(BENCHMARK_SOURCES:.cpp=.o))) 11 | PERF_BINARY := $(PERF_BUILD_DIR)/perf.out 12 | PERF_LINK_FLAGS := -lbenchmark -lbenchmark_main -lpfm -lpthread 13 | BENCHMARK_OUT_FILE := bench_result_at_commit_$(shell git log -1 --pretty=format:"%h")_on_$(shell uname -s)_$(shell uname -r)_$(shell uname -m)_with_$(CXX)_$(shell $(CXX) -dumpversion).json 14 | 15 | $(BENCHMARK_BUILD_DIR): 16 | mkdir -p $@ 17 | 18 | $(PERF_BUILD_DIR): 19 | mkdir -p $@ 20 | 21 | $(BENCHMARK_BUILD_DIR)/%.o: $(BENCHMARK_DIR)/%.cpp $(BENCHMARK_BUILD_DIR) 22 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(RELEASE_FLAGS) $(I_FLAGS) -c $< -o $@ 23 | 24 | $(BENCHMARK_BINARY): $(BENCHMARK_OBJECTS) 25 | $(CXX) $(RELEASE_FLAGS) $(LINK_OPT_FLAGS) $^ $(BENCHMARK_LINK_FLAGS) -o $@ 26 | 27 | benchmark: $(BENCHMARK_BINARY) ## Build and run all benchmarks, without libPFM -based CPU CYCLE counter statistics 28 | # Must *not* build google-benchmark with libPFM 29 | ./$< --benchmark_min_warmup_time=.05 --benchmark_enable_random_interleaving=false --benchmark_repetitions=10 --benchmark_min_time=0.1s --benchmark_display_aggregates_only=true --benchmark_report_aggregates_only=true --benchmark_counters_tabular=true --benchmark_out_format=json --benchmark_out=$(BENCHMARK_OUT_FILE) 30 | 31 | $(PERF_BUILD_DIR)/%.o: $(BENCHMARK_DIR)/%.cpp $(PERF_BUILD_DIR) 32 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(RELEASE_FLAGS) $(I_FLAGS) $(PERF_DEFS) -c $< -o $@ 33 | 34 | $(PERF_BINARY): $(PERF_OBJECTS) 35 | $(CXX) $(RELEASE_FLAGS) $(LINK_OPT_FLAGS) $^ $(PERF_LINK_FLAGS) -o $@ 36 | 37 | perf: $(PERF_BINARY) ## Build and run all benchmarks, while also collecting libPFM -based CPU CYCLE counter statistics 38 | # Must build google-benchmark with libPFM, follow https://gist.github.com/itzmeanjan/05dc3e946f635d00c5e0b21aae6203a7 39 | ./$< --benchmark_min_warmup_time=.05 --benchmark_enable_random_interleaving=false --benchmark_repetitions=10 --benchmark_min_time=0.1s --benchmark_display_aggregates_only=true --benchmark_report_aggregates_only=true --benchmark_counters_tabular=true --benchmark_perf_counters=CYCLES --benchmark_out_format=json --benchmark_out=$(BENCHMARK_OUT_FILE) 40 | -------------------------------------------------------------------------------- /tests/test_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace sha3_test_utils { 12 | 13 | // Generates N -many random values of type T | N >= 0. 14 | template 15 | static inline void 16 | random_data(std::span data) 17 | requires(std::is_unsigned_v) 18 | { 19 | std::random_device rd; 20 | std::mt19937_64 gen(rd()); 21 | std::uniform_int_distribution dis; 22 | 23 | const size_t len = data.size(); 24 | for (size_t i = 0; i < len; i++) { 25 | data[i] = dis(gen); 26 | } 27 | } 28 | 29 | // Given a static sized hex encoded string of length 2*L, this routine can be used for parsing it as a byte array of length L. 30 | template 31 | static inline std::array 32 | parse_static_sized_hex_string(std::string_view hex) 33 | { 34 | const size_t hlen = hex.length(); 35 | assert(hlen == 2 * L); 36 | 37 | const size_t blen = hlen / 2; 38 | std::array res{ 0 }; 39 | 40 | for (size_t i = 0; i < blen; i++) { 41 | const size_t off = i * 2; 42 | 43 | uint8_t byte = 0; 44 | auto sstr = hex.substr(off, 2); 45 | std::from_chars(sstr.data(), sstr.data() + 2, byte, 16); 46 | 47 | res[i] = byte; 48 | } 49 | 50 | return res; 51 | } 52 | 53 | // Given a dynamic sized hex encoded string of length 2*L, this routine can be used for parsing it as a byte array of length L. 54 | static inline std::vector 55 | parse_dynamic_sized_hex_string(std::string_view hex) 56 | { 57 | const size_t hlen = hex.length(); 58 | assert(hlen % 2 == 0); 59 | 60 | const size_t blen = hlen / 2; 61 | std::vector res(blen, 0); 62 | 63 | for (size_t i = 0; i < blen; i++) { 64 | const size_t off = i * 2; 65 | 66 | uint8_t byte = 0; 67 | auto sstr = hex.substr(off, 2); 68 | std::from_chars(sstr.data(), sstr.data() + 2, byte, 16); 69 | 70 | res[i] = byte; 71 | } 72 | 73 | return res; 74 | } 75 | 76 | /** 77 | * Generates bytearray of length n by repeating static byte pattern returned by `pattern()`, 78 | * following https://www.ietf.org/archive/id/draft-irtf-cfrg-kangarootwelve-09.html#name-test-vectors 79 | */ 80 | static inline std::vector 81 | ptn(const size_t n) 82 | { 83 | // Generates static byte pattern of length 251, following https://www.rfc-editor.org/rfc/rfc9861.html#name-test-vectors. 84 | auto pattern = []() -> auto { 85 | std::array pattern{}; 86 | std::iota(pattern.begin(), pattern.end(), 0); 87 | 88 | return pattern; 89 | }; 90 | 91 | std::vector res(n, 0); 92 | auto res_span = std::span(res); 93 | 94 | size_t off = 0; 95 | while (off < n) { 96 | const auto read = std::min(n - off, 251); 97 | 98 | auto static_pattern = pattern(); 99 | auto static_pattern_span = std::span(static_pattern); 100 | 101 | std::copy_n(static_pattern_span.subspan(0, read).begin(), read, res_span.subspan(off, read).begin()); 102 | off += read; 103 | } 104 | 105 | return res; 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /tests/test_sha3_224.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_224.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Eval SHA3-224 hash on statically defined input message during compilation-time. 11 | constexpr std::array 12 | eval_sha3_224() 13 | { 14 | // Statically defined input. 15 | std::array data{}; 16 | std::iota(data.begin(), data.end(), 0); 17 | 18 | // Computed output message digest. 19 | return sha3_224::sha3_224_t::hash(data); 20 | } 21 | 22 | // Ensure that SHA3-224 implementation is compile-time evaluable. 23 | TEST(Sha3Hashing, CompileTimeEvalSha3_224) 24 | { 25 | // Input = 26 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637 27 | // Output = fc95d44e806cbbd484e379882238f555fda923878c443abe4ce4cdd6 28 | 29 | constexpr auto md = eval_sha3_224(); 30 | static_assert(md == std::array{ 252, 149, 212, 78, 128, 108, 187, 212, 132, 227, 121, 136, 34, 56, 31 | 245, 85, 253, 169, 35, 135, 140, 68, 58, 190, 76, 228, 205, 214 }, 32 | "Must be able to compute Sha3-224 hash during compile-time !"); 33 | } 34 | 35 | // Test that absorbing same input message bytes using both incremental and one-shot hashing, should yield same output bytes, for SHA3-224 hasher. 36 | TEST(Sha3Hashing, Sha3_224IncrementalAbsorption) 37 | { 38 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 39 | std::vector msg(mlen); 40 | auto msg_span = std::span(msg); 41 | 42 | sha3_test_utils::random_data(msg_span); 43 | 44 | // Oneshot Hashing 45 | auto oneshot_out = sha3_224::sha3_224_t::hash(msg_span); 46 | 47 | // Incremental Hashing 48 | std::array multishot_out{ 0 }; 49 | sha3_224::sha3_224_t hasher; 50 | 51 | size_t off = 0; 52 | while (off < mlen) { 53 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 ! 54 | auto elen = std::min(std::max(msg[off], 1), mlen - off); 55 | 56 | hasher.absorb(msg_span.subspan(off, elen)); 57 | off += elen; 58 | } 59 | 60 | hasher.finalize(); 61 | hasher.digest(multishot_out); 62 | 63 | EXPECT_EQ(oneshot_out, multishot_out); 64 | } 65 | } 66 | 67 | /** 68 | * Ensure that SHA3-224 implementation is conformant with FIPS 202 standard, by using KAT file generated following 69 | * https://gist.github.com/itzmeanjan/448f97f9c49d781a5eb3ddd6ea6e7364. 70 | */ 71 | TEST(Sha3Hashing, Sha3_224KnownAnswerTests) 72 | { 73 | using namespace std::literals; 74 | 75 | const std::string kat_file = "./kats/sha3_224.kat"; 76 | std::fstream file(kat_file); 77 | 78 | while (true) { 79 | std::string len0; 80 | 81 | if (!std::getline(file, len0).eof()) { 82 | std::string msg0; 83 | std::string md0; 84 | 85 | std::getline(file, msg0); 86 | std::getline(file, md0); 87 | 88 | auto msg1 = std::string_view(msg0); 89 | auto md1 = std::string_view(md0); 90 | 91 | auto msg2 = msg1.substr(msg1.find("="sv) + 2, msg1.size()); 92 | auto md2 = md1.substr(md1.find("="sv) + 2, md1.size()); 93 | 94 | auto msg = sha3_test_utils::parse_dynamic_sized_hex_string(msg2); 95 | 96 | auto expected_md = sha3_test_utils::parse_static_sized_hex_string(md2); 97 | auto computed_md = sha3_224::sha3_224_t::hash(msg); 98 | 99 | EXPECT_EQ(computed_md, expected_md); 100 | 101 | std::string empty_line; 102 | std::getline(file, empty_line); 103 | } else { 104 | break; 105 | } 106 | } 107 | 108 | file.close(); 109 | } 110 | -------------------------------------------------------------------------------- /tests/test_sha3_256.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_256.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Eval SHA3-256 hash on statically defined input message during compilation-time. 11 | constexpr std::array 12 | eval_sha3_256() 13 | { 14 | // Statically defined input. 15 | std::array data{}; 16 | std::iota(data.begin(), data.end(), 0); 17 | 18 | // Computed output message digest. 19 | return sha3_256::sha3_256_t::hash(data); 20 | } 21 | 22 | // Ensure that SHA3-256 implementation is compile-time evaluable. 23 | TEST(Sha3Hashing, CompileTimeEvalSha3_256) 24 | { 25 | // Input = 26 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f 27 | // Output = c8ad478f4e1dd9d47dfc3b985708d92db1f8db48fe9cddd459e63c321f490402 28 | 29 | constexpr auto md = eval_sha3_256(); 30 | static_assert(md == std::array{ 200, 173, 71, 143, 78, 29, 217, 212, 125, 252, 59, 152, 87, 8, 217, 45, 31 | 177, 248, 219, 72, 254, 156, 221, 212, 89, 230, 60, 50, 31, 73, 4, 2 }, 32 | "Must be able to compute Sha3-256 hash during compile-time !"); 33 | } 34 | 35 | // Test that absorbing same input message bytes using both incremental and one-shot hashing, should yield same output bytes, for SHA3-256 hasher. 36 | TEST(Sha3Hashing, Sha3_256IncrementalAbsorption) 37 | { 38 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 39 | std::vector msg(mlen); 40 | auto msg_span = std::span(msg); 41 | 42 | sha3_test_utils::random_data(msg_span); 43 | 44 | // Oneshot Hashing 45 | auto oneshot_out = sha3_256::sha3_256_t::hash(msg_span); 46 | 47 | // Incremental Hashing 48 | std::array multishot_out{ 0 }; 49 | sha3_256::sha3_256_t hasher; 50 | 51 | size_t off = 0; 52 | while (off < mlen) { 53 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 ! 54 | auto elen = std::min(std::max(msg[off], 1), mlen - off); 55 | 56 | hasher.absorb(msg_span.subspan(off, elen)); 57 | off += elen; 58 | } 59 | 60 | hasher.finalize(); 61 | hasher.digest(multishot_out); 62 | 63 | EXPECT_EQ(oneshot_out, multishot_out); 64 | } 65 | } 66 | 67 | /** 68 | * Ensure that SHA3-256 implementation is conformant with FIPS 202 standard, by using KAT file generated following 69 | * https://gist.github.com/itzmeanjan/448f97f9c49d781a5eb3ddd6ea6e7364. 70 | */ 71 | TEST(Sha3Hashing, Sha3_256KnownAnswerTests) 72 | { 73 | using namespace std::literals; 74 | 75 | const std::string kat_file = "./kats/sha3_256.kat"; 76 | std::fstream file(kat_file); 77 | 78 | while (true) { 79 | std::string len0; 80 | 81 | if (!std::getline(file, len0).eof()) { 82 | std::string msg0; 83 | std::string md0; 84 | 85 | std::getline(file, msg0); 86 | std::getline(file, md0); 87 | 88 | auto msg1 = std::string_view(msg0); 89 | auto md1 = std::string_view(md0); 90 | 91 | auto msg2 = msg1.substr(msg1.find("="sv) + 2, msg1.size()); 92 | auto md2 = md1.substr(md1.find("="sv) + 2, md1.size()); 93 | 94 | auto msg = sha3_test_utils::parse_dynamic_sized_hex_string(msg2); 95 | 96 | auto expected_md = sha3_test_utils::parse_static_sized_hex_string(md2); 97 | auto computed_md = sha3_256::sha3_256_t::hash(msg); 98 | 99 | EXPECT_EQ(computed_md, expected_md); 100 | 101 | std::string empty_line; 102 | std::getline(file, empty_line); 103 | } else { 104 | break; 105 | } 106 | } 107 | 108 | file.close(); 109 | } 110 | -------------------------------------------------------------------------------- /benches/bench_hashing.cpp: -------------------------------------------------------------------------------- 1 | #include "bench_common.hpp" 2 | #include "sha3/sha3_224.hpp" 3 | #include "sha3/sha3_256.hpp" 4 | #include "sha3/sha3_384.hpp" 5 | #include "sha3/sha3_512.hpp" 6 | #include 7 | 8 | // Benchmarks SHA3-224 hash function with variable length input message. 9 | void 10 | bench_sha3_224(benchmark::State& state) 11 | { 12 | const size_t mlen = static_cast(state.range()); 13 | 14 | std::vector msg(mlen); 15 | random_data(msg); 16 | 17 | for (auto _ : state) { 18 | auto md = sha3_224::sha3_224_t::hash(msg); 19 | 20 | benchmark::DoNotOptimize(msg); 21 | benchmark::DoNotOptimize(md); 22 | benchmark::ClobberMemory(); 23 | } 24 | 25 | const size_t bytes_processed = state.iterations() * (msg.size() + sha3_224::DIGEST_LEN); 26 | state.SetBytesProcessed(bytes_processed); 27 | 28 | #ifdef CYCLES_PER_BYTE 29 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 30 | #endif 31 | } 32 | 33 | // Benchmarks SHA3-256 hash function with variable length input message. 34 | void 35 | bench_sha3_256(benchmark::State& state) 36 | { 37 | const size_t mlen = static_cast(state.range()); 38 | 39 | std::vector msg(mlen); 40 | random_data(msg); 41 | 42 | for (auto _ : state) { 43 | auto md = sha3_256::sha3_256_t::hash(msg); 44 | 45 | benchmark::DoNotOptimize(msg); 46 | benchmark::DoNotOptimize(md); 47 | benchmark::ClobberMemory(); 48 | } 49 | 50 | const size_t bytes_processed = state.iterations() * (msg.size() + sha3_256::DIGEST_LEN); 51 | state.SetBytesProcessed(bytes_processed); 52 | 53 | #ifdef CYCLES_PER_BYTE 54 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 55 | #endif 56 | } 57 | 58 | // Benchmarks SHA3-384 hash function with variable length input message. 59 | void 60 | bench_sha3_384(benchmark::State& state) 61 | { 62 | const size_t mlen = static_cast(state.range()); 63 | 64 | std::vector msg(mlen); 65 | random_data(msg); 66 | 67 | for (auto _ : state) { 68 | auto md = sha3_384::sha3_384_t::hash(msg); 69 | 70 | benchmark::DoNotOptimize(msg); 71 | benchmark::DoNotOptimize(md); 72 | benchmark::ClobberMemory(); 73 | } 74 | 75 | const size_t bytes_processed = state.iterations() * (msg.size() + sha3_384::DIGEST_LEN); 76 | state.SetBytesProcessed(bytes_processed); 77 | 78 | #ifdef CYCLES_PER_BYTE 79 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 80 | #endif 81 | } 82 | 83 | // Benchmarks SHA3-512 hash function with variable length input message. 84 | void 85 | bench_sha3_512(benchmark::State& state) 86 | { 87 | const size_t mlen = static_cast(state.range()); 88 | 89 | std::vector msg(mlen); 90 | random_data(msg); 91 | 92 | for (auto _ : state) { 93 | auto md = sha3_512::sha3_512_t::hash(msg); 94 | 95 | benchmark::DoNotOptimize(msg); 96 | benchmark::DoNotOptimize(md); 97 | benchmark::ClobberMemory(); 98 | } 99 | 100 | const size_t bytes_processed = state.iterations() * (msg.size() + sha3_512::DIGEST_LEN); 101 | state.SetBytesProcessed(bytes_processed); 102 | 103 | #ifdef CYCLES_PER_BYTE 104 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 105 | #endif 106 | } 107 | 108 | BENCHMARK(bench_sha3_224)->RangeMultiplier(4)->Range(64, 16384)->Name("sha3_224")->ComputeStatistics("min", compute_min)->ComputeStatistics("max", compute_max); 109 | BENCHMARK(bench_sha3_256)->RangeMultiplier(4)->Range(64, 16384)->Name("sha3_256")->ComputeStatistics("min", compute_min)->ComputeStatistics("max", compute_max); 110 | BENCHMARK(bench_sha3_384)->RangeMultiplier(4)->Range(64, 16384)->Name("sha3_384")->ComputeStatistics("min", compute_min)->ComputeStatistics("max", compute_max); 111 | BENCHMARK(bench_sha3_512)->RangeMultiplier(4)->Range(64, 16384)->Name("sha3_512")->ComputeStatistics("min", compute_min)->ComputeStatistics("max", compute_max); 112 | -------------------------------------------------------------------------------- /tests/test_sha3_384.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_384.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Eval SHA3-384 hash on statically defined input message during compilation-time. 11 | constexpr std::array 12 | eval_sha3_384() 13 | { 14 | // Statically defined input. 15 | std::array data{}; 16 | std::iota(data.begin(), data.end(), 0); 17 | 18 | // Computed output message digest. 19 | return sha3_384::sha3_384_t::hash(data); 20 | } 21 | 22 | // Ensure that SHA3-384 implementation is compile-time evaluable. 23 | TEST(Sha3Hashing, CompileTimeEvalSha3_384) 24 | { 25 | // Input = 26 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f 27 | // Output = 28 | // d6e266970a3fdcd4a833da861599179a060b576959e993b4698529304ee38c23c7102a7084c4d568b1d95523d14077e7 29 | 30 | constexpr auto md = eval_sha3_384(); 31 | static_assert(md == std::array{ 214, 226, 102, 151, 10, 63, 220, 212, 168, 51, 218, 134, 21, 153, 23, 154, 32 | 6, 11, 87, 105, 89, 233, 147, 180, 105, 133, 41, 48, 78, 227, 140, 35, 33 | 199, 16, 42, 112, 132, 196, 213, 104, 177, 217, 85, 35, 209, 64, 119, 231 }, 34 | "Must be able to compute Sha3-384 hash during compile-time !"); 35 | } 36 | 37 | // Test that absorbing same input message bytes using both incremental and one-shot hashing, should yield same output bytes, for SHA3-384 hasher. 38 | TEST(Sha3Hashing, Sha3_384IncrementalAbsorption) 39 | { 40 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 41 | std::vector msg(mlen); 42 | auto msg_span = std::span(msg); 43 | 44 | sha3_test_utils::random_data(msg_span); 45 | 46 | // Oneshot Hashing 47 | auto oneshot_out = sha3_384::sha3_384_t::hash(msg_span); 48 | 49 | // Incremental Hashing 50 | std::array multishot_out{ 0 }; 51 | sha3_384::sha3_384_t hasher; 52 | 53 | size_t off = 0; 54 | while (off < mlen) { 55 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 ! 56 | auto elen = std::min(std::max(msg[off], 1), mlen - off); 57 | 58 | hasher.absorb(msg_span.subspan(off, elen)); 59 | off += elen; 60 | } 61 | 62 | hasher.finalize(); 63 | hasher.digest(multishot_out); 64 | 65 | EXPECT_EQ(oneshot_out, multishot_out); 66 | } 67 | } 68 | 69 | /** 70 | * Ensure that SHA3-384 implementation is conformant with FIPS 202 standard, by using KAT file generated following 71 | * https://gist.github.com/itzmeanjan/448f97f9c49d781a5eb3ddd6ea6e7364. 72 | */ 73 | TEST(Sha3Hashing, Sha3_384KnownAnswerTests) 74 | { 75 | using namespace std::literals; 76 | 77 | const std::string kat_file = "./kats/sha3_384.kat"; 78 | std::fstream file(kat_file); 79 | 80 | while (true) { 81 | std::string len0; 82 | 83 | if (!std::getline(file, len0).eof()) { 84 | std::string msg0; 85 | std::string md0; 86 | 87 | std::getline(file, msg0); 88 | std::getline(file, md0); 89 | 90 | auto msg1 = std::string_view(msg0); 91 | auto md1 = std::string_view(md0); 92 | 93 | auto msg2 = msg1.substr(msg1.find("="sv) + 2, msg1.size()); 94 | auto md2 = md1.substr(md1.find("="sv) + 2, md1.size()); 95 | 96 | auto msg = sha3_test_utils::parse_dynamic_sized_hex_string(msg2); 97 | 98 | auto expected_md = sha3_test_utils::parse_static_sized_hex_string(md2); 99 | auto computed_md = sha3_384::sha3_384_t::hash(msg); 100 | 101 | EXPECT_EQ(computed_md, expected_md); 102 | 103 | std::string empty_line; 104 | std::getline(file, empty_line); 105 | } else { 106 | break; 107 | } 108 | } 109 | 110 | file.close(); 111 | } 112 | -------------------------------------------------------------------------------- /include/sha3/sha3_512.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/sponge.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // SHA3-512 Hash Function : Keccak[1024](M || 01, 512) 8 | namespace sha3_512 { 9 | 10 | // Number of rounds keccak-p[1600] is applied. 11 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS; 12 | 13 | // Bit length of SHA3-512 message digest. 14 | static constexpr size_t DIGEST_BIT_LEN = 512; 15 | 16 | // Byte length of SHA3-512 message digest. 17 | static constexpr size_t DIGEST_LEN = DIGEST_BIT_LEN / std::numeric_limits::digits; 18 | 19 | // Width of capacity portion of the sponge, in bits. 20 | static constexpr size_t CAPACITY = 2 * DIGEST_BIT_LEN; 21 | 22 | // Width of rate portion of the sponge, in bits. 23 | static constexpr size_t RATE = 1600 - CAPACITY; 24 | 25 | // Domain separator bits, used for finalization. 26 | static constexpr uint8_t DOM_SEP = 0b00000010; 27 | 28 | // Bit-width of domain separator, starting from least significant bit. 29 | static constexpr size_t DOM_SEP_BW = std::bit_width(DOM_SEP); 30 | 31 | /** 32 | * Given arbitrary many input message bytes, this routine consumes it into keccak[1024] sponge state and squeezes out 64 -bytes digest. 33 | * 34 | * See SHA3 hash function definition in section 6.1 of SHA3 specification https://dx.doi.org/10.6028/NIST.FIPS.202. 35 | */ 36 | struct sha3_512_t 37 | { 38 | private: 39 | uint64_t state[keccak::LANE_CNT]{}; 40 | size_t offset = 0; 41 | alignas(4) bool finalized = false; 42 | alignas(4) bool squeezed = false; 43 | 44 | public: 45 | // Constructor 46 | forceinline constexpr sha3_512_t() = default; 47 | 48 | /** 49 | * Given an arbitrary length message, absorbs it all in the SHA3_512 hasher and returns a 64 -bytes message digest. 50 | * This is the oneshot hashing API. For working with longer message stream, prefer using `absorb() -> finalize() -> digest()`. 51 | */ 52 | forceinline static constexpr std::array hash(std::span msg) 53 | { 54 | std::array md{ 0 }; 55 | 56 | sha3_512_t hasher; 57 | hasher.absorb(msg); 58 | hasher.finalize(); 59 | hasher.digest(md); 60 | 61 | return md; 62 | } 63 | 64 | /* 65 | * Given N (>=0) -bytes message as input, this routine can be invoked arbitrary many times ( until the sponge is 66 | * finalized ), each time absorbing arbitrary many message bytes into RATE portion of the sponge. 67 | */ 68 | forceinline constexpr void absorb(std::span msg) 69 | { 70 | if (!finalized) { 71 | sponge::absorb(state, offset, msg); 72 | } 73 | } 74 | 75 | /** 76 | * Finalizes the sponge after all message bytes are absorbed into it, now it should be ready for squeezing message 77 | * digest bytes. Once finalized, you can't absorb any message bytes into sponge. After finalization, calling this 78 | * function again and again doesn't mutate anything. 79 | */ 80 | forceinline constexpr void finalize() 81 | { 82 | if (!finalized) { 83 | sponge::finalize(state, offset); 84 | finalized = true; 85 | } 86 | } 87 | 88 | /** 89 | * After sponge state is finalized, 64 message digest bytes can be squeezed by calling this function. 90 | * Once digest bytes are squeezed, calling this function again and again returns nothing. 91 | */ 92 | forceinline constexpr void digest(std::span md) 93 | { 94 | if (finalized && !squeezed) { 95 | size_t squeezable = RATE / std::numeric_limits::digits; 96 | sponge::squeeze(state, squeezable, md); 97 | 98 | squeezed = true; 99 | } 100 | } 101 | 102 | /** 103 | * Reset the internal state of the SHA3-512 hasher. 104 | * Now it can again be used for another `absorb() -> finalize() -> digest()` cycle. 105 | */ 106 | forceinline constexpr void reset() 107 | { 108 | std::fill(std::begin(state), std::end(state), 0); 109 | offset = 0; 110 | finalized = false; 111 | squeezed = false; 112 | } 113 | }; 114 | 115 | } 116 | -------------------------------------------------------------------------------- /include/sha3/sha3_256.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/sponge.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // SHA3-256 Hash Function : Keccak[512](M || 01, 256) 8 | namespace sha3_256 { 9 | 10 | // Number of rounds keccak-p[1600] is applied. 11 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS; 12 | 13 | // Bit length of SHA3-256 message digest. 14 | static constexpr size_t DIGEST_BIT_LEN = 256; 15 | 16 | // Byte length of SHA3-256 message digest. 17 | static constexpr size_t DIGEST_LEN = DIGEST_BIT_LEN / std::numeric_limits::digits; 18 | 19 | // Width of capacity portion of the sponge, in bits. 20 | static constexpr size_t CAPACITY = 2 * DIGEST_BIT_LEN; 21 | 22 | // Width of rate portion of the sponge, in bits. 23 | static constexpr size_t RATE = 1600 - CAPACITY; 24 | 25 | // Domain separator bits, used for finalization. 26 | static constexpr uint8_t DOM_SEP = 0b00000010; 27 | 28 | // Bit-width of domain separator, starting from least significant bit. 29 | static constexpr size_t DOM_SEP_BW = std::bit_width(DOM_SEP); 30 | 31 | /** 32 | * Given arbitrary many input message bytes, this routine consumes it into keccak[512] sponge state and squeezes out 32 -bytes digest. 33 | * 34 | * See SHA3 hash function definition in section 6.1 of SHA3 specification https://dx.doi.org/10.6028/NIST.FIPS.202. 35 | */ 36 | struct sha3_256_t 37 | { 38 | private: 39 | uint64_t state[keccak::LANE_CNT]{}; 40 | size_t offset = 0; 41 | alignas(4) bool finalized = false; 42 | alignas(4) bool squeezed = false; 43 | 44 | public: 45 | // Constructor 46 | forceinline constexpr sha3_256_t() = default; 47 | 48 | /** 49 | * Given an arbitrary length message, absorbs it all in the SHA3_256 hasher and returns a 32 -bytes message digest. 50 | * This is the oneshot hashing API. For working with longer message stream, prefer using `absorb() -> finalize() -> digest()`. 51 | */ 52 | forceinline static constexpr std::array hash(std::span msg) 53 | { 54 | std::array md{ 0 }; 55 | 56 | sha3_256_t hasher; 57 | hasher.absorb(msg); 58 | hasher.finalize(); 59 | hasher.digest(md); 60 | 61 | return md; 62 | } 63 | 64 | /** 65 | * Given N (>=0) -bytes message as input, this routine can be invoked arbitrary many times ( until the sponge is 66 | * finalized ), each time absorbing arbitrary many message bytes into RATE portion of the sponge. 67 | */ 68 | forceinline constexpr void absorb(std::span msg) 69 | { 70 | if (!finalized) { 71 | sponge::absorb(state, offset, msg); 72 | } 73 | } 74 | 75 | /** 76 | * Finalizes the sponge after all message bytes are absorbed into it, now it should be ready for squeezing message 77 | * digest bytes. Once finalized, you can't absorb any message bytes into sponge. After finalization, calling this 78 | * function again and again doesn't mutate anything. 79 | */ 80 | forceinline constexpr void finalize() 81 | { 82 | if (!finalized) { 83 | sponge::finalize(state, offset); 84 | finalized = true; 85 | } 86 | } 87 | 88 | /** 89 | * After sponge state is finalized, 32 message digest (MD) bytes can be squeezed by calling this function. 90 | * Once digest bytes are squeezed, calling this function again and again returns nothing. 91 | */ 92 | forceinline constexpr void digest(std::span md) 93 | { 94 | if (finalized && !squeezed) { 95 | size_t squeezable = RATE / std::numeric_limits::digits; 96 | sponge::squeeze(state, squeezable, md); 97 | 98 | squeezed = true; 99 | } 100 | } 101 | 102 | /** 103 | * Reset the internal state of the SHA3-256 hasher. 104 | * Now it can again be used for another `absorb() -> finalize() -> digest()` cycle. 105 | */ 106 | forceinline constexpr void reset() 107 | { 108 | std::fill(std::begin(state), std::end(state), 0); 109 | offset = 0; 110 | finalized = false; 111 | squeezed = false; 112 | } 113 | }; 114 | 115 | } 116 | -------------------------------------------------------------------------------- /include/sha3/sha3_384.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/sponge.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | // SHA3-384 Hash Function : Keccak[768](M || 01, 384) 8 | namespace sha3_384 { 9 | 10 | // Number of rounds keccak-p[1600] is applied. 11 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS; 12 | 13 | // Bit length of SHA3-384 message digest. 14 | static constexpr size_t DIGEST_BIT_LEN = 384; 15 | 16 | // Byte length of SHA3-384 message digest. 17 | static constexpr size_t DIGEST_LEN = DIGEST_BIT_LEN / std::numeric_limits::digits; 18 | 19 | // Width of capacity portion of the sponge, in bits. 20 | static constexpr size_t CAPACITY = 2 * DIGEST_BIT_LEN; 21 | 22 | // Width of rate portion of the sponge, in bits. 23 | static constexpr size_t RATE = 1600 - CAPACITY; 24 | 25 | // Domain separator bits, used for finalization. 26 | static constexpr uint8_t DOM_SEP = 0b00000010; 27 | 28 | // Bit-width of domain separator, starting from least significant bit. 29 | static constexpr size_t DOM_SEP_BW = std::bit_width(DOM_SEP); 30 | 31 | /** 32 | * Given arbitrary many input message bytes, this routine consumes it into keccak[768] sponge state and squeezes out 48 -bytes digest. 33 | * 34 | * See SHA3 hash function definition in section 6.1 of SHA3 specification https://dx.doi.org/10.6028/NIST.FIPS.202. 35 | */ 36 | struct sha3_384_t 37 | { 38 | private: 39 | uint64_t state[keccak::LANE_CNT]{}; 40 | size_t offset = 0; 41 | alignas(4) bool finalized = false; 42 | alignas(4) bool squeezed = false; 43 | 44 | public: 45 | // Constructor 46 | forceinline constexpr sha3_384_t() = default; 47 | 48 | /** 49 | * Given an arbitrary length message, absorbs it all in the SHA3_384 hasher and returns a 48 -bytes message digest. 50 | * This is the oneshot hashing API. For working with longer message stream, prefer using `absorb() -> finalize() -> digest()`. 51 | */ 52 | forceinline static constexpr std::array hash(std::span msg) 53 | { 54 | std::array md{ 0 }; 55 | 56 | sha3_384_t hasher; 57 | hasher.absorb(msg); 58 | hasher.finalize(); 59 | hasher.digest(md); 60 | 61 | return md; 62 | } 63 | 64 | /** 65 | * Given N (>=0) -bytes message as input, this routine can be invoked arbitrary many times ( until the sponge is 66 | * finalized ), each time absorbing arbitrary many message bytes into RATE portion of the sponge. 67 | */ 68 | forceinline constexpr void absorb(std::span msg) 69 | { 70 | if (!finalized) { 71 | sponge::absorb(state, offset, msg); 72 | } 73 | } 74 | 75 | /** 76 | * Finalizes the sponge after all message bytes are absorbed into it, now it should be ready for squeezing message 77 | * digest bytes. Once finalized, you can't absorb any message bytes into sponge. After finalization, calling this 78 | * function again and again doesn't mutate anything. 79 | */ 80 | forceinline constexpr void finalize() 81 | { 82 | if (!finalized) { 83 | sponge::finalize(state, offset); 84 | finalized = true; 85 | } 86 | } 87 | 88 | /** 89 | * After sponge state is finalized, 48 message digest (MD) bytes can be squeezed by calling this function. 90 | * Once digest bytes are squeezed, calling this function again and again returns nothing. 91 | */ 92 | forceinline constexpr void digest(std::span md) 93 | { 94 | if (finalized && !squeezed) { 95 | size_t squeezable = RATE / std::numeric_limits::digits; 96 | sponge::squeeze(state, squeezable, md); 97 | 98 | squeezed = true; 99 | } 100 | } 101 | 102 | /** 103 | * Reset the internal state of the SHA3-384 hasher. 104 | * Now it can again be used for another `absorb() -> finalize() -> digest()` cycle. 105 | */ 106 | forceinline constexpr void reset() 107 | { 108 | std::fill(std::begin(state), std::end(state), 0); 109 | offset = 0; 110 | finalized = false; 111 | squeezed = false; 112 | } 113 | }; 114 | 115 | } 116 | -------------------------------------------------------------------------------- /include/sha3/sha3_224.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/force_inline.hpp" 3 | #include "sha3/internals/keccak.hpp" 4 | #include "sha3/internals/sponge.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // SHA3-224 Hash Function : Keccak[448](M || 01, 224) 11 | namespace sha3_224 { 12 | 13 | // Number of rounds keccak-p[1600] is applied. 14 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS; 15 | 16 | // Bit length of SHA3-224 message digest. 17 | static constexpr size_t DIGEST_BIT_LEN = 224; 18 | 19 | // Byte length of SHA3-224 message digest. 20 | static constexpr size_t DIGEST_LEN = DIGEST_BIT_LEN / std::numeric_limits::digits; 21 | 22 | // Width of capacity portion of the sponge, in bits. 23 | static constexpr size_t CAPACITY = 2 * DIGEST_BIT_LEN; 24 | 25 | // Width of rate portion of the sponge, in bits. 26 | static constexpr size_t RATE = 1600 - CAPACITY; 27 | 28 | // Domain separator bits, used for finalization. 29 | static constexpr uint8_t DOM_SEP = 0b00000010; 30 | 31 | // Bit-width of domain separator, starting from least significant bit. 32 | static constexpr size_t DOM_SEP_BW = std::bit_width(DOM_SEP); 33 | 34 | /** 35 | * Given arbitrary many input message bytes, this routine consumes it into keccak[448] sponge state and squeezes out 28 -bytes digest. 36 | * 37 | * See SHA3 hash function definition in section 6.1 of SHA3 specification https://dx.doi.org/10.6028/NIST.FIPS.202. 38 | */ 39 | struct sha3_224_t 40 | { 41 | private: 42 | uint64_t state[keccak::LANE_CNT]{}; 43 | size_t offset = 0; 44 | alignas(4) bool finalized = false; 45 | alignas(4) bool squeezed = false; 46 | 47 | public: 48 | // Constructor 49 | forceinline constexpr sha3_224_t() = default; 50 | 51 | /** 52 | * Given an arbitrary length message, absorbs it all in the SHA3_224 hasher and returns a 28 -bytes message digest. 53 | * This is the oneshot hashing API. For working with longer message stream, prefer using `absorb() -> finalize() -> digest()`. 54 | */ 55 | forceinline static constexpr std::array hash(std::span msg) 56 | { 57 | std::array md{ 0 }; 58 | 59 | sha3_224_t hasher; 60 | hasher.absorb(msg); 61 | hasher.finalize(); 62 | hasher.digest(md); 63 | 64 | return md; 65 | } 66 | 67 | /** 68 | * Given N (>=0) -bytes message as input, this routine can be invoked arbitrary many times ( until the sponge is 69 | * finalized ), each time absorbing arbitrary many message bytes into RATE portion of the sponge. 70 | */ 71 | forceinline constexpr void absorb(std::span msg) 72 | { 73 | if (!finalized) { 74 | sponge::absorb(state, offset, msg); 75 | } 76 | } 77 | 78 | /** 79 | * Finalizes the sponge after all message bytes are absorbed into it, now it should be ready for squeezing message 80 | * digest bytes. Once finalized, you can't absorb any message bytes into sponge. After finalization, calling this 81 | * function again and again doesn't mutate anything. 82 | */ 83 | forceinline constexpr void finalize() 84 | { 85 | if (!finalized) { 86 | sponge::finalize(state, offset); 87 | finalized = true; 88 | } 89 | } 90 | 91 | /** 92 | * After sponge state is finalized, 28 message digest (MD) bytes can be squeezed by calling this function. 93 | * Once digest bytes are squeezed, calling this function again and again returns nothing. 94 | */ 95 | forceinline constexpr void digest(std::span md) 96 | { 97 | if (finalized && !squeezed) { 98 | size_t squeezable = RATE / std::numeric_limits::digits; 99 | sponge::squeeze(state, squeezable, md); 100 | 101 | squeezed = true; 102 | } 103 | } 104 | 105 | /** 106 | * Reset the internal state of the SHA3-224 hasher. 107 | * Now it can again be used for another `absorb() -> finalize() -> digest()` cycle. 108 | */ 109 | forceinline constexpr void reset() 110 | { 111 | std::fill(std::begin(state), std::end(state), 0); 112 | offset = 0; 113 | finalized = false; 114 | squeezed = false; 115 | } 116 | }; 117 | 118 | } 119 | -------------------------------------------------------------------------------- /include/sha3/shake256.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/sponge.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | // SHAKE256 Extendable Output Function : Keccak[512](M || 1111, d) 9 | namespace shake256 { 10 | 11 | // Number of rounds keccak-p[1600] is applied. 12 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS; 13 | 14 | // SHAKE256 XOF offers at max 256-bits of security. 15 | static constexpr size_t TARGET_BIT_SECURITY_LEVEL = 256; 16 | 17 | // Width of capacity portion of the sponge, in bits. 18 | static constexpr size_t CAPACITY = 2 * TARGET_BIT_SECURITY_LEVEL; 19 | 20 | // Width of rate portion of the sponge, in bits. 21 | static constexpr size_t RATE = 1600 - CAPACITY; 22 | 23 | // Domain separator bits, used for finalization. 24 | static constexpr uint8_t DOM_SEP = 0b00001111; 25 | 26 | // Bit-width of domain separator, starting from least significant bit. 27 | static constexpr size_t DOM_SEP_BW = std::bit_width(DOM_SEP); 28 | 29 | /** 30 | * SHAKE256 eXtendable Output Function (XOF). 31 | * 32 | * See SHA3 extendable output function definition in section 6.2 of SHA3 specification https://dx.doi.org/10.6028/NIST.FIPS.202. 33 | */ 34 | struct shake256_t 35 | { 36 | private: 37 | uint64_t state[keccak::LANE_CNT]{}; 38 | size_t offset = 0; 39 | alignas(4) bool finalized = false; // all message bytes absorbed ? 40 | size_t squeezable = 0; 41 | 42 | public: 43 | forceinline constexpr shake256_t() = default; 44 | forceinline constexpr size_t squeezable_num_bytes() const { return squeezable; } 45 | 46 | /** 47 | * Given N -many bytes input message, this routine consumes those into keccak[512] sponge state. 48 | * 49 | * Note, this routine can be called arbitrary number of times, each time with arbitrary bytes of input message, until 50 | * keccak[512] state is finalized ( by calling routine with the same name ). Once the sponge is finalized, it can't 51 | * absorb any more message bytes. 52 | */ 53 | forceinline constexpr void absorb(std::span msg) 54 | { 55 | if (!finalized) { 56 | sponge::absorb(state, offset, msg); 57 | } 58 | } 59 | 60 | /** 61 | * After consuming arbitrary many input bytes, this routine is invoked when no more input bytes remaining to be 62 | * consumed by keccak[512] state. 63 | * 64 | * Note, once this routine is called, calling absorb() or finalize() again, on same SHAKE256 object, doesn't do 65 | * anything. After finalization, one might intend to read arbitrary many bytes by squeezing sponge, which is done by 66 | * calling read() function, as many times required. 67 | */ 68 | forceinline constexpr void finalize() 69 | { 70 | if (!finalized) { 71 | sponge::finalize(state, offset); 72 | 73 | finalized = true; 74 | squeezable = RATE / std::numeric_limits::digits; 75 | } 76 | } 77 | 78 | /** 79 | * After sponge state is finalized, arbitrary many output bytes can be squeezed by calling this function 80 | * any number of times required. 81 | */ 82 | forceinline constexpr void squeeze(std::span dig) 83 | { 84 | if (finalized) { 85 | sponge::squeeze(state, squeezable, dig); 86 | } 87 | } 88 | 89 | /** 90 | * Reset the internal state of the SHAKE256 XOF hasher. 91 | * Now it can again be used for another `absorb() -> finalize() -> squeeze()` cycle. 92 | */ 93 | forceinline constexpr void reset() 94 | { 95 | std::fill(std::begin(state), std::end(state), 0); 96 | offset = 0; 97 | finalized = false; 98 | squeezable = 0; 99 | } 100 | 101 | /** 102 | * Given that sponge is already finalized, this routine can be used for zeroizing first n -bytes of 103 | * permutation state s.t. n <= 200 and applying permutation. 104 | */ 105 | forceinline void ratchet(const size_t byte_len) 106 | { 107 | if (finalized) { 108 | const auto ratchetable_portion_byte_len = std::min(byte_len, keccak::STATE_BYTE_LEN); 109 | 110 | auto state_as_bytes = reinterpret_cast(state); 111 | std::memset(state_as_bytes, 0, ratchetable_portion_byte_len); 112 | 113 | keccak::permute(state); 114 | } 115 | } 116 | }; 117 | 118 | } 119 | -------------------------------------------------------------------------------- /tests/test_sha3_512.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/sha3_512.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Eval SHA3-512 hash on statically defined input message during compilation-time. 11 | constexpr std::array 12 | eval_sha3_512() 13 | { 14 | // Statically defined input. 15 | std::array data{}; 16 | std::iota(data.begin(), data.end(), 0); 17 | 18 | // Compute output message digest. 19 | return sha3_512::sha3_512_t::hash(data); 20 | } 21 | 22 | // Ensure that SHA3-512 implementation is compile-time evaluable. 23 | TEST(Sha3Hashing, CompileTimeEvalSha3_512) 24 | { 25 | // Input = 26 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f 27 | // Output = 28 | // 989c1995da9d2d341f993c2e2ca695f3477075061bfbd2cdf0be75cf7ba99fbe33d8d2c4dcc31fa89917786b883e6c9d5b02ed81b7483a4cb3ea98671588f745 29 | 30 | constexpr auto md = eval_sha3_512(); 31 | static_assert(md == std::array{ 152, 156, 25, 149, 218, 157, 45, 52, 31, 153, 60, 46, 44, 166, 149, 243, 32 | 71, 112, 117, 6, 27, 251, 210, 205, 240, 190, 117, 207, 123, 169, 159, 190, 33 | 51, 216, 210, 196, 220, 195, 31, 168, 153, 23, 120, 107, 136, 62, 108, 157, 34 | 91, 2, 237, 129, 183, 72, 58, 76, 179, 234, 152, 103, 21, 136, 247, 69 }, 35 | "Must be able to compute Sha3-512 hash during compile-time !"); 36 | } 37 | 38 | // Test that absorbing same input message bytes using both incremental and one-shot hashing, should yield same output bytes, for SHA3-512 hasher. 39 | TEST(Sha3Hashing, Sha3_512IncrementalAbsorption) 40 | { 41 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 42 | std::vector msg(mlen); 43 | auto msg_span = std::span(msg); 44 | 45 | sha3_test_utils::random_data(msg_span); 46 | 47 | // Oneshot Hashing 48 | auto oneshot_out = sha3_512::sha3_512_t::hash(msg_span); 49 | 50 | // Incremental Hashing 51 | std::array multishot_out{ 0 }; 52 | sha3_512::sha3_512_t hasher; 53 | 54 | size_t off = 0; 55 | while (off < mlen) { 56 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 ! 57 | auto elen = std::min(std::max(msg[off], 1), mlen - off); 58 | 59 | hasher.absorb(msg_span.subspan(off, elen)); 60 | off += elen; 61 | } 62 | 63 | hasher.finalize(); 64 | hasher.digest(multishot_out); 65 | 66 | EXPECT_EQ(oneshot_out, multishot_out); 67 | } 68 | } 69 | 70 | /** 71 | * Ensure that SHA3-512 implementation is conformant with FIPS 202 standard, by using KAT file generated following 72 | * https://gist.github.com/itzmeanjan/448f97f9c49d781a5eb3ddd6ea6e7364. 73 | */ 74 | TEST(Sha3Hashing, Sha3_512KnownAnswerTests) 75 | { 76 | using namespace std::literals; 77 | 78 | const std::string kat_file = "./kats/sha3_512.kat"; 79 | std::fstream file(kat_file); 80 | 81 | while (true) { 82 | std::string len0; 83 | 84 | if (!std::getline(file, len0).eof()) { 85 | std::string msg0; 86 | std::string md0; 87 | 88 | std::getline(file, msg0); 89 | std::getline(file, md0); 90 | 91 | auto msg1 = std::string_view(msg0); 92 | auto md1 = std::string_view(md0); 93 | 94 | auto msg2 = msg1.substr(msg1.find("="sv) + 2, msg1.size()); 95 | auto md2 = md1.substr(md1.find("="sv) + 2, md1.size()); 96 | 97 | auto msg = sha3_test_utils::parse_dynamic_sized_hex_string(msg2); 98 | 99 | auto expected_md = sha3_test_utils::parse_static_sized_hex_string(md2); 100 | auto computed_md = sha3_512::sha3_512_t::hash(msg); 101 | 102 | EXPECT_EQ(computed_md, expected_md); 103 | 104 | std::string empty_line; 105 | std::getline(file, empty_line); 106 | } else { 107 | break; 108 | } 109 | } 110 | 111 | file.close(); 112 | } 113 | -------------------------------------------------------------------------------- /include/sha3/turboshake128.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/keccak.hpp" 3 | #include "sha3/internals/sponge.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // TurboSHAKE128 eXtendable Output Function 12 | namespace turboshake128 { 13 | 14 | // 12 -rounds Keccak-p[1600, 12] is applied. 15 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS / 2; 16 | 17 | // TurboSHAKE128 XOF offers at max 128-bits of security. 18 | static constexpr size_t TARGET_BIT_SECURITY_LEVEL = 128; 19 | 20 | // Width of capacity portion of the sponge, in bits. 21 | static constexpr size_t CAPACITY = 2 * TARGET_BIT_SECURITY_LEVEL; 22 | 23 | // Width of rate portion of the sponge, in bits. 24 | static constexpr size_t RATE = 1600 - CAPACITY; 25 | 26 | /** 27 | * TurboSHAKE128 eXtendable Output Function (XOF). 28 | * 29 | * See TurboSHAKE extendable output function definition in section 2 of RFC 9861 https://datatracker.ietf.org/doc/rfc9861. 30 | */ 31 | struct turboshake128_t 32 | { 33 | private: 34 | uint64_t state[keccak::LANE_CNT]{}; 35 | size_t offset = 0; 36 | alignas(4) bool finalized = false; // all message bytes absorbed ? 37 | size_t squeezable = 0; 38 | 39 | public: 40 | forceinline constexpr turboshake128_t() = default; 41 | forceinline constexpr size_t squeezable_num_bytes() const { return squeezable; } 42 | 43 | /** 44 | * Given N -many bytes input message, this routine consumes those into keccak[256] sponge state. 45 | * 46 | * Note, this routine can be called arbitrary number of times, each time with arbitrary bytes of input message, until 47 | * keccak[256] state is finalized ( by calling routine with similar name ). Once the sponge is finalized, it can't 48 | * absorb any more message bytes. 49 | */ 50 | forceinline constexpr void absorb(std::span msg) 51 | { 52 | if (!finalized) { 53 | sponge::absorb(state, offset, msg); 54 | } 55 | } 56 | 57 | /** 58 | * After consuming arbitrary many input bytes, this routine is invoked when no more input bytes remaining to be consumed by keccak[256] state. 59 | * This function expects a domain separator byte ∈ [0x01, 0x7f]. If not supplied, it will take 0x1f as default value. 60 | * 61 | * Note, once this routine is called, calling absorb() or finalize() again, on same TurboSHAKE128 object, doesn't do 62 | * anything. After finalization, one might intend to read arbitrary many bytes by squeezing sponge, which is done by 63 | * calling squeeze() function, as many times required. 64 | */ 65 | template 66 | forceinline constexpr void finalize() 67 | requires((dom_sep >= 0x01) && (dom_sep <= 0x7f)) 68 | { 69 | if (!finalized) { 70 | sponge::finalize(state, offset); 71 | 72 | finalized = true; 73 | squeezable = RATE / std::numeric_limits::digits; 74 | } 75 | } 76 | 77 | /** 78 | * After sponge state is finalized, arbitrary many output bytes can be squeezed by calling this function 79 | * any number of times required. 80 | */ 81 | forceinline constexpr void squeeze(std::span dig) 82 | { 83 | if (finalized) { 84 | sponge::squeeze(state, squeezable, dig); 85 | } 86 | } 87 | 88 | /** 89 | * Reset the internal state of the TurboSHAKE128 XOF hasher. 90 | * Now it can again be used for another `absorb() -> finalize() -> squeeze()` cycle. 91 | */ 92 | forceinline constexpr void reset() 93 | { 94 | std::fill(std::begin(state), std::end(state), 0); 95 | offset = 0; 96 | finalized = false; 97 | squeezable = 0; 98 | } 99 | 100 | /** 101 | * Given that sponge is already finalized, this routine can be used for zeroizing first n -bytes of 102 | * permutation state s.t. n <= 200 and applying permutation. 103 | */ 104 | forceinline void ratchet(const size_t byte_len) 105 | { 106 | if (finalized) { 107 | const auto ratchetable_portion_byte_len = std::min(byte_len, keccak::STATE_BYTE_LEN); 108 | 109 | auto state_as_bytes = reinterpret_cast(state); 110 | std::memset(state_as_bytes, 0, ratchetable_portion_byte_len); 111 | 112 | keccak::permute(state); 113 | } 114 | } 115 | }; 116 | 117 | } 118 | -------------------------------------------------------------------------------- /include/sha3/turboshake256.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/keccak.hpp" 3 | #include "sha3/internals/sponge.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // TurboSHAKE256 eXtendable Output Function 12 | namespace turboshake256 { 13 | 14 | // 12 -rounds Keccak-p[1600, 12] is applied. 15 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS / 2; 16 | 17 | // TurboSHAKE256 XOF offers at max 256-bits of security. 18 | static constexpr size_t TARGET_BIT_SECURITY_LEVEL = 256; 19 | 20 | // Width of capacity portion of the sponge, in bits. 21 | static constexpr size_t CAPACITY = 2 * TARGET_BIT_SECURITY_LEVEL; 22 | 23 | // Width of rate portion of the sponge, in bits. 24 | static constexpr size_t RATE = 1600 - CAPACITY; 25 | 26 | /** 27 | * TurboSHAKE256 eXtendable Output Function (XOF). 28 | * 29 | * See TurboSHAKE extendable output function definition in section 2 of RFC 9861 https://datatracker.ietf.org/doc/rfc9861. 30 | */ 31 | struct turboshake256_t 32 | { 33 | private: 34 | uint64_t state[keccak::LANE_CNT]{}; 35 | size_t offset = 0; 36 | alignas(4) bool finalized = false; // all message bytes absorbed ? 37 | size_t squeezable = 0; 38 | 39 | public: 40 | forceinline constexpr turboshake256_t() = default; 41 | forceinline constexpr size_t squeezable_num_bytes() const { return squeezable; } 42 | 43 | /** 44 | * Given N -many bytes input message, this routine consumes those into keccak[256] sponge state. 45 | * 46 | * Note, this routine can be called arbitrary number of times, each time with arbitrary bytes of input message, until 47 | * keccak[256] state is finalized ( by calling routine with similar name ). Once the sponge is finalized, it can't 48 | * absorb any more message bytes. 49 | */ 50 | forceinline constexpr void absorb(std::span msg) 51 | { 52 | if (!finalized) { 53 | sponge::absorb(state, offset, msg); 54 | } 55 | } 56 | 57 | /** 58 | * After consuming arbitrary many input bytes, this routine is invoked when no more input bytes remaining to be consumed by keccak[256] state. 59 | * This function expects a domain separator byte ∈ [0x01, 0x7f]. If not supplied, it will take 0x1f as default value. 60 | * 61 | * Note, once this routine is called, calling absorb() or finalize() again, on same TurboSHAKE256 object, doesn't do 62 | * anything. After finalization, one might intend to read arbitrary many bytes by squeezing sponge, which is done by 63 | * calling squeeze() function, as many times required. 64 | */ 65 | template 66 | forceinline constexpr void finalize() 67 | requires((dom_sep >= 0x01) && (dom_sep <= 0x7f)) 68 | { 69 | if (!finalized) { 70 | sponge::finalize(state, offset); 71 | 72 | finalized = true; 73 | squeezable = RATE / std::numeric_limits::digits; 74 | } 75 | } 76 | 77 | /** 78 | * After sponge state is finalized, arbitrary many output bytes can be squeezed by calling this function 79 | * any number of times required. 80 | */ 81 | forceinline constexpr void squeeze(std::span dig) 82 | { 83 | if (finalized) { 84 | sponge::squeeze(state, squeezable, dig); 85 | } 86 | } 87 | 88 | /** 89 | * Reset the internal state of the TurboSHAKE256 XOF hasher. 90 | * Now it can again be used for another `absorb() -> finalize() -> squeeze()` cycle. 91 | */ 92 | forceinline constexpr void reset() 93 | { 94 | std::fill(std::begin(state), std::end(state), 0); 95 | offset = 0; 96 | finalized = false; 97 | squeezable = 0; 98 | } 99 | 100 | /** 101 | * Given that sponge is already finalized, this routine can be used for zeroizing first n -bytes of 102 | * permutation state s.t. n <= 200 and applying permutation. 103 | */ 104 | forceinline void ratchet(const size_t byte_len) 105 | { 106 | if (finalized) { 107 | const auto ratchetable_portion_byte_len = std::min(byte_len, keccak::STATE_BYTE_LEN); 108 | 109 | auto state_as_bytes = reinterpret_cast(state); 110 | std::memset(state_as_bytes, 0, ratchetable_portion_byte_len); 111 | 112 | keccak::permute(state); 113 | } 114 | } 115 | }; 116 | 117 | } 118 | -------------------------------------------------------------------------------- /include/sha3/shake128.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/keccak.hpp" 3 | #include "sha3/internals/sponge.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // SHAKE128 Extendable Output Function : Keccak[256](M || 1111, d) 12 | namespace shake128 { 13 | 14 | // Number of rounds keccak-p[1600] is applied. 15 | static constexpr size_t NUM_KECCAK_ROUNDS = keccak::MAX_NUM_ROUNDS; 16 | 17 | // SHAKE128 XOF offers at max 128-bits of security. 18 | static constexpr size_t TARGET_BIT_SECURITY_LEVEL = 128; 19 | 20 | // Width of capacity portion of the sponge, in bits. 21 | static constexpr size_t CAPACITY = 2 * TARGET_BIT_SECURITY_LEVEL; 22 | 23 | // Width of rate portion of the sponge, in bits. 24 | static constexpr size_t RATE = 1600 - CAPACITY; 25 | 26 | // Domain separator bits, used for finalization. 27 | static constexpr uint8_t DOM_SEP = 0b00001111; 28 | 29 | // Bit-width of domain separator, starting from least significant bit. 30 | static constexpr size_t DOM_SEP_BW = std::bit_width(DOM_SEP); 31 | 32 | /** 33 | * SHAKE128 eXtendable Output Function (XOF). 34 | * 35 | * See SHA3 extendable output function definition in section 6.2 of SHA3 specification https://dx.doi.org/10.6028/NIST.FIPS.202. 36 | */ 37 | struct shake128_t 38 | { 39 | private: 40 | uint64_t state[keccak::LANE_CNT]{}; 41 | size_t offset = 0; 42 | alignas(4) bool finalized = false; // All message bytes absorbed? 43 | size_t squeezable = 0; 44 | 45 | public: 46 | forceinline constexpr shake128_t() = default; 47 | forceinline constexpr size_t squeezable_num_bytes() const { return squeezable; } 48 | 49 | /** 50 | * Given N -many bytes input message, this routine consumes those into keccak[256] sponge state. 51 | * 52 | * Note, this routine can be called arbitrary number of times, each time with arbitrary bytes of input message, until 53 | * keccak[256] state is finalized ( by calling routine with the same name ). Once the sponge is finalized, it can't 54 | * absorb any more message bytes. 55 | */ 56 | forceinline constexpr void absorb(std::span msg) 57 | { 58 | if (!finalized) { 59 | sponge::absorb(state, offset, msg); 60 | } 61 | } 62 | 63 | /** 64 | * After consuming arbitrary many input bytes, this routine is invoked when no more input bytes remaining to be 65 | * consumed by keccak[256] state. 66 | * 67 | * Note, once this routine is called, calling absorb() or finalize() again, on same SHAKE128 object, doesn't do 68 | * anything. After finalization, one might intend to read arbitrary many bytes by squeezing sponge, which is done by 69 | * calling read() function, as many times required. 70 | */ 71 | forceinline constexpr void finalize() 72 | { 73 | if (!finalized) { 74 | sponge::finalize(state, offset); 75 | 76 | finalized = true; 77 | squeezable = RATE / std::numeric_limits::digits; 78 | } 79 | } 80 | 81 | /** 82 | * After sponge state is finalized, arbitrary many output bytes can be squeezed by calling this function 83 | * any number of times required. 84 | */ 85 | forceinline constexpr void squeeze(std::span dig) 86 | { 87 | if (finalized) { 88 | sponge::squeeze(state, squeezable, dig); 89 | } 90 | } 91 | 92 | /** 93 | * Reset the internal state of the SHAKE128 XOF hasher. 94 | * Now it can again be used for another `absorb() -> finalize() -> squeeze()` cycle. 95 | */ 96 | forceinline constexpr void reset() 97 | { 98 | std::fill(std::begin(state), std::end(state), 0); 99 | offset = 0; 100 | finalized = false; 101 | squeezable = 0; 102 | } 103 | 104 | /** 105 | * Given that sponge is already finalized, this routine can be used for zeroizing first n -bytes of 106 | * permutation state s.t. n <= 200 and applying permutation. 107 | */ 108 | forceinline void ratchet(const size_t byte_len) 109 | { 110 | if (finalized) { 111 | const auto ratchetable_portion_byte_len = std::min(byte_len, keccak::STATE_BYTE_LEN); 112 | 113 | auto state_as_bytes = reinterpret_cast(state); 114 | std::memset(state_as_bytes, 0, ratchetable_portion_byte_len); 115 | 116 | keccak::permute(state); 117 | } 118 | } 119 | }; 120 | 121 | } 122 | -------------------------------------------------------------------------------- /tests/test.mk: -------------------------------------------------------------------------------- 1 | ASAN_FLAGS := -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address # From https://clang.llvm.org/docs/AddressSanitizer.html 2 | DEBUG_ASAN_FLAGS := $(DEBUG_FLAGS) $(ASAN_FLAGS) 3 | RELEASE_ASAN_FLAGS := -g $(RELEASE_FLAGS) $(ASAN_FLAGS) 4 | UBSAN_FLAGS := -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=undefined # From https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html 5 | DEBUG_UBSAN_FLAGS := $(DEBUG_FLAGS) $(UBSAN_FLAGS) 6 | RELEASE_UBSAN_FLAGS := -g $(RELEASE_FLAGS) $(UBSAN_FLAGS) 7 | 8 | TEST_BUILD_DIR := $(BUILD_DIR)/test 9 | ASAN_BUILD_DIR := $(BUILD_DIR)/asan 10 | DEBUG_ASAN_BUILD_DIR := $(ASAN_BUILD_DIR)/debug 11 | RELEASE_ASAN_BUILD_DIR := $(ASAN_BUILD_DIR)/release 12 | UBSAN_BUILD_DIR := $(BUILD_DIR)/ubsan 13 | DEBUG_UBSAN_BUILD_DIR := $(UBSAN_BUILD_DIR)/debug 14 | RELEASE_UBSAN_BUILD_DIR := $(UBSAN_BUILD_DIR)/release 15 | 16 | TEST_DIR := tests 17 | TEST_SOURCES := $(wildcard $(TEST_DIR)/*.cpp) 18 | TEST_HEADERS := $(wildcard $(TEST_DIR)/*.hpp) 19 | TEST_OBJECTS := $(addprefix $(TEST_BUILD_DIR)/, $(notdir $(TEST_SOURCES:.cpp=.o))) 20 | TEST_BINARY := $(TEST_BUILD_DIR)/test.out 21 | TEST_LINK_FLAGS := -lgtest -lgtest_main 22 | GTEST_PARALLEL := ./gtest-parallel/gtest-parallel 23 | 24 | DEBUG_ASAN_TEST_OBJECTS := $(addprefix $(DEBUG_ASAN_BUILD_DIR)/, $(notdir $(TEST_SOURCES:.cpp=.o))) 25 | RELEASE_ASAN_TEST_OBJECTS := $(addprefix $(RELEASE_ASAN_BUILD_DIR)/, $(notdir $(TEST_SOURCES:.cpp=.o))) 26 | DEBUG_ASAN_TEST_BINARY := $(DEBUG_ASAN_BUILD_DIR)/test.out 27 | RELEASE_ASAN_TEST_BINARY := $(RELEASE_ASAN_BUILD_DIR)/test.out 28 | DEBUG_UBSAN_TEST_OBJECTS := $(addprefix $(DEBUG_UBSAN_BUILD_DIR)/, $(notdir $(TEST_SOURCES:.cpp=.o))) 29 | RELEASE_UBSAN_TEST_OBJECTS := $(addprefix $(RELEASE_UBSAN_BUILD_DIR)/, $(notdir $(TEST_SOURCES:.cpp=.o))) 30 | DEBUG_UBSAN_TEST_BINARY := $(DEBUG_UBSAN_BUILD_DIR)/test.out 31 | RELEASE_UBSAN_TEST_BINARY := $(RELEASE_UBSAN_BUILD_DIR)/test.out 32 | 33 | $(DEBUG_ASAN_BUILD_DIR): 34 | mkdir -p $@ 35 | 36 | $(RELEASE_ASAN_BUILD_DIR): 37 | mkdir -p $@ 38 | 39 | $(DEBUG_UBSAN_BUILD_DIR): 40 | mkdir -p $@ 41 | 42 | $(RELEASE_UBSAN_BUILD_DIR): 43 | mkdir -p $@ 44 | 45 | $(TEST_BUILD_DIR): 46 | mkdir -p $@ 47 | 48 | $(TEST_BUILD_DIR)/%.o: $(TEST_DIR)/%.cpp $(TEST_BUILD_DIR) 49 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(RELEASE_FLAGS) $(I_FLAGS) -c $< -o $@ 50 | 51 | $(DEBUG_ASAN_BUILD_DIR)/%.o: $(TEST_DIR)/%.cpp $(DEBUG_ASAN_BUILD_DIR) 52 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(DEBUG_ASAN_FLAGS) $(I_FLAGS) -c $< -o $@ 53 | 54 | $(RELEASE_ASAN_BUILD_DIR)/%.o: $(TEST_DIR)/%.cpp $(RELEASE_ASAN_BUILD_DIR) 55 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(RELEASE_ASAN_FLAGS) $(I_FLAGS) -c $< -o $@ 56 | 57 | $(DEBUG_UBSAN_BUILD_DIR)/%.o: $(TEST_DIR)/%.cpp $(DEBUG_UBSAN_BUILD_DIR) 58 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(DEBUG_UBSAN_FLAGS) $(I_FLAGS) -c $< -o $@ 59 | 60 | $(RELEASE_UBSAN_BUILD_DIR)/%.o: $(TEST_DIR)/%.cpp $(RELEASE_UBSAN_BUILD_DIR) 61 | $(CXX) $(CXX_DEFS) $(CXX_FLAGS) $(WARN_FLAGS) $(RELEASE_UBSAN_FLAGS) $(I_FLAGS) -c $< -o $@ 62 | 63 | $(TEST_BINARY): $(TEST_OBJECTS) 64 | $(CXX) $(RELEASE_FLAGS) $(LINK_OPT_FLAGS) $^ $(TEST_LINK_FLAGS) -o $@ 65 | 66 | $(DEBUG_ASAN_TEST_BINARY): $(DEBUG_ASAN_TEST_OBJECTS) 67 | $(CXX) $(DEBUG_ASAN_FLAGS) $^ $(TEST_LINK_FLAGS) -o $@ 68 | 69 | $(RELEASE_ASAN_TEST_BINARY): $(RELEASE_ASAN_TEST_OBJECTS) 70 | $(CXX) $(RELEASE_ASAN_FLAGS) $^ $(TEST_LINK_FLAGS) -o $@ 71 | 72 | $(DEBUG_UBSAN_TEST_BINARY): $(DEBUG_UBSAN_TEST_OBJECTS) 73 | $(CXX) $(DEBUG_UBSAN_FLAGS) $^ $(TEST_LINK_FLAGS) -o $@ 74 | 75 | $(RELEASE_UBSAN_TEST_BINARY): $(RELEASE_UBSAN_TEST_OBJECTS) 76 | $(CXX) $(RELEASE_UBSAN_FLAGS) $^ $(TEST_LINK_FLAGS) -o $@ 77 | 78 | test: $(TEST_BINARY) $(GTEST_PARALLEL) ## Build and run all tests in RELEASE mode 79 | $(GTEST_PARALLEL) $< --print_test_times 80 | 81 | debug_asan_test: $(DEBUG_ASAN_TEST_BINARY) $(GTEST_PARALLEL) ## Build and run all tests in DEBUG mode, with Address Sanitizer 82 | $(GTEST_PARALLEL) $< --print_test_times 83 | 84 | release_asan_test: $(RELEASE_ASAN_TEST_BINARY) $(GTEST_PARALLEL) ## Build and run all tests in RELEASE mode, with Address Sanitizer 85 | $(GTEST_PARALLEL) $< --print_test_times 86 | 87 | debug_ubsan_test: $(DEBUG_UBSAN_TEST_BINARY) $(GTEST_PARALLEL) ## Build and run all tests in DEBUG mode, with Undefined Behavior Sanitizer 88 | $(GTEST_PARALLEL) $< --print_test_times 89 | 90 | release_ubsan_test: $(RELEASE_UBSAN_TEST_BINARY) $(GTEST_PARALLEL) ## Build and run all tests in RELEASE mode, with Undefined Behavior Sanitizer 91 | $(GTEST_PARALLEL) $< --print_test_times 92 | -------------------------------------------------------------------------------- /benches/bench_xof.cpp: -------------------------------------------------------------------------------- 1 | #include "bench_common.hpp" 2 | #include "sha3/shake128.hpp" 3 | #include "sha3/shake256.hpp" 4 | #include "sha3/turboshake128.hpp" 5 | #include "sha3/turboshake256.hpp" 6 | #include 7 | 8 | /** 9 | * Benchmarks SHAKE128 extendable output function with variable length input and squeezed output. 10 | * 11 | * Note, all input bytes are absorbed in a single call to `absorb` function. 12 | * And all output bytes are squeezed in a single call to `squeeze` function. 13 | */ 14 | void 15 | bench_shake128(benchmark::State& state) 16 | { 17 | const size_t mlen = static_cast(state.range(0)); 18 | const size_t olen = static_cast(state.range(1)); 19 | 20 | std::vector msg(mlen); 21 | std::vector out(olen); 22 | 23 | random_data(msg); 24 | 25 | for (auto _ : state) { 26 | shake128::shake128_t hasher; 27 | hasher.absorb(msg); 28 | hasher.finalize(); 29 | hasher.squeeze(out); 30 | 31 | benchmark::DoNotOptimize(hasher); 32 | benchmark::DoNotOptimize(msg); 33 | benchmark::DoNotOptimize(out); 34 | benchmark::ClobberMemory(); 35 | } 36 | 37 | const size_t bytes_processed = state.iterations() * (msg.size() + out.size()); 38 | state.SetBytesProcessed(bytes_processed); 39 | 40 | #ifdef CYCLES_PER_BYTE 41 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 42 | #endif 43 | } 44 | 45 | /** 46 | * Benchmarks SHAKE256 extendable output function with variable length input and squeezed output. 47 | * 48 | * Note, all input bytes are absorbed in a single call to `absorb` function. 49 | * And all output bytes are squeezed in a single call to `squeeze` function. 50 | */ 51 | void 52 | bench_shake256(benchmark::State& state) 53 | { 54 | const size_t mlen = static_cast(state.range(0)); 55 | const size_t olen = static_cast(state.range(1)); 56 | 57 | std::vector msg(mlen); 58 | std::vector out(olen); 59 | 60 | random_data(msg); 61 | 62 | for (auto _ : state) { 63 | shake256::shake256_t hasher; 64 | hasher.absorb(msg); 65 | hasher.finalize(); 66 | hasher.squeeze(out); 67 | 68 | benchmark::DoNotOptimize(hasher); 69 | benchmark::DoNotOptimize(msg); 70 | benchmark::DoNotOptimize(out); 71 | benchmark::ClobberMemory(); 72 | } 73 | 74 | const size_t bytes_processed = state.iterations() * (msg.size() + out.size()); 75 | state.SetBytesProcessed(bytes_processed); 76 | 77 | #ifdef CYCLES_PER_BYTE 78 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 79 | #endif 80 | } 81 | 82 | /** 83 | * Benchmarks TurboSHAKE128 extendable output function with variable length input and squeezed output. 84 | * 85 | * Note, all input bytes are absorbed in a single call to `absorb` function. 86 | * And all output bytes are squeezed in a single call to `squeeze` function. 87 | */ 88 | void 89 | bench_turboshake128(benchmark::State& state) 90 | { 91 | const size_t mlen = static_cast(state.range(0)); 92 | const size_t olen = static_cast(state.range(1)); 93 | 94 | std::vector msg(mlen); 95 | std::vector out(olen); 96 | 97 | random_data(msg); 98 | 99 | for (auto _ : state) { 100 | turboshake128::turboshake128_t hasher; 101 | hasher.absorb(msg); 102 | hasher.finalize(); 103 | hasher.squeeze(out); 104 | 105 | benchmark::DoNotOptimize(hasher); 106 | benchmark::DoNotOptimize(msg); 107 | benchmark::DoNotOptimize(out); 108 | benchmark::ClobberMemory(); 109 | } 110 | 111 | const size_t bytes_processed = state.iterations() * (msg.size() + out.size()); 112 | state.SetBytesProcessed(bytes_processed); 113 | 114 | #ifdef CYCLES_PER_BYTE 115 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 116 | #endif 117 | } 118 | 119 | /** 120 | * Benchmarks TurboSHAKE256 extendable output function with variable length input and squeezed output. 121 | * 122 | * Note, all input bytes are absorbed in a single call to `absorb` function. 123 | * And all output bytes are squeezed in a single call to `squeeze` function. 124 | */ 125 | void 126 | bench_turboshake256(benchmark::State& state) 127 | { 128 | const size_t mlen = static_cast(state.range(0)); 129 | const size_t olen = static_cast(state.range(1)); 130 | 131 | std::vector msg(mlen); 132 | std::vector out(olen); 133 | 134 | random_data(msg); 135 | 136 | for (auto _ : state) { 137 | turboshake256::turboshake256_t hasher; 138 | hasher.absorb(msg); 139 | hasher.finalize(); 140 | hasher.squeeze(out); 141 | 142 | benchmark::DoNotOptimize(hasher); 143 | benchmark::DoNotOptimize(msg); 144 | benchmark::DoNotOptimize(out); 145 | benchmark::ClobberMemory(); 146 | } 147 | 148 | const size_t bytes_processed = state.iterations() * (msg.size() + out.size()); 149 | state.SetBytesProcessed(bytes_processed); 150 | 151 | #ifdef CYCLES_PER_BYTE 152 | state.counters["CYCLES/ BYTE"] = state.counters["CYCLES"] / bytes_processed; 153 | #endif 154 | } 155 | 156 | BENCHMARK(bench_shake128) 157 | ->ArgsProduct({ benchmark::CreateRange(64, 16384, 4), { 64 } }) 158 | ->Name("shake128") 159 | ->ComputeStatistics("min", compute_min) 160 | ->ComputeStatistics("max", compute_max); 161 | BENCHMARK(bench_shake256) 162 | ->ArgsProduct({ benchmark::CreateRange(64, 16384, 4), { 64 } }) 163 | ->Name("shake256") 164 | ->ComputeStatistics("min", compute_min) 165 | ->ComputeStatistics("max", compute_max); 166 | BENCHMARK(bench_turboshake128) 167 | ->ArgsProduct({ benchmark::CreateRange(64, 16384, 4), { 64 } }) 168 | ->Name("turboshake128") 169 | ->ComputeStatistics("min", compute_min) 170 | ->ComputeStatistics("max", compute_max); 171 | BENCHMARK(bench_turboshake256) 172 | ->ArgsProduct({ benchmark::CreateRange(64, 16384, 4), { 64 } }) 173 | ->Name("turboshake256") 174 | ->ComputeStatistics("min", compute_min) 175 | ->ComputeStatistics("max", compute_max); 176 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: Mozilla 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveAssignments: 8 | Enabled: false 9 | AcrossEmptyLines: false 10 | AcrossComments: false 11 | AlignCompound: false 12 | PadOperators: true 13 | AlignConsecutiveBitFields: 14 | Enabled: false 15 | AcrossEmptyLines: false 16 | AcrossComments: false 17 | AlignCompound: false 18 | PadOperators: false 19 | AlignConsecutiveDeclarations: 20 | Enabled: false 21 | AcrossEmptyLines: false 22 | AcrossComments: false 23 | AlignCompound: false 24 | PadOperators: false 25 | AlignConsecutiveMacros: 26 | Enabled: false 27 | AcrossEmptyLines: false 28 | AcrossComments: false 29 | AlignCompound: false 30 | PadOperators: false 31 | AlignEscapedNewlines: Right 32 | AlignOperands: Align 33 | AlignTrailingComments: 34 | Kind: Always 35 | OverEmptyLines: 0 36 | AllowAllArgumentsOnNextLine: true 37 | AllowAllParametersOfDeclarationOnNextLine: false 38 | AllowShortBlocksOnASingleLine: Never 39 | AllowShortCaseLabelsOnASingleLine: false 40 | AllowShortEnumsOnASingleLine: true 41 | AllowShortFunctionsOnASingleLine: Inline 42 | AllowShortIfStatementsOnASingleLine: Never 43 | AllowShortLambdasOnASingleLine: All 44 | AllowShortLoopsOnASingleLine: false 45 | AlwaysBreakAfterDefinitionReturnType: TopLevel 46 | AlwaysBreakAfterReturnType: TopLevel 47 | AlwaysBreakBeforeMultilineStrings: false 48 | AlwaysBreakTemplateDeclarations: Yes 49 | AttributeMacros: 50 | - __capability 51 | BinPackArguments: false 52 | BinPackParameters: false 53 | BitFieldColonSpacing: Both 54 | BraceWrapping: 55 | AfterCaseLabel: false 56 | AfterClass: true 57 | AfterControlStatement: Never 58 | AfterEnum: true 59 | AfterExternBlock: true 60 | AfterFunction: true 61 | AfterNamespace: false 62 | AfterObjCDeclaration: false 63 | AfterStruct: true 64 | AfterUnion: true 65 | BeforeCatch: false 66 | BeforeElse: false 67 | BeforeLambdaBody: false 68 | BeforeWhile: false 69 | IndentBraces: false 70 | SplitEmptyFunction: true 71 | SplitEmptyRecord: false 72 | SplitEmptyNamespace: true 73 | BreakAfterAttributes: Never 74 | BreakAfterJavaFieldAnnotations: false 75 | BreakArrays: true 76 | BreakBeforeBinaryOperators: None 77 | BreakBeforeConceptDeclarations: Always 78 | BreakBeforeBraces: Mozilla 79 | BreakBeforeInlineASMColon: OnlyMultiline 80 | BreakBeforeTernaryOperators: true 81 | BreakConstructorInitializers: BeforeComma 82 | BreakInheritanceList: BeforeComma 83 | BreakStringLiterals: true 84 | ColumnLimit: 160 85 | CommentPragmas: '^ IWYU pragma:' 86 | CompactNamespaces: false 87 | ConstructorInitializerIndentWidth: 2 88 | ContinuationIndentWidth: 2 89 | Cpp11BracedListStyle: false 90 | DerivePointerAlignment: false 91 | DisableFormat: false 92 | EmptyLineAfterAccessModifier: Never 93 | EmptyLineBeforeAccessModifier: LogicalBlock 94 | ExperimentalAutoDetectBinPacking: false 95 | FixNamespaceComments: false 96 | ForEachMacros: 97 | - foreach 98 | - Q_FOREACH 99 | - BOOST_FOREACH 100 | IfMacros: 101 | - KJ_IF_MAYBE 102 | IncludeBlocks: Preserve 103 | IncludeCategories: 104 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 105 | Priority: 2 106 | SortPriority: 0 107 | CaseSensitive: false 108 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 109 | Priority: 3 110 | SortPriority: 0 111 | CaseSensitive: false 112 | - Regex: '.*' 113 | Priority: 1 114 | SortPriority: 0 115 | CaseSensitive: false 116 | IncludeIsMainRegex: '(Test)?$' 117 | IncludeIsMainSourceRegex: '' 118 | IndentAccessModifiers: false 119 | IndentCaseBlocks: false 120 | IndentCaseLabels: true 121 | IndentExternBlock: AfterExternBlock 122 | IndentGotoLabels: true 123 | IndentPPDirectives: None 124 | IndentRequiresClause: true 125 | IndentWidth: 2 126 | IndentWrappedFunctionNames: false 127 | InsertBraces: false 128 | InsertNewlineAtEOF: false 129 | InsertTrailingCommas: None 130 | IntegerLiteralSeparator: 131 | Binary: 0 132 | BinaryMinDigits: 0 133 | Decimal: 0 134 | DecimalMinDigits: 0 135 | Hex: 0 136 | HexMinDigits: 0 137 | JavaScriptQuotes: Leave 138 | JavaScriptWrapImports: true 139 | KeepEmptyLinesAtTheStartOfBlocks: true 140 | LambdaBodyIndentation: Signature 141 | LineEnding: DeriveLF 142 | MacroBlockBegin: '' 143 | MacroBlockEnd: '' 144 | MaxEmptyLinesToKeep: 1 145 | NamespaceIndentation: None 146 | ObjCBinPackProtocolList: Auto 147 | ObjCBlockIndentWidth: 2 148 | ObjCBreakBeforeNestedBlockParam: true 149 | ObjCSpaceAfterProperty: true 150 | ObjCSpaceBeforeProtocolList: false 151 | PackConstructorInitializers: BinPack 152 | PenaltyBreakAssignment: 2 153 | PenaltyBreakBeforeFirstCallParameter: 19 154 | PenaltyBreakComment: 300 155 | PenaltyBreakFirstLessLess: 120 156 | PenaltyBreakOpenParenthesis: 0 157 | PenaltyBreakString: 1000 158 | PenaltyBreakTemplateDeclaration: 10 159 | PenaltyExcessCharacter: 1000000 160 | PenaltyIndentedWhitespace: 0 161 | PenaltyReturnTypeOnItsOwnLine: 200 162 | PointerAlignment: Left 163 | PPIndentWidth: -1 164 | QualifierAlignment: Leave 165 | ReferenceAlignment: Pointer 166 | ReflowComments: true 167 | RemoveBracesLLVM: false 168 | RemoveSemicolon: false 169 | RequiresClausePosition: OwnLine 170 | RequiresExpressionIndentation: OuterScope 171 | SeparateDefinitionBlocks: Leave 172 | ShortNamespaceLines: 1 173 | SortIncludes: CaseSensitive 174 | SortJavaStaticImport: Before 175 | SortUsingDeclarations: LexicographicNumeric 176 | SpaceAfterCStyleCast: false 177 | SpaceAfterLogicalNot: false 178 | SpaceAfterTemplateKeyword: false 179 | SpaceAroundPointerQualifiers: Default 180 | SpaceBeforeAssignmentOperators: true 181 | SpaceBeforeCaseColon: false 182 | SpaceBeforeCpp11BracedList: false 183 | SpaceBeforeCtorInitializerColon: true 184 | SpaceBeforeInheritanceColon: true 185 | SpaceBeforeParens: ControlStatements 186 | SpaceBeforeParensOptions: 187 | AfterControlStatements: true 188 | AfterForeachMacros: true 189 | AfterFunctionDefinitionName: false 190 | AfterFunctionDeclarationName: false 191 | AfterIfMacros: true 192 | AfterOverloadedOperator: false 193 | AfterRequiresInClause: false 194 | AfterRequiresInExpression: false 195 | BeforeNonEmptyParentheses: false 196 | SpaceBeforeRangeBasedForLoopColon: true 197 | SpaceBeforeSquareBrackets: false 198 | SpaceInEmptyBlock: false 199 | SpaceInEmptyParentheses: false 200 | SpacesBeforeTrailingComments: 1 201 | SpacesInAngles: Never 202 | SpacesInConditionalStatement: false 203 | SpacesInContainerLiterals: true 204 | SpacesInCStyleCastParentheses: false 205 | SpacesInLineCommentPrefix: 206 | Minimum: 1 207 | Maximum: -1 208 | SpacesInParentheses: false 209 | SpacesInSquareBrackets: false 210 | Standard: Latest 211 | StatementAttributeLikeMacros: 212 | - Q_EMIT 213 | StatementMacros: 214 | - Q_UNUSED 215 | - QT_REQUIRE_VERSION 216 | TabWidth: 8 217 | UseTab: Never 218 | WhitespaceSensitiveMacros: 219 | - BOOST_PP_STRINGIZE 220 | - CF_SWIFT_NAME 221 | - NS_SWIFT_NAME 222 | - PP_STRINGIZE 223 | - STRINGIZE 224 | ... 225 | 226 | -------------------------------------------------------------------------------- /include/sha3/internals/sponge.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/force_inline.hpp" 3 | #include "sha3/internals/keccak.hpp" 4 | #include "sha3/internals/utils.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Keccak family of sponge functions 13 | namespace sponge { 14 | 15 | static constexpr size_t KECCAK_WORD_BYTE_LEN = keccak::LANE_BW / std::numeric_limits::digits; 16 | 17 | // Compile-time check domain separator bit length. 18 | constexpr bool 19 | check_domain_separator(const size_t dom_sep_bit_len) 20 | { 21 | return dom_sep_bit_len <= 6u; 22 | } 23 | 24 | /** 25 | * Given `mlen` (>=0) -bytes message, this routine consumes it into Keccak[c] permutation state s.t. `offset` ( second parameter ) denotes how many bytes are 26 | * already consumed into rate portion of the state. 27 | * 28 | * - `num_bits_in_rate` portion of sponge will have bitwidth of 1600 - c. 29 | * - `offset` must ∈ [0, `num_bytes_in_rate`). 30 | * 31 | * This function implementation collects inspiration from https://github.com/itzmeanjan/turboshake/blob/e1a6b950/src/sponge.rs#L4-L56. 32 | */ 33 | template 34 | static forceinline constexpr void 35 | absorb(uint64_t state[keccak::LANE_CNT], size_t& offset, std::span msg) 36 | { 37 | constexpr size_t num_bytes_in_rate = num_bits_in_rate / std::numeric_limits::digits; 38 | 39 | std::array block{}; 40 | auto block_span = std::span(block); 41 | 42 | size_t msg_offset = 0; 43 | while (msg_offset < msg.size()) { 44 | const size_t remaining_num_bytes = msg.size() - msg_offset; 45 | const size_t absorbable_num_bytes = std::min(remaining_num_bytes, num_bytes_in_rate - offset); 46 | const size_t effective_block_byte_len = offset + absorbable_num_bytes; 47 | const size_t padded_effective_block_byte_len = (effective_block_byte_len + (KECCAK_WORD_BYTE_LEN - 1)) & (-KECCAK_WORD_BYTE_LEN); 48 | const size_t padded_effective_block_begins_at = offset & (-KECCAK_WORD_BYTE_LEN); 49 | 50 | std::fill_n(block_span.subspan(padded_effective_block_begins_at).begin(), padded_effective_block_byte_len - padded_effective_block_begins_at, 0x00); 51 | std::copy_n(msg.subspan(msg_offset).begin(), absorbable_num_bytes, block_span.subspan(offset).begin()); 52 | 53 | size_t state_word_index = padded_effective_block_begins_at / KECCAK_WORD_BYTE_LEN; 54 | for (size_t i = padded_effective_block_begins_at; i < padded_effective_block_byte_len; i += KECCAK_WORD_BYTE_LEN) { 55 | auto msg_chunk = std::span(block_span.subspan(i, KECCAK_WORD_BYTE_LEN)); 56 | auto msg_word = sha3_utils::le_bytes_to_u64(msg_chunk); 57 | 58 | state[state_word_index] ^= msg_word; 59 | state_word_index++; 60 | } 61 | 62 | offset += absorbable_num_bytes; 63 | msg_offset += absorbable_num_bytes; 64 | 65 | if (offset == num_bytes_in_rate) [[unlikely]] { 66 | keccak::permute(state); 67 | offset = 0; 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * Given that N message bytes are already consumed into Keccak[c] permutation state, this routine finalizes sponge state and makes it ready for squeezing, by 74 | * appending (along with domain separation bits) `10*1` padding bits to input message s.t. total absorbed message byte length becomes multiple of `rate/ 8` 75 | * -bytes. 76 | * 77 | * - `num_bits_in_rate` portion of sponge will have bitwidth of 1600 - c. 78 | * - `offset` must ∈ [0, `num_bytes_in_rate`) 79 | * 80 | * This function implementation collects some motivation from https://github.com/itzmeanjan/turboshake/blob/e1a6b950/src/sponge.rs#L58-L81. 81 | */ 82 | template 83 | static forceinline constexpr void 84 | finalize(uint64_t state[keccak::LANE_CNT], size_t& offset) 85 | requires(check_domain_separator(ds_bit_len)) 86 | { 87 | constexpr size_t num_bytes_in_rate = num_bits_in_rate / std::numeric_limits::digits; 88 | constexpr size_t num_words_in_rate = num_bytes_in_rate / std::numeric_limits::digits; 89 | 90 | const auto state_word_index = offset / KECCAK_WORD_BYTE_LEN; 91 | const auto byte_index_in_state_word = offset % KECCAK_WORD_BYTE_LEN; 92 | const auto shl_bit_offset = byte_index_in_state_word * std::numeric_limits::digits; 93 | 94 | constexpr uint8_t mask = (1u << ds_bit_len) - 1u; 95 | constexpr uint8_t pad_byte = (1u << ds_bit_len) | (domain_separator & mask); 96 | 97 | state[state_word_index] ^= static_cast(pad_byte) << shl_bit_offset; 98 | state[num_words_in_rate - 1] ^= UINT64_C(0x80) << 56; 99 | 100 | keccak::permute(state); 101 | offset = 0; 102 | } 103 | 104 | /** 105 | * Given that Keccak[c] permutation state is finalized, this routine can be invoked for squeezing `olen` -bytes out of rate portion of the state. 106 | * 107 | * - `num_bits_in_rate` portion of sponge will have bitwidth of 1600 - c. 108 | * - `squeezable` denotes how many bytes can be squeezed without permutating the sponge state. 109 | * - When `squeezable` becomes 0, state needs to be permutated again, after which `num_bytes_in_rate` can again be squeezed from rate portion of the state. 110 | * 111 | * This function implementation collects motivation from https://github.com/itzmeanjan/turboshake/blob/e1a6b950/src/sponge.rs#L83-L118. 112 | */ 113 | template 114 | static forceinline constexpr void 115 | squeeze(uint64_t state[keccak::LANE_CNT], size_t& squeezable, std::span out) 116 | { 117 | constexpr size_t num_bytes_in_rate = num_bits_in_rate / std::numeric_limits::digits; 118 | 119 | std::array blk_bytes{}; 120 | auto blk_bytes_span = std::span(blk_bytes); 121 | 122 | size_t out_offset = 0; 123 | while (out_offset < out.size()) { 124 | const size_t state_byte_offset = num_bytes_in_rate - squeezable; 125 | const size_t remaining_num_bytes = out.size() - out_offset; 126 | const size_t squeezable_num_bytes = std::min(remaining_num_bytes, squeezable); 127 | const size_t effective_block_byte_len = state_byte_offset + squeezable_num_bytes; 128 | const size_t padded_effective_block_byte_len = (effective_block_byte_len + (KECCAK_WORD_BYTE_LEN - 1)) & (-KECCAK_WORD_BYTE_LEN); 129 | const size_t padded_effective_block_begins_at = state_byte_offset & (-KECCAK_WORD_BYTE_LEN); 130 | 131 | size_t state_word_index = padded_effective_block_begins_at / KECCAK_WORD_BYTE_LEN; 132 | for (size_t i = padded_effective_block_begins_at; i < padded_effective_block_byte_len; i += KECCAK_WORD_BYTE_LEN) { 133 | auto chunk_bytes = std::span(blk_bytes_span.subspan(i, KECCAK_WORD_BYTE_LEN)); 134 | sha3_utils::u64_to_le_bytes(state[state_word_index], chunk_bytes); 135 | 136 | state_word_index++; 137 | } 138 | 139 | std::copy_n(blk_bytes_span.subspan(state_byte_offset).begin(), squeezable_num_bytes, out.subspan(out_offset).begin()); 140 | 141 | squeezable -= squeezable_num_bytes; 142 | out_offset += squeezable_num_bytes; 143 | 144 | if (squeezable == 0) [[unlikely]] { 145 | keccak::permute(state); 146 | squeezable = num_bytes_in_rate; 147 | } 148 | } 149 | } 150 | 151 | } 152 | -------------------------------------------------------------------------------- /tests/test_shake128.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/shake128.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Eval SHAKE128 XOF on statically defined input message during compilation-time. 11 | constexpr std::array 12 | eval_shake128() 13 | { 14 | // Statically defined input. 15 | std::array data{}; 16 | std::iota(data.begin(), data.end(), 0); 17 | 18 | // To be computed output. 19 | std::array md{}; 20 | 21 | shake128::shake128_t hasher; 22 | hasher.absorb(data); 23 | hasher.finalize(); 24 | hasher.squeeze(md); 25 | 26 | return md; 27 | } 28 | 29 | // Ensure that SHAKE128 XOF implementation is compile-time evaluable. 30 | TEST(Sha3XOF, CompileTimeEvalSHAKE128) 31 | { 32 | // Input = 33 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 34 | // Output = 35 | // 9d32ba2aa8f40b0cdf108376d77abfd5c97f149e6ba0c9efe3499c7b3c039b0afac641a978ef435b3d83b9712da8ea826bb38078899b3efaec77d44a0460b220225d1b0b11a1d1c5cb0acb5aca92c6fb95f64a992eee6b6de24434aae4fba9d496bd8bd90624391f79c0db7d20eef1ddbfe8d771b4123e97ad7664012188590eb0b43c7073b7a9ab8af27229bc7246296ac0e172fca7314b8f100dc247d51c949bc4977c345d7c1d5536c96825f3650b7f80b5981b252ce4a858e54f9833cceaf38c12a91a8c6b341e197eb894553ca6f100f731f00f43b854098aace7a4e0ed8252782523f561dd994c291229eaf70185c98ed0026be1bd39c17dd817424009 36 | 37 | constexpr auto md = eval_shake128(); 38 | static_assert(md == std::array{ 157, 50, 186, 42, 168, 244, 11, 12, 223, 16, 131, 118, 215, 122, 191, 213, 201, 127, 20, 158, 107, 160, 39 | 201, 239, 227, 73, 156, 123, 60, 3, 155, 10, 250, 198, 65, 169, 120, 239, 67, 91, 61, 131, 185, 113, 40 | 45, 168, 234, 130, 107, 179, 128, 120, 137, 155, 62, 250, 236, 119, 212, 74, 4, 96, 178, 32, 34, 93, 41 | 27, 11, 17, 161, 209, 197, 203, 10, 203, 90, 202, 146, 198, 251, 149, 246, 74, 153, 46, 238, 107, 109, 42 | 226, 68, 52, 170, 228, 251, 169, 212, 150, 189, 139, 217, 6, 36, 57, 31, 121, 192, 219, 125, 32, 238, 43 | 241, 221, 191, 232, 215, 113, 180, 18, 62, 151, 173, 118, 100, 1, 33, 136, 89, 14, 176, 180, 60, 112, 44 | 115, 183, 169, 171, 138, 242, 114, 41, 188, 114, 70, 41, 106, 192, 225, 114, 252, 167, 49, 75, 143, 16, 45 | 13, 194, 71, 213, 28, 148, 155, 196, 151, 124, 52, 93, 124, 29, 85, 54, 201, 104, 37, 243, 101, 11, 46 | 127, 128, 181, 152, 27, 37, 44, 228, 168, 88, 229, 79, 152, 51, 204, 234, 243, 140, 18, 169, 26, 140, 47 | 107, 52, 30, 25, 126, 184, 148, 85, 60, 166, 241, 0, 247, 49, 240, 15, 67, 184, 84, 9, 138, 172, 48 | 231, 164, 224, 237, 130, 82, 120, 37, 35, 245, 97, 221, 153, 76, 41, 18, 41, 234, 247, 1, 133, 201, 49 | 142, 208, 2, 107, 225, 189, 57, 193, 125, 216, 23, 66, 64, 9 }, 50 | "Must be able to compute Shake128 Xof during compile-time !"); 51 | } 52 | 53 | /** 54 | * Test that absorbing same message bytes using both incremental and one-shot hashing, should yield same output bytes, for SHAKE128 XOF. 55 | * 56 | * This test collects inspiration from https://github.com/itzmeanjan/turboshake/blob/e1a6b950c5374aff49f04f6d51d807e68077ab25/src/tests.rs#L372-L415 57 | */ 58 | TEST(Sha3XOF, SHAKE128IncrementalAbsorptionAndSqueezing) 59 | { 60 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 61 | for (size_t olen = MIN_OUT_LEN; olen < MAX_OUT_LEN; olen++) { 62 | std::vector msg(mlen); 63 | std::vector oneshot_out(olen); 64 | std::vector multishot_out(olen); 65 | 66 | auto msg_span = std::span(msg); 67 | auto oneshot_out_span = std::span(oneshot_out); 68 | auto multishot_out_span = std::span(multishot_out); 69 | 70 | sha3_test_utils::random_data(msg_span); 71 | 72 | shake128::shake128_t hasher; 73 | 74 | // Oneshot absorption and squeezing 75 | hasher.absorb(msg_span); 76 | hasher.finalize(); 77 | hasher.squeeze(oneshot_out_span); 78 | 79 | hasher.reset(); 80 | 81 | // Incremental absorption and squeezing 82 | size_t off = 0; 83 | while (off < mlen) { 84 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 85 | auto tmp = std::max(msg[off], 1); 86 | auto elen = std::min(tmp, mlen - off); 87 | 88 | hasher.absorb(msg_span.subspan(off, elen)); 89 | off += elen; 90 | } 91 | 92 | hasher.finalize(); 93 | 94 | // squeeze message bytes in many iterations 95 | off = 0; 96 | while (off < olen) { 97 | hasher.squeeze(multishot_out_span.subspan(off, 1)); 98 | 99 | auto elen = std::min(multishot_out[off], olen - (off + 1)); 100 | 101 | off += 1; 102 | hasher.squeeze(multishot_out_span.subspan(off, elen)); 103 | off += elen; 104 | } 105 | 106 | EXPECT_EQ(oneshot_out, multishot_out); 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Ensure that SHAKE128 XOF implementation is conformant with FIPS 202 standard, by using KAT file generated following 113 | * https://gist.github.com/itzmeanjan/448f97f9c49d781a5eb3ddd6ea6e7364. 114 | */ 115 | TEST(Sha3XOF, SHAKE128KnownAnswerTests) 116 | { 117 | using namespace std::literals; 118 | 119 | const std::string kat_file = "./kats/shake128.kat"; 120 | std::fstream file(kat_file); 121 | 122 | while (true) { 123 | std::string len0; 124 | 125 | if (!std::getline(file, len0).eof()) { 126 | std::string msg0; 127 | std::string out0; 128 | 129 | std::getline(file, msg0); 130 | std::getline(file, out0); 131 | 132 | auto msg1 = std::string_view(msg0); 133 | auto out1 = std::string_view(out0); 134 | 135 | auto msg2 = msg1.substr(msg1.find("="sv) + 2, msg1.size()); 136 | auto out2 = out1.substr(out1.find("="sv) + 2, out1.size()); 137 | 138 | auto msg = sha3_test_utils::parse_dynamic_sized_hex_string(msg2); 139 | auto expected_out = sha3_test_utils::parse_dynamic_sized_hex_string(out2); 140 | 141 | std::vector computed_out(expected_out.size()); 142 | shake128::shake128_t hasher; 143 | 144 | hasher.absorb(msg); 145 | hasher.finalize(); 146 | hasher.squeeze(computed_out); 147 | 148 | EXPECT_EQ(computed_out, expected_out); 149 | 150 | std::string empty_line; 151 | std::getline(file, empty_line); 152 | } else { 153 | break; 154 | } 155 | } 156 | 157 | file.close(); 158 | } 159 | -------------------------------------------------------------------------------- /tests/test_shake256.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/shake256.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Eval SHAKE256 XOF on statically defined input message during compilation-time. 11 | constexpr std::array 12 | eval_shake256() 13 | { 14 | // Statically defined input. 15 | std::array data{}; 16 | std::iota(data.begin(), data.end(), 0); 17 | 18 | // To be computed output. 19 | std::array md{}; 20 | 21 | shake256::shake256_t hasher; 22 | hasher.absorb(data); 23 | hasher.finalize(); 24 | hasher.squeeze(md); 25 | 26 | return md; 27 | } 28 | 29 | // Ensure that SHAKE256 XOF implementation is compile-time evaluable. 30 | TEST(Sha3XOF, CompileTimeEvalSHAKE256) 31 | { 32 | // Input = 33 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 34 | // Output = 35 | // 336c8aa7f2b08bda6bd7402cd2ea89760b7728a8b31802b80524756361165366ff8159f2f4568a2bfa286db6387895629938c2868a6421c37f988455763a75e4b9259e0a939aaa68295119ccea72c9f0ca7d048aa70eeeb4534c6bd08ecc6163217c790f33b84a89623f8e5538b734967e9490a48b7d0658afb4565364e8b234dfe6a2bceb12ce2130eec00bf2113615a276819d7815f5891d07600275f4d8fbc87b056f44bc2b141ca5ed9e4cb6e9a7bf71f520971dca1c8da6140e2af31faef5502e84991a2d9e9a80183c174cc105ef178d5f6fa45b0f284eb7bced20a47c3f584aca27eac5558da517af7569fe2e843461b4b65f81f819bf81aae6dfaa3b 36 | 37 | constexpr auto md = eval_shake256(); 38 | static_assert(md == std::array{ 51, 108, 138, 167, 242, 176, 139, 218, 107, 215, 64, 44, 210, 234, 137, 118, 11, 119, 40, 168, 179, 24, 39 | 2, 184, 5, 36, 117, 99, 97, 22, 83, 102, 255, 129, 89, 242, 244, 86, 138, 43, 250, 40, 109, 182, 40 | 56, 120, 149, 98, 153, 56, 194, 134, 138, 100, 33, 195, 127, 152, 132, 85, 118, 58, 117, 228, 185, 37, 41 | 158, 10, 147, 154, 170, 104, 41, 81, 25, 204, 234, 114, 201, 240, 202, 125, 4, 138, 167, 14, 238, 180, 42 | 83, 76, 107, 208, 142, 204, 97, 99, 33, 124, 121, 15, 51, 184, 74, 137, 98, 63, 142, 85, 56, 183, 43 | 52, 150, 126, 148, 144, 164, 139, 125, 6, 88, 175, 180, 86, 83, 100, 232, 178, 52, 223, 230, 162, 188, 44 | 235, 18, 206, 33, 48, 238, 192, 11, 242, 17, 54, 21, 162, 118, 129, 157, 120, 21, 245, 137, 29, 7, 45 | 96, 2, 117, 244, 216, 251, 200, 123, 5, 111, 68, 188, 43, 20, 28, 165, 237, 158, 76, 182, 233, 167, 46 | 191, 113, 245, 32, 151, 29, 202, 28, 141, 166, 20, 14, 42, 243, 31, 174, 245, 80, 46, 132, 153, 26, 47 | 45, 158, 154, 128, 24, 60, 23, 76, 193, 5, 239, 23, 141, 95, 111, 164, 91, 15, 40, 78, 183, 188, 48 | 237, 32, 164, 124, 63, 88, 74, 202, 39, 234, 197, 85, 141, 165, 23, 175, 117, 105, 254, 46, 132, 52, 49 | 97, 180, 182, 95, 129, 248, 25, 191, 129, 170, 230, 223, 170, 59 }, 50 | "Must be able to compute Shake256 Xof during compile-time !"); 51 | } 52 | 53 | /** 54 | * Test that absorbing same message bytes using both incremental and one-shot hashing, should yield same output bytes, for SHAKE256 XOF. 55 | * 56 | * This test collects inspiration from https://github.com/itzmeanjan/turboshake/blob/e1a6b950c5374aff49f04f6d51d807e68077ab25/src/tests.rs#L372-L415 57 | */ 58 | TEST(Sha3XOF, SHAKE256IncrementalAbsorptionAndSqueezing) 59 | { 60 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 61 | for (size_t olen = MIN_OUT_LEN; olen < MAX_OUT_LEN; olen++) { 62 | std::vector msg(mlen); 63 | std::vector oneshot_out(olen); 64 | std::vector multishot_out(olen); 65 | 66 | auto msg_span = std::span(msg); 67 | auto oneshot_out_span = std::span(oneshot_out); 68 | auto multishot_out_span = std::span(multishot_out); 69 | 70 | sha3_test_utils::random_data(msg_span); 71 | 72 | shake256::shake256_t hasher; 73 | 74 | // Oneshot absorption and squeezing 75 | hasher.absorb(msg_span); 76 | hasher.finalize(); 77 | hasher.squeeze(oneshot_out_span); 78 | 79 | hasher.reset(); 80 | 81 | // Incremental absorption and squeezing 82 | size_t off = 0; 83 | while (off < mlen) { 84 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 85 | auto tmp = std::max(msg[off], 1); 86 | auto elen = std::min(tmp, mlen - off); 87 | 88 | hasher.absorb(msg_span.subspan(off, elen)); 89 | off += elen; 90 | } 91 | 92 | hasher.finalize(); 93 | 94 | // squeeze message bytes in many iterations 95 | off = 0; 96 | while (off < olen) { 97 | hasher.squeeze(multishot_out_span.subspan(off, 1)); 98 | 99 | auto elen = std::min(multishot_out[off], olen - (off + 1)); 100 | 101 | off += 1; 102 | hasher.squeeze(multishot_out_span.subspan(off, elen)); 103 | off += elen; 104 | } 105 | 106 | EXPECT_EQ(oneshot_out, multishot_out); 107 | } 108 | } 109 | } 110 | 111 | /** 112 | * Ensure that SHAKE256 XOF implementation is conformant with FIPS 202 standard, by using KAT file generated following 113 | * https://gist.github.com/itzmeanjan/448f97f9c49d781a5eb3ddd6ea6e7364. 114 | */ 115 | TEST(Sha3XOF, SHAKE256KnownAnswerTests) 116 | { 117 | using namespace std::literals; 118 | 119 | const std::string kat_file = "./kats/shake256.kat"; 120 | std::fstream file(kat_file); 121 | 122 | while (true) { 123 | std::string len0; 124 | 125 | if (!std::getline(file, len0).eof()) { 126 | std::string msg0; 127 | std::string out0; 128 | 129 | std::getline(file, msg0); 130 | std::getline(file, out0); 131 | 132 | auto msg1 = std::string_view(msg0); 133 | auto out1 = std::string_view(out0); 134 | 135 | auto msg2 = msg1.substr(msg1.find("="sv) + 2, msg1.size()); 136 | auto out2 = out1.substr(out1.find("="sv) + 2, out1.size()); 137 | 138 | auto msg = sha3_test_utils::parse_dynamic_sized_hex_string(msg2); 139 | auto expected_out = sha3_test_utils::parse_dynamic_sized_hex_string(out2); 140 | 141 | std::vector computed_out(expected_out.size()); 142 | shake256::shake256_t hasher; 143 | 144 | hasher.absorb(msg); 145 | hasher.finalize(); 146 | hasher.squeeze(computed_out); 147 | 148 | EXPECT_EQ(computed_out, expected_out); 149 | 150 | std::string empty_line; 151 | std::getline(file, empty_line); 152 | } else { 153 | break; 154 | } 155 | } 156 | 157 | file.close(); 158 | } 159 | -------------------------------------------------------------------------------- /tests/test_turboshake128.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/turboshake128.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Eval TurboSHAKE128 XOF on statically defined input message during compilation-time. 13 | constexpr std::array 14 | eval_turboshake128() 15 | { 16 | // Statically defined input. 17 | std::array data{}; 18 | std::iota(data.begin(), data.end(), 0); 19 | 20 | // To be computed output. 21 | std::array md{}; 22 | 23 | turboshake128::turboshake128_t hasher; 24 | hasher.absorb(data); 25 | hasher.finalize(); 26 | hasher.squeeze(md); 27 | 28 | return md; 29 | } 30 | 31 | // Ensure that TurboSHAKE128 XOF implementation is compile-time evaluable. 32 | TEST(Sha3XOF, CompileTimeEvalTurboSHAKE128) 33 | { 34 | // Input = 35 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 36 | // Output = 37 | // 5d6efc5eb4b62f82fe835511ad0ac01f55d47366f99d1476d99069c2f43997989263aacacabbe3c80b376c8403424b714b20a5d4a389aec7c72fd0efcf25ba3c9aac118bc557bd1d1f691db24eea33b6a569dfc2c13b0d4f4ed7b5860f5af9b1a014249dec67df97538904d48926a323d462908be20d56fbae9d34f7912e35d14406b9bfabb8f0bda7b7f2e54f8f6d16d34fed442129a281e2f5fa80d22b2ce90e5229bc31e5138cc7075e8b4b912d5d6147e02a37777a93feb819dd9d07953f00c29bfb38f719774d7f0130c6d984d6728c296ba094057d64b27abfd32e50a1982ad8cc0ebe220e458ac29bf127d3e827aea4f33efe253efc315a2caacd5cff 38 | 39 | constexpr auto md = eval_turboshake128(); 40 | static_assert(md == std::array{ 93, 110, 252, 94, 180, 182, 47, 130, 254, 131, 85, 17, 173, 10, 192, 31, 85, 212, 115, 102, 249, 157, 41 | 20, 118, 217, 144, 105, 194, 244, 57, 151, 152, 146, 99, 170, 202, 202, 187, 227, 200, 11, 55, 108, 132, 42 | 3, 66, 75, 113, 75, 32, 165, 212, 163, 137, 174, 199, 199, 47, 208, 239, 207, 37, 186, 60, 154, 172, 43 | 17, 139, 197, 87, 189, 29, 31, 105, 29, 178, 78, 234, 51, 182, 165, 105, 223, 194, 193, 59, 13, 79, 44 | 78, 215, 181, 134, 15, 90, 249, 177, 160, 20, 36, 157, 236, 103, 223, 151, 83, 137, 4, 212, 137, 38, 45 | 163, 35, 212, 98, 144, 139, 226, 13, 86, 251, 174, 157, 52, 247, 145, 46, 53, 209, 68, 6, 185, 191, 46 | 171, 184, 240, 189, 167, 183, 242, 229, 79, 143, 109, 22, 211, 79, 237, 68, 33, 41, 162, 129, 226, 245, 47 | 250, 128, 210, 43, 44, 233, 14, 82, 41, 188, 49, 229, 19, 140, 199, 7, 94, 139, 75, 145, 45, 93, 48 | 97, 71, 224, 42, 55, 119, 122, 147, 254, 184, 25, 221, 157, 7, 149, 63, 0, 194, 155, 251, 56, 247, 49 | 25, 119, 77, 127, 1, 48, 198, 217, 132, 214, 114, 140, 41, 107, 160, 148, 5, 125, 100, 178, 122, 191, 50 | 211, 46, 80, 161, 152, 42, 216, 204, 14, 190, 34, 14, 69, 138, 194, 155, 241, 39, 211, 232, 39, 174, 51 | 164, 243, 62, 254, 37, 62, 252, 49, 90, 44, 170, 205, 92, 255 }, 52 | "Must be able to compute TurboSHAKE128 XOF during compile-time !"); 53 | } 54 | 55 | /** 56 | * Test that absorbing same message bytes using both incremental and one-shot hashing, should yield same output bytes, for TurboSHAKE128 XOF. 57 | * 58 | * This test collects inspiration from https://github.com/itzmeanjan/turboshake/blob/e1a6b950c5374aff49f04f6d51d807e68077ab25/src/tests.rs#L372-L415. 59 | */ 60 | TEST(Sha3XOF, TurboSHAKE128IncrementalAbsorptionAndSqueezing) 61 | { 62 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 63 | for (size_t olen = MIN_OUT_LEN; olen < MAX_OUT_LEN; olen++) { 64 | std::vector msg(mlen); 65 | std::vector oneshot_out(olen); 66 | std::vector multishot_out(olen); 67 | 68 | auto msg_span = std::span(msg); 69 | auto oneshot_out_span = std::span(oneshot_out); 70 | auto multishot_out_span = std::span(multishot_out); 71 | 72 | sha3_test_utils::random_data(msg_span); 73 | 74 | turboshake128::turboshake128_t hasher; 75 | 76 | // Oneshot absorption and squeezing 77 | hasher.absorb(msg_span); 78 | hasher.finalize(); 79 | hasher.squeeze(oneshot_out_span); 80 | 81 | hasher.reset(); 82 | 83 | // Incremental absorption and squeezing 84 | size_t off = 0; 85 | while (off < mlen) { 86 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 87 | auto tmp = std::max(msg[off], 1); 88 | auto elen = std::min(tmp, mlen - off); 89 | 90 | hasher.absorb(msg_span.subspan(off, elen)); 91 | off += elen; 92 | } 93 | 94 | hasher.finalize(); 95 | 96 | // squeeze message bytes in many iterations 97 | off = 0; 98 | while (off < olen) { 99 | hasher.squeeze(multishot_out_span.subspan(off, 1)); 100 | 101 | auto elen = std::min(multishot_out[off], olen - (off + 1)); 102 | 103 | off += 1; 104 | hasher.squeeze(multishot_out_span.subspan(off, elen)); 105 | off += elen; 106 | } 107 | 108 | EXPECT_EQ(oneshot_out, multishot_out); 109 | } 110 | } 111 | } 112 | 113 | template 114 | std::vector 115 | compute_turboshake128_output(const std::vector msg, const size_t out_byte_len) 116 | { 117 | std::vector out_bytes(out_byte_len, 0); 118 | 119 | turboshake128::turboshake128_t hasher; 120 | 121 | hasher.absorb(msg); 122 | hasher.finalize(); 123 | hasher.squeeze(out_bytes); 124 | 125 | return out_bytes; 126 | } 127 | 128 | // Ensure that TurboSHAKE128 XOF implementation is conformant with RFC 9861 https://datatracker.ietf.org/doc/rfc9861, by using test vectors defined there. 129 | TEST(Sha3XOF, TurboSHAKE128KnownAnswerTests) 130 | { 131 | // clang-format off 132 | EXPECT_EQ(compute_turboshake128_output<0x01>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("868cbd53b078205abb85815d941f7d0376bff5b8888a6a2d03483afbaf83967f")); 133 | EXPECT_EQ(compute_turboshake128_output<0x02>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("8bcf8b0266eb3ef49e2b1df2eb627021d86281801116761f44efc976444f021b")); 134 | EXPECT_EQ(compute_turboshake128_output<0x03>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("a0347b35a7fa3d2f8561b3a4648de357be6762a6b76d5b2c1119cda104688192")); 135 | EXPECT_EQ(compute_turboshake128_output<0x0c>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("2c6462e826d1d5fa989b91ae4d8b3a3b63df64141e0ac0f9a1fbdf653b4ccf13")); 136 | EXPECT_EQ(compute_turboshake128_output<0x1f>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("1e415f1c5983aff2169217277d17bb538cd945a397ddec541f1ce41af2c1b74c")); 137 | EXPECT_EQ(compute_turboshake128_output<0x23>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("82d2b02713285b0dc2e8d1f2b40848ee62589b5b11262867e610e15ee62e1835")); 138 | EXPECT_EQ(compute_turboshake128_output<0x3a>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("55c63f13a040da7034f67d7b7b9a173426970419232209c01ca176e08b5acf5c")); 139 | EXPECT_EQ(compute_turboshake128_output<0x51>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("4e2695cf70d7c6c87e80a9f383b7aa6f0f8a4b0727f5cd2951c6947dffab6425")); 140 | EXPECT_EQ(compute_turboshake128_output<0x68>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("2e1c136a8af2e8b4c4cf9a7bca593d798f61bd1f153cd08483447a5de4369b1e")); 141 | EXPECT_EQ(compute_turboshake128_output<0x7f>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("e4e1fd449c36ef25256c896e1907af3f458253d4a0bd820a6fef83377ae031f9")); 142 | 143 | EXPECT_EQ(compute_turboshake128_output<0x01>({}, 64), sha3_test_utils::parse_dynamic_sized_hex_string("868cbd53b078205abb85815d941f7d0376bff5b8888a6a2d03483afbaf83967f226e2cad5e7b1ec4ca72236f076462199fea48c93438ad4c49c767f9417be7c5")); 144 | EXPECT_EQ(compute_turboshake128_output<0x1f>({}, 64), sha3_test_utils::parse_dynamic_sized_hex_string("1E415F1C5983AFF2169217277D17BB538CD945A397DDEC541F1CE41AF2C1B74C3E8CCAE2A4DAE56C84A04C2385C03C15E8193BDF58737363321691C05462C8DF")); 145 | 146 | EXPECT_EQ(compute_turboshake128_output<0x1f>(sha3_test_utils::ptn(1), 32), sha3_test_utils::parse_dynamic_sized_hex_string("55cedd6f60af7bb29a4042ae832ef3f58db7299f893ebb9247247d856958daa9")); 147 | EXPECT_EQ(compute_turboshake128_output<0x1f>(sha3_test_utils::ptn(17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("9c97d036a3bac819db70ede0ca554ec6e4c2a1a4ffbfd9ec269ca6a111161233")); 148 | EXPECT_EQ(compute_turboshake128_output<0x1f>(sha3_test_utils::ptn(17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("96c77c279e0126f7fc07c9b07f5cdae1e0be60bdbe10620040e75d7223a624d2")); 149 | EXPECT_EQ(compute_turboshake128_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("d4976eb56bcf118520582b709f73e1d6853e001fdaf80e1b13e0d0599d5fb372")); 150 | EXPECT_EQ(compute_turboshake128_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("DA67C7039E98BF530CF7A37830C6664E14CBAB7F540F58403B1B82951318EE5C")); 151 | EXPECT_EQ(compute_turboshake128_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("B97A906FBF83EF7C812517ABF3B2D0AEA0C4F60318CE11CF103925127F59EECD")); 152 | EXPECT_EQ(compute_turboshake128_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17 * 17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("35CD494ADEDED2F25239AF09A7B8EF0C4D1CA4FE2D1AC370FA63216FE7B4C2B1")); 153 | 154 | EXPECT_EQ(compute_turboshake128_output<0x01>(sha3_test_utils::ptn(1), 32), sha3_test_utils::parse_dynamic_sized_hex_string("0fc5bb1616bfd8121beb8cd6cde167ffbe4b11e51d9bc9a6a92c34ed3e46f4e1")); 155 | EXPECT_EQ(compute_turboshake128_output<0x01>(sha3_test_utils::ptn(17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("6f0f5f330a7114ed345b97d012f8a8bac5ba32f1c0aafab22ef880737bf0c103")); 156 | EXPECT_EQ(compute_turboshake128_output<0x01>(sha3_test_utils::ptn(17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("6232caa37353b5adb0e16e5beb97928110c5b837531339a2c9eb08014faa8ef6")); 157 | EXPECT_EQ(compute_turboshake128_output<0x01>(sha3_test_utils::ptn(17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("668105870786e2aa80718487563aa06824eabc1d3a8e8b642f6d9996244fe8cf")); 158 | EXPECT_EQ(compute_turboshake128_output<0x01>(sha3_test_utils::ptn(17 * 17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("795de7dd0ec596c20145d1784ac2acd625b4f62653872a06d8a8b9a0543aa863")); 159 | EXPECT_EQ(compute_turboshake128_output<0x01>(sha3_test_utils::ptn(17 * 17 * 17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("4185e05262bcbcf7f74f50f08a710791ea0a12fba13c3a23ff07c33c0110bd20")); 160 | 161 | EXPECT_EQ(compute_turboshake128_output<0x01>({ 0xff, 0xff, 0xff }, 32), sha3_test_utils::parse_dynamic_sized_hex_string("BF323F940494E88EE1C540FE660BE8A0C93F43D15EC006998462FA994EED5DAB")); 162 | EXPECT_EQ(compute_turboshake128_output<0x06>({ 0xff }, 32), sha3_test_utils::parse_dynamic_sized_hex_string("8EC9C66465ED0D4A6C35D13506718D687A25CB05C74CCA1E42501ABD83874A67")); 163 | EXPECT_EQ(compute_turboshake128_output<0x07>({ 0xff, 0xff, 0xff }, 32), sha3_test_utils::parse_dynamic_sized_hex_string("B658576001CAD9B1E5F399A9F77723BBA05458042D68206F7252682DBA3663ED")); 164 | EXPECT_EQ(compute_turboshake128_output<0x0b>({ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, 32), sha3_test_utils::parse_dynamic_sized_hex_string("8DEEAA1AEC47CCEE569F659C21DFA8E112DB3CEE37B18178B2ACD805B799CC37")); 165 | EXPECT_EQ(compute_turboshake128_output<0x30>({ 0xff }, 32), sha3_test_utils::parse_dynamic_sized_hex_string("553122E2135E363C3292BED2C6421FA232BAB03DAA07C7D6636603286506325B")); 166 | EXPECT_EQ(compute_turboshake128_output<0x7f>({ 0xff, 0xff, 0xff }, 32), sha3_test_utils::parse_dynamic_sized_hex_string("16274CC656D44CEFD422395D0F9053BDA6D28E122ABA15C765E5AD0E6EAF26F9")); 167 | 168 | { 169 | auto out_bytes = compute_turboshake128_output<0x01>({}, 10032); 170 | auto out_bytes_span = std::span(out_bytes); 171 | 172 | EXPECT_TRUE(std::equal(out_bytes_span.last<32>().begin(), out_bytes_span.last<32>().end(), sha3_test_utils::parse_dynamic_sized_hex_string("fa09df77a17a33fe098328ba02786ac770301386f77d0731f2b866bd0140b412").begin())); 173 | } 174 | 175 | { 176 | auto out_bytes = compute_turboshake128_output<0x1f>({}, 10032); 177 | auto out_bytes_span = std::span(out_bytes); 178 | 179 | EXPECT_TRUE(std::equal(out_bytes_span.last<32>().begin(), out_bytes_span.last<32>().end(), sha3_test_utils::parse_dynamic_sized_hex_string("A3B9B0385900CE761F22AED548E754DA10A5242D62E8C658E3F3A923A7555607").begin())); 180 | } 181 | // clang-format on 182 | } 183 | -------------------------------------------------------------------------------- /tests/test_turboshake256.cpp: -------------------------------------------------------------------------------- 1 | #include "sha3/turboshake256.hpp" 2 | #include "test_conf.hpp" 3 | #include "test_utils.hpp" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Eval TurboSHAKE256 XOF on statically defined input message during compilation-time. 13 | constexpr std::array 14 | eval_turboshake256() 15 | { 16 | // Statically defined input. 17 | std::array data{}; 18 | std::iota(data.begin(), data.end(), 0); 19 | 20 | // To be computed output. 21 | std::array md{}; 22 | 23 | turboshake256::turboshake256_t hasher; 24 | hasher.absorb(data); 25 | hasher.finalize(); 26 | hasher.squeeze(md); 27 | 28 | return md; 29 | } 30 | 31 | // Ensure that TurboSHAKE256 XOF implementation is compile-time evaluable. 32 | TEST(Sha3XOF, CompileTimeEvalTurboSHAKE256) 33 | { 34 | // Input = 35 | // 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff 36 | // Output = 37 | // 2e1c8e060e12fab9d0f69f476a85832ca52590a8ac1cbc55b4ffc6d128dd10376c62c23ed88d53ec22e65f6026dc846fbbe9c23afa8e40e6f37fcfdbc77cfc64326c9d3afb5580c1eaeef1cdce48ded652ffc6d752d7293078ef60f77839620a20e2810d1c1e5ef3f8dc062798c1f9518b1294786e3d2600c779b819b37b423d3bfa4064ff58de6a0c226417dcc0405e0186c3922979c360694a412a97dba2fa89a41c6720e9dce5681e74c9a2f8eb8fa0cbc09f1cfb180a83315ea040811534eed86907f8f9f098c5d68b2385b3fac242259a1d099a14a984dfce3464cde9ecda311c041c9a44e2566475b6c781a791a88fba3d0169fcf1b27c8d82b27a34e1 38 | 39 | constexpr auto md = eval_turboshake256(); 40 | static_assert(md == std::array{ 46, 28, 142, 6, 14, 18, 250, 185, 208, 246, 159, 71, 106, 133, 131, 44, 165, 37, 144, 168, 172, 28, 41 | 188, 85, 180, 255, 198, 209, 40, 221, 16, 55, 108, 98, 194, 62, 216, 141, 83, 236, 34, 230, 95, 96, 42 | 38, 220, 132, 111, 187, 233, 194, 58, 250, 142, 64, 230, 243, 127, 207, 219, 199, 124, 252, 100, 50, 108, 43 | 157, 58, 251, 85, 128, 193, 234, 238, 241, 205, 206, 72, 222, 214, 82, 255, 198, 215, 82, 215, 41, 48, 44 | 120, 239, 96, 247, 120, 57, 98, 10, 32, 226, 129, 13, 28, 30, 94, 243, 248, 220, 6, 39, 152, 193, 45 | 249, 81, 139, 18, 148, 120, 110, 61, 38, 0, 199, 121, 184, 25, 179, 123, 66, 61, 59, 250, 64, 100, 46 | 255, 88, 222, 106, 12, 34, 100, 23, 220, 192, 64, 94, 1, 134, 195, 146, 41, 121, 195, 96, 105, 74, 47 | 65, 42, 151, 219, 162, 250, 137, 164, 28, 103, 32, 233, 220, 229, 104, 30, 116, 201, 162, 248, 235, 143, 48 | 160, 203, 192, 159, 28, 251, 24, 10, 131, 49, 94, 160, 64, 129, 21, 52, 238, 216, 105, 7, 248, 249, 49 | 240, 152, 197, 214, 139, 35, 133, 179, 250, 194, 66, 37, 154, 29, 9, 154, 20, 169, 132, 223, 206, 52, 50 | 100, 205, 233, 236, 218, 49, 28, 4, 28, 154, 68, 226, 86, 100, 117, 182, 199, 129, 167, 145, 168, 143, 51 | 186, 61, 1, 105, 252, 241, 178, 124, 141, 130, 178, 122, 52, 225 }, 52 | "Must be able to compute TurboSHAKE256 XOF during compile-time !"); 53 | } 54 | 55 | /** 56 | * Test that absorbing same message bytes using both incremental and one-shot hashing, should yield same output bytes, for TurboSHAKE256 XOF. 57 | * 58 | * This test collects inspiration from https://github.com/itzmeanjan/turboshake/blob/e1a6b950c5374aff49f04f6d51d807e68077ab25/src/tests.rs#L372-L415. 59 | */ 60 | TEST(Sha3XOF, TurboSHAKE256IncrementalAbsorptionAndSqueezing) 61 | { 62 | for (size_t mlen = MIN_MSG_LEN; mlen < MAX_MSG_LEN; mlen++) { 63 | for (size_t olen = MIN_OUT_LEN; olen < MAX_OUT_LEN; olen++) { 64 | std::vector msg(mlen); 65 | std::vector oneshot_out(olen); 66 | std::vector multishot_out(olen); 67 | 68 | auto msg_span = std::span(msg); 69 | auto oneshot_out_span = std::span(oneshot_out); 70 | auto multishot_out_span = std::span(multishot_out); 71 | 72 | sha3_test_utils::random_data(msg_span); 73 | 74 | turboshake256::turboshake256_t hasher; 75 | 76 | // Oneshot absorption and squeezing 77 | hasher.absorb(msg_span); 78 | hasher.finalize(); 79 | hasher.squeeze(oneshot_out_span); 80 | 81 | hasher.reset(); 82 | 83 | // Incremental absorption and squeezing 84 | size_t off = 0; 85 | while (off < mlen) { 86 | // because we don't want to be stuck in an infinite loop if msg[off] = 0 87 | auto tmp = std::max(msg[off], 1); 88 | auto elen = std::min(tmp, mlen - off); 89 | 90 | hasher.absorb(msg_span.subspan(off, elen)); 91 | off += elen; 92 | } 93 | 94 | hasher.finalize(); 95 | 96 | // squeeze message bytes in many iterations 97 | off = 0; 98 | while (off < olen) { 99 | hasher.squeeze(multishot_out_span.subspan(off, 1)); 100 | 101 | auto elen = std::min(multishot_out[off], olen - (off + 1)); 102 | 103 | off += 1; 104 | hasher.squeeze(multishot_out_span.subspan(off, elen)); 105 | off += elen; 106 | } 107 | 108 | EXPECT_EQ(oneshot_out, multishot_out); 109 | } 110 | } 111 | } 112 | 113 | template 114 | std::vector 115 | compute_turboshake256_output(const std::vector msg, const size_t out_byte_len) 116 | { 117 | std::vector out_bytes(out_byte_len, 0); 118 | 119 | turboshake256::turboshake256_t hasher; 120 | 121 | hasher.absorb(msg); 122 | hasher.finalize(); 123 | hasher.squeeze(out_bytes); 124 | 125 | return out_bytes; 126 | } 127 | 128 | // Ensure that TurboSHAKE256 XOF implementation is conformant with RFC 9861 https://datatracker.ietf.org/doc/rfc9861, by using test vectors defined there. 129 | TEST(Sha3XOF, TurboSHAKE256KnownAnswerTests) 130 | { 131 | // clang-format off 132 | EXPECT_EQ(compute_turboshake256_output<0x01>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("e3dd2df0943bde6d82e39ec36059f35cd76720e2df38cc6b10b69fddfcaa3a4a")); 133 | EXPECT_EQ(compute_turboshake256_output<0x02>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("cfdbc69ec2652711dc3013cee68def374948ef09e62d82f2749e3dbc71f04dce")); 134 | EXPECT_EQ(compute_turboshake256_output<0x03>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("1402a1d6bebcf52cdbc7074c3d7b1adc545646458400a63980ebb3dd0ab04c68")); 135 | EXPECT_EQ(compute_turboshake256_output<0x0c>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("3c78a84557f19506a6151985664cf6163c4d4033d6bc310f8e8dde56e232abf4")); 136 | EXPECT_EQ(compute_turboshake256_output<0x1f>({}, 64), sha3_test_utils::parse_dynamic_sized_hex_string("367A329DAFEA871C7802EC67F905AE13C57695DC2C6663C61035F59A18F8E7DB11EDC0E12E91EA60EB6B32DF06DD7F002FBAFABB6E13EC1CC20D995547600DB0")); 137 | EXPECT_EQ(compute_turboshake256_output<0x23>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("bd8f3f5eae3fb4ba604ad2d9d9431867532ab1e2f773819620b79281e3258bbc")); 138 | EXPECT_EQ(compute_turboshake256_output<0x3a>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("cfa491078479604fd78e967071a081cf357a1244d2999c929c318782a24d7c21")); 139 | EXPECT_EQ(compute_turboshake256_output<0x51>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("b92a11dd21017255a8285bbdf413269dcfae55f79d188a55cc2e04ea667bc047")); 140 | EXPECT_EQ(compute_turboshake256_output<0x68>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("229acb8530b6e700bebb304655a5dfad00f7ac4ab7f582ee909c11b96f6d5fb3")); 141 | EXPECT_EQ(compute_turboshake256_output<0x7f>({}, 32), sha3_test_utils::parse_dynamic_sized_hex_string("49b38a11204328440c4c40fdaee305629379936d7a31f9474c4f0fb062a2a427")); 142 | 143 | EXPECT_EQ(compute_turboshake256_output<0x01>({}, 64), sha3_test_utils::parse_dynamic_sized_hex_string("e3dd2df0943bde6d82e39ec36059f35cd76720e2df38cc6b10b69fddfcaa3a4a72fbbbe42c00ced7aa88e26d4675dd6e2c43c4413c4ea4d44bb170f03a981cab")); 144 | EXPECT_EQ(compute_turboshake256_output<0x1f>({}, 64), sha3_test_utils::parse_dynamic_sized_hex_string("367A329DAFEA871C7802EC67F905AE13C57695DC2C6663C61035F59A18F8E7DB11EDC0E12E91EA60EB6B32DF06DD7F002FBAFABB6E13EC1CC20D995547600DB0")); 145 | 146 | EXPECT_EQ(compute_turboshake256_output<0x1f>(sha3_test_utils::ptn(1), 64), sha3_test_utils::parse_dynamic_sized_hex_string("3E1712F928F8EAF1054632B2AA0A246ED8B0C378728F60BC970410155C28820E90CC90D8A3006AA2372C5C5EA176B0682BF22BAE7467AC94F74D43D39B0482E2")); 147 | EXPECT_EQ(compute_turboshake256_output<0x1f>(sha3_test_utils::ptn(17), 64), sha3_test_utils::parse_dynamic_sized_hex_string("B3BAB0300E6A191FBE6137939835923578794EA54843F5011090FA2F3780A9E5CB22C59D78B40A0FBFF9E672C0FBE0970BD2C845091C6044D687054DA5D8E9C7")); 148 | EXPECT_EQ(compute_turboshake256_output<0x1f>(sha3_test_utils::ptn(17 * 17), 64), sha3_test_utils::parse_dynamic_sized_hex_string("66B810DB8E90780424C0847372FDC95710882FDE31C6DF75BEB9D4CD9305CFCAE35E7B83E8B7E6EB4B78605880116316FE2C078A09B94AD7B8213C0A738B65C0")); 149 | EXPECT_EQ(compute_turboshake256_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17), 64), sha3_test_utils::parse_dynamic_sized_hex_string("C74EBC919A5B3B0DD1228185BA02D29EF442D69D3D4276A93EFE0BF9A16A7DC0CD4EABADAB8CD7A5EDD96695F5D360ABE09E2C6511A3EC397DA3B76B9E1674FB")); 150 | EXPECT_EQ(compute_turboshake256_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17 * 17), 64), sha3_test_utils::parse_dynamic_sized_hex_string("02CC3A8897E6F4F6CCB6FD46631B1F5207B66C6DE9C7B55B2D1A23134A170AFDAC234EABA9A77CFF88C1F020B73724618C5687B362C430B248CD38647F848A1D")); 151 | EXPECT_EQ(compute_turboshake256_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17 * 17 * 17), 64), sha3_test_utils::parse_dynamic_sized_hex_string("ADD53B06543E584B5823F626996AEE50FE45ED15F20243A7165485ACB4AA76B4FFDA75CEDF6D8CDC95C332BD56F4B986B58BB17D1778BFC1B1A97545CDF4EC9F")); 152 | EXPECT_EQ(compute_turboshake256_output<0x1f>(sha3_test_utils::ptn(17 * 17 * 17 * 17 * 17 * 17), 64), sha3_test_utils::parse_dynamic_sized_hex_string("9E11BC59C24E73993C1484EC66358EF71DB74AEFD84E123F7800BA9C4853E02CFE701D9E6BB765A304F0DC34A4EE3BA82C410F0DA70E86BFBD90EA877C2D6104")); 153 | 154 | EXPECT_EQ(compute_turboshake256_output<0x01>(sha3_test_utils::ptn(1), 32), sha3_test_utils::parse_dynamic_sized_hex_string("73ebf1d543d855a3c5e4be6322f75604c254f70394b396884b6010fcca694722")); 155 | EXPECT_EQ(compute_turboshake256_output<0x01>(sha3_test_utils::ptn(17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("1da47d188755b75307a242a8f2675bbd76aebf8a13b1d40f587a0732cbb3dc3d")); 156 | EXPECT_EQ(compute_turboshake256_output<0x01>(sha3_test_utils::ptn(17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("a48c938770f916b09d764e29e2279b90d5fa3dd0e006ee8d6c2eb0db8893525e")); 157 | EXPECT_EQ(compute_turboshake256_output<0x01>(sha3_test_utils::ptn(17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("75e8668d3a46baa7c75c3ac7d33fc2c218df38cdf0f8d70352a495bd9d5d6dfa")); 158 | EXPECT_EQ(compute_turboshake256_output<0x01>(sha3_test_utils::ptn(17 * 17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("ffa49653e40c7ba33f11c278d99be3010f65446a7bf8a69d70b07feb54e7107c")); 159 | EXPECT_EQ(compute_turboshake256_output<0x01>(sha3_test_utils::ptn(17 * 17 * 17 * 17 * 17), 32), sha3_test_utils::parse_dynamic_sized_hex_string("2ad2b3beb8671840fa9d5e8f7faf2d1139d99483f3c4e56a6a25553f83c25931")); 160 | 161 | EXPECT_EQ(compute_turboshake256_output<0x01>({ 0xff, 0xff, 0xff }, 64), sha3_test_utils::parse_dynamic_sized_hex_string("D21C6FBBF587FA2282F29AEA620175FB0257413AF78A0B1B2A87419CE031D933AE7A4D383327A8A17641A34F8A1D1003AD7DA6B72DBA84BB62FEF28F62F12424")); 162 | EXPECT_EQ(compute_turboshake256_output<0x06>({ 0xff },64), sha3_test_utils::parse_dynamic_sized_hex_string("738D7B4E37D18B7F22AD1B5313E357E3DD7D07056A26A303C433FA3533455280F4F5A7D4F700EFB437FE6D281405E07BE32A0A972E22E63ADC1B090DAEFE004B")); 163 | EXPECT_EQ(compute_turboshake256_output<0x07>({ 0xff, 0xff, 0xff }, 64), sha3_test_utils::parse_dynamic_sized_hex_string("18B3B5B7061C2E67C1753A00E6AD7ED7BA1C906CF93EFB7092EAF27FBEEBB755AE6E292493C110E48D260028492B8E09B5500612B8F2578985DED5357D00EC67")); 164 | EXPECT_EQ(compute_turboshake256_output<0x0b>({ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, 64), sha3_test_utils::parse_dynamic_sized_hex_string("BB36764951EC97E9D85F7EE9A67A7718FC005CF42556BE79CE12C0BDE50E5736D6632B0D0DFB202D1BBB8FFE3DD74CB00834FA756CB03471BAB13A1E2C16B3C0")); 165 | EXPECT_EQ(compute_turboshake256_output<0x30>({ 0xff }, 64), sha3_test_utils::parse_dynamic_sized_hex_string("F3FE12873D34BCBB2E608779D6B70E7F86BEC7E90BF113CBD4FDD0C4E2F4625E148DD7EE1A52776CF77F240514D9CCFC3B5DDAB8EE255E39EE389072962C111A")); 166 | EXPECT_EQ(compute_turboshake256_output<0x7f>({ 0xff, 0xff, 0xff }, 64), sha3_test_utils::parse_dynamic_sized_hex_string("ABE569C1F77EC340F02705E7D37C9AB7E155516E4A6A150021D70B6FAC0BB40C069F9A9828A0D575CD99F9BAE435AB1ACF7ED9110BA97CE0388D074BAC768776")); 167 | 168 | { 169 | auto out_bytes = compute_turboshake256_output<0x01>({}, 10032); 170 | auto out_bytes_span = std::span(out_bytes); 171 | 172 | EXPECT_TRUE(std::equal(out_bytes_span.last<32>().begin(), out_bytes_span.last<32>().end(), sha3_test_utils::parse_dynamic_sized_hex_string("b021b244dcd9599966d7742225fc7372639233f0ff0863fa79683ebf1f57114f").begin())); 173 | } 174 | 175 | { 176 | auto out_bytes = compute_turboshake256_output<0x1f>({}, 10032); 177 | auto out_bytes_span = std::span(out_bytes); 178 | 179 | EXPECT_TRUE(std::equal(out_bytes_span.last<32>().begin(), out_bytes_span.last<32>().end(), sha3_test_utils::parse_dynamic_sized_hex_string("ABEFA11630C661269249742685EC082F207265DCCF2F43534E9C61BA0C9D1D75").begin())); 180 | } 181 | // clang-format on 182 | } 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sha3 2 | 3 | `constexpr` SHA3: Keccak Permutation-based Hash and Extendable-Output Functions. 4 | 5 | > [!NOTE] 6 | > `constexpr`? Yes, you can evaluate message digest (MD) of any message, during program compilation time itself, given that the message is known at compile-time. This can be useful if you have to embed a message digest in program as a constant. Instead of hardcoding the MD as constant, the compiler can compute it for you. And you can run a `static_assert` against the computed value. 7 | 8 | ## Overview 9 | 10 | SHA3 standard i.e. NIST FIPS 202, specifies four permutation-based hash functions and two eXtendable Output Functions (XOF), which are built on top of 24-rounds keccak-p[1600, 24] permutation. In IETF RFC 9861, two additional XOFs are defined based on 12-rounds keccak-p[1600, 12] permutation. The round reduced keccak permutation almost doubles the performance of TurboSHAKE compared to the original SHAKE XOFs. 11 | 12 | These hash functions and extendable output functions are commonly used in various post-quantum cryptography algorithms (i.e. those used for public key encryption, key establishment mechanism and digital signature). Some of which are already standardized (e.g. ML-KEM, ML-DSA, SLH-DSA etc.) by NIST, some are waiting to be standardized (e.g. FN-DSA) or some are still competing. We implement SHA3 specification as a **header-only fully constexpr C++ library**, so that we can use it as a modular dependency (say pinned to a specific commit using git submodule) in libraries, where we implement various PQC schemes. We follow NIST FIPS 202 @ and RFC 9861 @ . 13 | 14 | Following algorithms (with flexible interfaces) are implemented in `sha3` library. 15 | 16 | Algorithm | Input | Output | Behaviour | Namespace + Header 17 | --- | :-: | :-: | :-: | --: 18 | SHA3-224 | N ( >=0 ) -bytes message | 28 -bytes digest | Given N -bytes input message, this routine computes 28 -bytes sha3-224 digest, while *(incrementally)* consuming message into Keccak[448] sponge. | [`sha3_224::sha3_224_t`](./include/sha3/sha3_224.hpp) 19 | SHA3-256 | N ( >=0 ) -bytes message | 32 -bytes digest | Given N -bytes input message, this routine computes 32 -bytes sha3-256 digest, while *(incrementally)* consuming message into Keccak[512] sponge. | [`sha3_256::sha3_256_t`](./include/sha3/sha3_256.hpp) 20 | SHA3-384 | N ( >=0 ) -bytes message | 48 -bytes digest | Given N -bytes input message, this routine computes 48 -bytes sha3-384 digest, while *(incrementally)* consuming message into Keccak[768] sponge. | [`sha3_384::sha3_384_t`](./include/sha3/sha3_384.hpp) 21 | SHA3-512 | N ( >=0 ) -bytes message | 64 -bytes digest | Given N -bytes input message, this routine computes 64 -bytes sha3-512 digest, while *(incrementally)* consuming message into Keccak[1024] sponge. | [`sha3_512::sha3_512_t`](./include/sha3/sha3_512.hpp) 22 | SHAKE128 | N ( >=0 ) -bytes message | M ( >=0 ) -bytes output | Given N -bytes input message, this routine squeezes arbitrary ( = M ) number of output bytes from Keccak[256] sponge, which has already *(incrementally)* absorbed input bytes. | [`shake128::shake128_t`](./include/sha3/shake128.hpp) 23 | SHAKE256 | N ( >=0 ) -bytes message | M ( >=0 ) -bytes digest | Given N -bytes input message, this routine squeezes arbitrary ( = M ) number of output bytes from Keccak[512] sponge, which has already *(incrementally)* absorbed input bytes. | [`shake256::shake256_t`](./include/sha3/shake256.hpp) 24 | TurboSHAKE128 | N ( >=0 ) -bytes message | M ( >=0 ) -bytes output | Given N -bytes input message, this routine squeezes arbitrary ( = M ) number of output bytes from Keccak[256] sponge, which has already *(incrementally)* absorbed input bytes. **It is faster than SHAKE128, because it is powered by 12-rounds keccak permutation.** | [`turboshake128::turboshake128_t`](./include/sha3/turboshake128.hpp) 25 | TurboSHAKE256 | N ( >=0 ) -bytes message | M ( >=0 ) -bytes output | Given N -bytes input message, this routine squeezes arbitrary ( = M ) number of output bytes from Keccak[512] sponge, which has already *(incrementally)* absorbed input bytes. **It is faster than SHAKE256, because it is powered by 12-rounds keccak permutation.** | [`turboshake256::turboshake256_t`](./include/sha3/turboshake256.hpp) 26 | 27 | XKCP is the state-of-the-art C library implementation, for all common constructions based on keccak permutation. It is available @ . Following screen capture shows a performance comparison of generic and portable TurboSHAKE128 XOF, implemented in this library, against the baseline of XKCP's optimized TurboSHAKE128 implementation. To compare performance of TurboSHAKE128, for both short and long messages, we absorb messages of variable length, starting from 32B to 1GB, with a multiplicative jump factor of 32, while squeezing a fixed length output of 64B. 28 | 29 | Benchmark comparison shows a clear and consistent trend of this portable implementation taking ~(10-15)% more time compared to XKCP. 30 | 31 | > [!IMPORTANT] 32 | > This performance comparison was carried out on a `12th Gen Intel(R) Core(TM) i7-1260P` machine, running `Linux 6.17.0-6-generic` kernel. The benchmark executable was compiled using `GCC-15.2.0`, with optimization options `-O3 -march=native -flto`. For XKCP, we built the library at git commit `e7a08f7baa3d43d64f5c21e641cb18fe292f2b75`. We used google-benchmark's benchmark comparison tool @ for easily comparing JSON dump. 33 | 34 | ![benchcmp_with_xkcp_turboshake128_on_x86_64.png](./benchcmp_with_xkcp_turboshake128_on_x86_64.png) 35 | 36 | Ideally, one should just go ahead with XKCP. Though, one edge this portable implementation of `sha3` has over XKCP - it is fully `constexpr`. In following code snippet, we compute SHA3-256 message digest (MD) of a compile-time known string, in program compilation time itself. The MD is checked using a static assertion. This header-only lightweight library allows you to embed SHA3-256 `MD` as a constant, which is computed by the compiler. It's not hard-coded. 37 | 38 | ```cpp 39 | #include "sha3/sha3_256.hpp" 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | template 46 | constexpr std::array 47 | string_to_bytes(std::string_view sv) 48 | { 49 | std::array arr{}; 50 | 51 | for (size_t i = 0; i < sv.size(); i++) { 52 | arr[i] = static_cast(sv[i]); 53 | } 54 | 55 | return arr; 56 | } 57 | 58 | constexpr std::string_view MSG = "keccak permutation rocks!"; 59 | constexpr auto MSG_bytes = string_to_bytes(MSG); 60 | constexpr auto MD = sha3_256::sha3_256_t::hash(MSG_bytes); 61 | 62 | int 63 | main() 64 | { 65 | // 4edc60a9ffe739ce44252716483a529e8a859a5a75cbf69d494037e914bac16b 66 | constexpr std::array expected_md = { 78, 220, 96, 169, 255, 231, 57, 206, 68, 37, 39, 22, 72, 58, 82, 158, 138, 133, 154, 90, 117, 203, 246, 157, 73, 64, 55, 233, 20, 186, 193, 107 }; 67 | static_assert(MD == expected_md, "Must compute SHA3-256 message digest in program compile-time!"); 68 | 69 | return EXIT_SUCCESS; 70 | } 71 | ``` 72 | 73 | ## Prerequisites 74 | 75 | - A C++ compiler such as `g++`/ `clang++`, with support for C++20 standard library. 76 | 77 | ```bash 78 | $ g++ --version 79 | g++ (Ubuntu 15.2.0-4ubuntu4) 15.2.0 80 | ``` 81 | 82 | - Build tools such as `cmake` and `make`. 83 | 84 | ```bash 85 | $ make --version 86 | GNU Make 4.4.1 87 | 88 | $ cmake --version 89 | cmake version 3.31.6 90 | ``` 91 | 92 | - For testing SHA3 algorithms, you need to globally install `google-test` library and headers. Follow [this](https://github.com/google/googletest/tree/main/googletest#standalone-cmake-project) guide if you haven't installed it yet. 93 | - For benchmarking SHA3 algorithms, targeting CPU systems, `google-benchmark` library and headers are required to be installed system-wide. Follow [this](https://github.com/google/benchmark#installation) guide if you don't have it installed yet. 94 | 95 | > [!NOTE] 96 | > If you are on a machine running GNU/Linux kernel and you want to obtain CPU cycles or Cycles/ byte or instruction/ cycle etc., when benchmarking SHA3 algorithms, you should consider building `google-benchmark` library yourself with `libPFM` support, following the step-by-step guide @ . Find more about libPFM @ . 97 | 98 | > [!TIP] 99 | > Git submodule based dependencies will generally be imported automatically, but in case that doesn't work, you can manually bring them in by issuing `$ git submodule update --init` from inside the root of this repository. 100 | 101 | ## Testing 102 | 103 | For ensuring that SHA3 hash function and extendable output function implementations are correct & conformant to the NIST FIPS 202, we make use of K(nown) A(nswer) T(ests), generated following the gist @ . For TurboSHAKE, we use test vectors defined in IETF RFC 9861. 104 | 105 | We also test correctness of 106 | 107 | - Incremental message absorption property of SHA3 hash functions and XOFs. 108 | - Incremental output squeezing property of SHA3 XOFs. 109 | 110 | Some compile-time executed tests ( using `static_assert` ) are also implemented, which ensure that all SHA3 hash functions and XOFs are `constexpr` - meaning they can be evaluated during compilation-time for any statically defined input message. 111 | 112 | Issue following command for running all the test cases. 113 | 114 | ```bash 115 | # Shows help message - which targets are available and what do each of them do 116 | make 117 | # or 118 | make help 119 | 120 | make test -j 121 | make debug_asan_test -j 122 | make debug_ubsan_test -j 123 | make release_asan_test -j 124 | make release_ubsan_test -j 125 | 126 | # Specify which compiler to use 127 | CXX=clang++ make test -j 128 | ``` 129 | 130 | ```bash 131 | PASSED TESTS (24/24): 132 | 3 ms: build/test/test.out Sha3XOF.CompileTimeEvalTurboSHAKE128 133 | 3 ms: build/test/test.out Sha3XOF.CompileTimeEvalTurboSHAKE256 134 | 4 ms: build/test/test.out Sha3Hashing.CompileTimeEvalSha3_224 135 | 4 ms: build/test/test.out Sha3Hashing.CompileTimeEvalSha3_512 136 | 4 ms: build/test/test.out Sha3XOF.CompileTimeEvalSHAKE128 137 | 4 ms: build/test/test.out Sha3XOF.CompileTimeEvalSHAKE256 138 | 5 ms: build/test/test.out Sha3Hashing.CompileTimeEvalSha3_384 139 | 6 ms: build/test/test.out Sha3Hashing.CompileTimeEvalSha3_256 140 | 12 ms: build/test/test.out Sha3Hashing.Sha3_256IncrementalAbsorption 141 | 12 ms: build/test/test.out Sha3Hashing.Sha3_512IncrementalAbsorption 142 | 12 ms: build/test/test.out Sha3Hashing.Sha3_384IncrementalAbsorption 143 | 15 ms: build/test/test.out Sha3Hashing.Sha3_224IncrementalAbsorption 144 | 20 ms: build/test/test.out Sha3XOF.SHAKE128KnownAnswerTests 145 | 20 ms: build/test/test.out Sha3XOF.SHAKE256KnownAnswerTests 146 | 21 ms: build/test/test.out Sha3Hashing.Sha3_256KnownAnswerTests 147 | 23 ms: build/test/test.out Sha3Hashing.Sha3_224KnownAnswerTests 148 | 23 ms: build/test/test.out Sha3Hashing.Sha3_384KnownAnswerTests 149 | 28 ms: build/test/test.out Sha3Hashing.Sha3_512KnownAnswerTests 150 | 99 ms: build/test/test.out Sha3XOF.TurboSHAKE256KnownAnswerTests 151 | 103 ms: build/test/test.out Sha3XOF.TurboSHAKE128KnownAnswerTests 152 | 1840 ms: build/test/test.out Sha3XOF.TurboSHAKE128IncrementalAbsorptionAndSqueezing 153 | 1894 ms: build/test/test.out Sha3XOF.TurboSHAKE256IncrementalAbsorptionAndSqueezing 154 | 2054 ms: build/test/test.out Sha3XOF.SHAKE128IncrementalAbsorptionAndSqueezing 155 | 2193 ms: build/test/test.out Sha3XOF.SHAKE256IncrementalAbsorptionAndSqueezing 156 | ``` 157 | 158 | ## Benchmarking 159 | 160 | For benchmarking SHA3 hash and extendable output functions, targeting CPU systems, using `google-benchmark`, issue following command. 161 | 162 | > [!CAUTION] 163 | > You must disable CPU frequency scaling during benchmarking, following [this](https://github.com/google/benchmark/blob/4931aefb51d1e5872b096a97f43e13fa0fc33c8c/docs/reducing_variance.md) guide. 164 | 165 | > [!NOTE] 166 | > When benchmarking extendable output functions ( XOFs ), fixed length output of 32/ 64 -bytes are squeezed from sponge ( s.t. all output bytes are requested in a single call to the `squeeze` function ), for input message byte array of length N s.t. N = 2^i (i.e. power of 2). 167 | 168 | ```bash 169 | make perf -j # You must issue this if you built your google-benchmark library with libPFM support. 170 | make benchmark -j # Else you have to issue this one. 171 | ``` 172 | 173 | ### On 12th Gen Intel(R) Core(TM) i7-1260P 174 | 175 | Compiled with `g++ (Ubuntu 15.2.0-4ubuntu4) 15.2.0` while running on `Linux 6.17.0-6-generic x86_64`. 176 | 177 | We maintain benchmark results in JSON format @ [bench_result_at_commit_79994d4_on_Linux_6.17.0-6-generic_x86_64_with_g++_15](./bench_result_at_commit_79994d4_on_Linux_6.17.0-6-generic_x86_64_with_g++_15.json). 178 | 179 | ### On Apple M1 Max 180 | 181 | Compiled with `Apple Clang version 16.0.0` while running kernel `Darwin 24.1.0 arm64`. 182 | 183 | Maintaining benchmark results in JSON format @ [bench_result_on_Darwin_24.3.0_arm64_with_c++_16.0.0](./bench_result_on_Darwin_24.3.0_arm64_with_c++_16.0.0.json). 184 | 185 | ## Usage 186 | 187 | `sha3` - C++ header-only library is written such that it's very easy to get started with. All one needs to do 188 | 189 | - Include proper header files (select which scheme you need by name). 190 | - Use proper struct(s)/ API(s)/ constant(s) (see [usage examples](./examples) or [test cases](./tests/)). 191 | - When compiling, let your compiler know where it can find respective header files, which is `./include` directory. 192 | 193 | Scheme | Header | Namespace | Example 194 | --- | --- | --- | --: 195 | SHA3-224 | ./include/sha3/sha3_224.hpp | `sha3_224::` | [examples/sha3_224.cpp](./examples/sha3_224.cpp) 196 | SHA3-256 | ./include/sha3/sha3_256.hpp | `sha3_256::` | [examples/sha3_256.cpp](./examples/sha3_256.cpp) 197 | SHA3-384 | ./include/sha3/sha3_384.hpp | `sha3_384::` | [examples/sha3_384.cpp](./examples/sha3_384.cpp) 198 | SHA3-512 | ./include/sha3/sha3_512.hpp | `sha3_512::` | [examples/sha3_512.cpp](./examples/sha3_512.cpp) 199 | SHAKE128 | ./include/sha3/shake128.hpp | `shake128::` | [examples/shake128.cpp](./examples/shake128.cpp) 200 | SHAKE256 | ./include/sha3/shake256.hpp | `shake256::` | [examples/shake256.cpp](./examples/shake256.cpp) 201 | TurboSHAKE128 | ./include/sha3/turboshake128.hpp | `turboshake128::` | [examples/turboshake128.cpp](./examples/turboshake128.cpp) 202 | TurboSHAKE256 | ./include/sha3/turboshake256.hpp | `turboshake256::` | [examples/turboshake256.cpp](./examples/turboshake256.cpp) 203 | 204 | We maintain couple of examples, showing how to use SHA3 hash functions and XOF API, inside [examples](./examples/) directory. Run them all by issuing. 205 | 206 | ```bash 207 | make example -j 208 | ``` 209 | 210 | ```bash 211 | SHA3-224 212 | 213 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 214 | Message Digest : bfc9c1e8939aee953ca0d425a2f0cbdd2d18025d5d6b798f1c8150b9 215 | --- --- --- 216 | SHA3-256 217 | 218 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 219 | Message Digest : 050a48733bd5c2756ba95c5828cc83ee16fabcd3c086885b7744f84a0f9e0d94 220 | --- --- --- 221 | SHA3-384 222 | 223 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 224 | Message Digest : e086a2b6a69bb6fae37caa70735723e7cc8ae2183788fbb4a5f1ccacd83226852ca6faff503e12ff95423f94f872dda3 225 | --- --- --- 226 | SHA3-512 227 | 228 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 229 | Message Digest : cbd3f6eeba676b21e0f2c47522292482fd830f330c1d84a794bb94728b2d93febe4c18eae5a7e017e35fa090de24262e70951ad1d7dfb3a8c96d1134fb1879f2 230 | --- --- --- 231 | SHAKE128 232 | 233 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 234 | Output : 066a361dc675f856cecdc02b25218a10cec0cecf79859ec0fec3d409e5847a92ba9d4e33d16a3a44 235 | --- --- --- 236 | SHAKE256 237 | 238 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 239 | Output : 69f07c8840ce80024db30939882c3d5bbc9c98b3e31e4513ebd2ca9b4503cdd3c9c90742452c7173 240 | --- --- --- 241 | TurboSHAKE128 242 | 243 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 244 | Output : f433704a62d1b17fd5ad80e9ba281fbdf2579b84bd941ea2748c10973d7458a212c4ab868d09af4f 245 | --- --- --- 246 | TurboSHAKE256 247 | 248 | Message : 000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 249 | Output : 4d5596cae904a6171715e08defa88f81dc7676c9f63b48740bcfbb6d932b1377a1414490f39cfcf1 250 | --- --- --- 251 | ``` 252 | 253 | > [!NOTE] 254 | > This library doesn't expose any raw pointer + length -based interface, rather everything is wrapped under much safer `std::span` - which one can easily create from `std::{array, vector}` or even raw pointers and length pair. See . We made this choice because this gives us much better type safety and compile-time error reporting. 255 | -------------------------------------------------------------------------------- /include/sha3/internals/keccak.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "sha3/internals/force_inline.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // Keccak-p[1600, 12] and Keccak-p[1600, 24] (aka Keccak-f[1600]) permutation 10 | namespace keccak { 11 | 12 | // Logarithmic base 2 of bit width of lane i.e. log2(LANE_BW) 13 | static constexpr size_t L = 6; 14 | 15 | // Bit width of each lane of Keccak-f[1600] state 16 | static constexpr size_t LANE_BW = 1ul << L; 17 | 18 | // Bit length of Keccak-f[1600] permutation state 19 | static constexpr size_t STATE_BIT_LEN = 1600; 20 | 21 | // Byte length of Keccak-f[1600] permutation state 22 | static constexpr size_t STATE_BYTE_LEN = STATE_BIT_LEN / std::numeric_limits::digits; 23 | 24 | // # -of lanes (each of 64 -bit width) in Keccak-f[1600] state 25 | static constexpr size_t LANE_CNT = STATE_BIT_LEN / LANE_BW; 26 | 27 | // Maximum number of rounds Keccak-p[b, nr] permutation can be applied s.t. b = 1600, w = b/ 25, l = log2(w), nr = 12 + 2l. 28 | static constexpr size_t MAX_NUM_ROUNDS = 12 + 2 * L; 29 | 30 | /** 31 | * Leftwards circular rotation offset of 25 lanes ( each lane is 64 -bit wide ) of state array, as provided in table 2 32 | * below algorithm 2 in section 3.2.2 of https://dx.doi.org/10.6028/NIST.FIPS.202. 33 | * 34 | * Note, following offsets are obtained by performing % 64 (bit width of lane) on offsets provided in above mentioned link. 35 | */ 36 | static constexpr size_t ROT[LANE_CNT]{ 0 % LANE_BW, 1 % LANE_BW, 190 % LANE_BW, 28 % LANE_BW, 91 % LANE_BW, 36 % LANE_BW, 300 % LANE_BW, 37 | 6 % LANE_BW, 55 % LANE_BW, 276 % LANE_BW, 3 % LANE_BW, 10 % LANE_BW, 171 % LANE_BW, 153 % LANE_BW, 38 | 231 % LANE_BW, 105 % LANE_BW, 45 % LANE_BW, 15 % LANE_BW, 21 % LANE_BW, 136 % LANE_BW, 210 % LANE_BW, 39 | 66 % LANE_BW, 253 % LANE_BW, 120 % LANE_BW, 78 % LANE_BW }; 40 | 41 | /** 42 | * Precomputed table used for looking up source index during application of `π` step mapping function on Keccak-f[1600] state. 43 | * print('to <= from') 44 | * for y in range(5): 45 | * for x in range(5): 46 | * print(f'{y * 5 + x} <= {x * 5 + (x + 3 * y) % 5}') 47 | * 48 | * Table generated using above Python code snippet. See section 3.2.3 of the specification https://dx.doi.org/10.6028/NIST.FIPS.202. 49 | */ 50 | static constexpr size_t PERM[LANE_CNT]{ 0, 6, 12, 18, 24, 3, 9, 10, 16, 22, 1, 7, 13, 19, 20, 4, 5, 11, 17, 23, 2, 8, 14, 15, 21 }; 51 | 52 | /** 53 | * Computes single bit of Keccak-f[1600] round constant (at compile-time), using binary LFSR, defined by primitive polynomial `x^8 + x^6 + x^5 + x^4 + 1`. 54 | * 55 | * See algorithm 5 in section 3.2.5 of http://dx.doi.org/10.6028/NIST.FIPS.202. 56 | * 57 | * Taken from https://github.com/itzmeanjan/elephant/blob/2a21c7e/include/keccak.hpp#L24-L59. 58 | */ 59 | static consteval bool 60 | rc(const size_t t) 61 | { 62 | // Step 1 of algorithm 5 63 | if (t % 255 == 0) { 64 | return 1; 65 | } 66 | 67 | // Step 2 of algorithm 5 68 | // 69 | // Note, step 3.a of algorithm 5 is also being executed in this statement ( for first iteration, with i = 1 ) ! 70 | uint16_t r = 0b10000000; 71 | 72 | // Step 3 of algorithm 5 73 | for (size_t i = 1; i <= t % 255; i++) { 74 | const uint16_t b0 = r & 1; 75 | 76 | r = (r & 0b011111111) ^ ((((r >> 8) & 1) ^ b0) << 8); 77 | r = (r & 0b111101111) ^ ((((r >> 4) & 1) ^ b0) << 4); 78 | r = (r & 0b111110111) ^ ((((r >> 3) & 1) ^ b0) << 3); 79 | r = (r & 0b111111011) ^ ((((r >> 2) & 1) ^ b0) << 2); 80 | 81 | // Step 3.f of algorithm 5 82 | // 83 | // Note, this statement also executes step 3.a for upcoming iterations ( i.e. when i > 1 ) 84 | r >>= 1; 85 | } 86 | 87 | return static_cast((r >> 7) & 1); 88 | } 89 | 90 | /** 91 | * Computes 64 -bit round constant (at compile-time), which is XOR-ed into the very first lane ( = lane(0, 0) ) of Keccak-f[1600] permutation state. 92 | * 93 | * Taken from https://github.com/itzmeanjan/elephant/blob/2a21c7e/include/keccak.hpp#L61-L74. 94 | */ 95 | static consteval uint64_t 96 | compute_rc(const size_t r_idx) 97 | { 98 | uint64_t tmp = 0; 99 | 100 | for (size_t j = 0; j < (L + 1); j++) { 101 | const size_t boff = (1 << j) - 1; 102 | tmp |= static_cast(rc(j + 7 * r_idx)) << boff; 103 | } 104 | 105 | return tmp; 106 | } 107 | 108 | // Compile-time evaluate Keccak-f[1600] round constants. 109 | static consteval std::array 110 | compute_rcs() 111 | { 112 | std::array res{}; 113 | 114 | for (size_t i = 0; i < MAX_NUM_ROUNDS; i++) { 115 | res[i] = compute_rc(i); 116 | } 117 | 118 | return res; 119 | } 120 | 121 | // Round constants to be XORed with lane (0, 0) of Keccak-f[1600] permutation state. See section 3.2.5 of https://dx.doi.org/10.s6028/NIST.FIPS.202. 122 | static constexpr std::array RC = compute_rcs(); 123 | 124 | /** 125 | * Keccak-f[1600] round function, applying all five step mapping functions, updating state array. 126 | * Note this implementation of round function applies four consecutive rounds in a single call i.e. if you invoke it to apply round `i` 127 | * 128 | * - It first applies round `i` 129 | * - Then round `i+1` 130 | * - And then round `i+2` 131 | * - And finally round `i+3` 132 | * 133 | * See section 3.3 of https://dx.doi.org/10.6028/NIST.FIPS.202. 134 | * This implementation collects a lot of inspiration from https://github.com/bwesterb/armed-keccak.git. 135 | */ 136 | static forceinline constexpr void 137 | roundx4(uint64_t* const state, const size_t ridx) 138 | { 139 | std::array bc{}, d{}; 140 | uint64_t t = 0; 141 | 142 | // Round ridx + 0 143 | #if defined __clang__ 144 | // Following https://clang.llvm.org/docs/LanguageExtensions.html#extensions-for-loop-hint-optimizations 145 | 146 | #pragma clang loop unroll(enable) 147 | #pragma clang loop vectorize(enable) 148 | #pragma clang loop interleave(enable) 149 | #elif defined __GNUG__ 150 | // Following https://gcc.gnu.org/onlinedocs/gcc/Loop-Specific-Pragmas.html#Loop-Specific-Pragmas 151 | 152 | #pragma GCC unroll 5 153 | #pragma GCC ivdep 154 | #endif 155 | for (size_t i = 0; i < 25; i += 5) { 156 | bc[0] ^= state[i + 0]; 157 | bc[1] ^= state[i + 1]; 158 | bc[2] ^= state[i + 2]; 159 | bc[3] ^= state[i + 3]; 160 | bc[4] ^= state[i + 4]; 161 | } 162 | 163 | d[0] = bc[4] ^ std::rotl(bc[1], 1); 164 | d[1] = bc[0] ^ std::rotl(bc[2], 1); 165 | d[2] = bc[1] ^ std::rotl(bc[3], 1); 166 | d[3] = bc[2] ^ std::rotl(bc[4], 1); 167 | d[4] = bc[3] ^ std::rotl(bc[0], 1); 168 | 169 | bc[0] = state[0] ^ d[0]; 170 | t = state[6] ^ d[1]; 171 | bc[1] = std::rotl(t, ROT[6]); 172 | t = state[12] ^ d[2]; 173 | bc[2] = std::rotl(t, ROT[12]); 174 | t = state[18] ^ d[3]; 175 | bc[3] = std::rotl(t, ROT[18]); 176 | t = state[24] ^ d[4]; 177 | bc[4] = std::rotl(t, ROT[24]); 178 | 179 | state[0] = bc[0] ^ (bc[2] & ~bc[1]) ^ RC[ridx]; 180 | state[6] = bc[1] ^ (bc[3] & ~bc[2]); 181 | state[12] = bc[2] ^ (bc[4] & ~bc[3]); 182 | state[18] = bc[3] ^ (bc[0] & ~bc[4]); 183 | state[24] = bc[4] ^ (bc[1] & ~bc[0]); 184 | 185 | t = state[10] ^ d[0]; 186 | bc[2] = std::rotl(t, ROT[10]); 187 | t = state[16] ^ d[1]; 188 | bc[3] = std::rotl(t, ROT[16]); 189 | t = state[22] ^ d[2]; 190 | bc[4] = std::rotl(t, ROT[22]); 191 | t = state[3] ^ d[3]; 192 | bc[0] = std::rotl(t, ROT[3]); 193 | t = state[9] ^ d[4]; 194 | bc[1] = std::rotl(t, ROT[9]); 195 | 196 | state[10] = bc[0] ^ (bc[2] & ~bc[1]); 197 | state[16] = bc[1] ^ (bc[3] & ~bc[2]); 198 | state[22] = bc[2] ^ (bc[4] & ~bc[3]); 199 | state[3] = bc[3] ^ (bc[0] & ~bc[4]); 200 | state[9] = bc[4] ^ (bc[1] & ~bc[0]); 201 | 202 | t = state[20] ^ d[0]; 203 | bc[4] = std::rotl(t, ROT[20]); 204 | t = state[1] ^ d[1]; 205 | bc[0] = std::rotl(t, ROT[1]); 206 | t = state[7] ^ d[2]; 207 | bc[1] = std::rotl(t, ROT[7]); 208 | t = state[13] ^ d[3]; 209 | bc[2] = std::rotl(t, ROT[13]); 210 | t = state[19] ^ d[4]; 211 | bc[3] = std::rotl(t, ROT[19]); 212 | 213 | state[20] = bc[0] ^ (bc[2] & ~bc[1]); 214 | state[1] = bc[1] ^ (bc[3] & ~bc[2]); 215 | state[7] = bc[2] ^ (bc[4] & ~bc[3]); 216 | state[13] = bc[3] ^ (bc[0] & ~bc[4]); 217 | state[19] = bc[4] ^ (bc[1] & ~bc[0]); 218 | 219 | t = state[5] ^ d[0]; 220 | bc[1] = std::rotl(t, ROT[5]); 221 | t = state[11] ^ d[1]; 222 | bc[2] = std::rotl(t, ROT[11]); 223 | t = state[17] ^ d[2]; 224 | bc[3] = std::rotl(t, ROT[17]); 225 | t = state[23] ^ d[3]; 226 | bc[4] = std::rotl(t, ROT[23]); 227 | t = state[4] ^ d[4]; 228 | bc[0] = std::rotl(t, ROT[4]); 229 | 230 | state[5] = bc[0] ^ (bc[2] & ~bc[1]); 231 | state[11] = bc[1] ^ (bc[3] & ~bc[2]); 232 | state[17] = bc[2] ^ (bc[4] & ~bc[3]); 233 | state[23] = bc[3] ^ (bc[0] & ~bc[4]); 234 | state[4] = bc[4] ^ (bc[1] & ~bc[0]); 235 | 236 | t = state[15] ^ d[0]; 237 | bc[3] = std::rotl(t, ROT[15]); 238 | t = state[21] ^ d[1]; 239 | bc[4] = std::rotl(t, ROT[21]); 240 | t = state[2] ^ d[2]; 241 | bc[0] = std::rotl(t, ROT[2]); 242 | t = state[8] ^ d[3]; 243 | bc[1] = std::rotl(t, ROT[8]); 244 | t = state[14] ^ d[4]; 245 | bc[2] = std::rotl(t, ROT[14]); 246 | 247 | state[15] = bc[0] ^ (bc[2] & ~bc[1]); 248 | state[21] = bc[1] ^ (bc[3] & ~bc[2]); 249 | state[2] = bc[2] ^ (bc[4] & ~bc[3]); 250 | state[8] = bc[3] ^ (bc[0] & ~bc[4]); 251 | state[14] = bc[4] ^ (bc[1] & ~bc[0]); 252 | 253 | // Round ridx + 1 254 | std::fill(bc.begin(), bc.end(), 0x00); 255 | 256 | #if defined __clang__ 257 | #pragma clang loop unroll(enable) 258 | #pragma clang loop vectorize(enable) 259 | #pragma clang loop interleave(enable) 260 | #elif defined __GNUG__ 261 | #pragma GCC unroll 5 262 | #pragma GCC ivdep 263 | #endif 264 | for (size_t i = 0; i < 25; i += 5) { 265 | bc[0] ^= state[i + 0]; 266 | bc[1] ^= state[i + 1]; 267 | bc[2] ^= state[i + 2]; 268 | bc[3] ^= state[i + 3]; 269 | bc[4] ^= state[i + 4]; 270 | } 271 | 272 | d[0] = bc[4] ^ std::rotl(bc[1], 1); 273 | d[1] = bc[0] ^ std::rotl(bc[2], 1); 274 | d[2] = bc[1] ^ std::rotl(bc[3], 1); 275 | d[3] = bc[2] ^ std::rotl(bc[4], 1); 276 | d[4] = bc[3] ^ std::rotl(bc[0], 1); 277 | 278 | bc[0] = state[0] ^ d[0]; 279 | t = state[16] ^ d[1]; 280 | bc[1] = std::rotl(t, ROT[6]); 281 | t = state[7] ^ d[2]; 282 | bc[2] = std::rotl(t, ROT[12]); 283 | t = state[23] ^ d[3]; 284 | bc[3] = std::rotl(t, ROT[18]); 285 | t = state[14] ^ d[4]; 286 | bc[4] = std::rotl(t, ROT[24]); 287 | 288 | state[0] = bc[0] ^ (bc[2] & ~bc[1]) ^ RC[ridx + 1]; 289 | state[16] = bc[1] ^ (bc[3] & ~bc[2]); 290 | state[7] = bc[2] ^ (bc[4] & ~bc[3]); 291 | state[23] = bc[3] ^ (bc[0] & ~bc[4]); 292 | state[14] = bc[4] ^ (bc[1] & ~bc[0]); 293 | 294 | t = state[20] ^ d[0]; 295 | bc[2] = std::rotl(t, ROT[10]); 296 | t = state[11] ^ d[1]; 297 | bc[3] = std::rotl(t, ROT[16]); 298 | t = state[2] ^ d[2]; 299 | bc[4] = std::rotl(t, ROT[22]); 300 | t = state[18] ^ d[3]; 301 | bc[0] = std::rotl(t, ROT[3]); 302 | t = state[9] ^ d[4]; 303 | bc[1] = std::rotl(t, ROT[9]); 304 | 305 | state[20] = bc[0] ^ (bc[2] & ~bc[1]); 306 | state[11] = bc[1] ^ (bc[3] & ~bc[2]); 307 | state[2] = bc[2] ^ (bc[4] & ~bc[3]); 308 | state[18] = bc[3] ^ (bc[0] & ~bc[4]); 309 | state[9] = bc[4] ^ (bc[1] & ~bc[0]); 310 | 311 | t = state[15] ^ d[0]; 312 | bc[4] = std::rotl(t, ROT[20]); 313 | t = state[6] ^ d[1]; 314 | bc[0] = std::rotl(t, ROT[1]); 315 | t = state[22] ^ d[2]; 316 | bc[1] = std::rotl(t, ROT[7]); 317 | t = state[13] ^ d[3]; 318 | bc[2] = std::rotl(t, ROT[13]); 319 | t = state[4] ^ d[4]; 320 | bc[3] = std::rotl(t, ROT[19]); 321 | 322 | state[15] = bc[0] ^ (bc[2] & ~bc[1]); 323 | state[6] = bc[1] ^ (bc[3] & ~bc[2]); 324 | state[22] = bc[2] ^ (bc[4] & ~bc[3]); 325 | state[13] = bc[3] ^ (bc[0] & ~bc[4]); 326 | state[4] = bc[4] ^ (bc[1] & ~bc[0]); 327 | 328 | t = state[10] ^ d[0]; 329 | bc[1] = std::rotl(t, ROT[5]); 330 | t = state[1] ^ d[1]; 331 | bc[2] = std::rotl(t, ROT[11]); 332 | t = state[17] ^ d[2]; 333 | bc[3] = std::rotl(t, ROT[17]); 334 | t = state[8] ^ d[3]; 335 | bc[4] = std::rotl(t, ROT[23]); 336 | t = state[24] ^ d[4]; 337 | bc[0] = std::rotl(t, ROT[4]); 338 | 339 | state[10] = bc[0] ^ (bc[2] & ~bc[1]); 340 | state[1] = bc[1] ^ (bc[3] & ~bc[2]); 341 | state[17] = bc[2] ^ (bc[4] & ~bc[3]); 342 | state[8] = bc[3] ^ (bc[0] & ~bc[4]); 343 | state[24] = bc[4] ^ (bc[1] & ~bc[0]); 344 | 345 | t = state[5] ^ d[0]; 346 | bc[3] = std::rotl(t, ROT[15]); 347 | t = state[21] ^ d[1]; 348 | bc[4] = std::rotl(t, ROT[21]); 349 | t = state[12] ^ d[2]; 350 | bc[0] = std::rotl(t, ROT[2]); 351 | t = state[3] ^ d[3]; 352 | bc[1] = std::rotl(t, ROT[8]); 353 | t = state[19] ^ d[4]; 354 | bc[2] = std::rotl(t, ROT[14]); 355 | 356 | state[5] = bc[0] ^ (bc[2] & ~bc[1]); 357 | state[21] = bc[1] ^ (bc[3] & ~bc[2]); 358 | state[12] = bc[2] ^ (bc[4] & ~bc[3]); 359 | state[3] = bc[3] ^ (bc[0] & ~bc[4]); 360 | state[19] = bc[4] ^ (bc[1] & ~bc[0]); 361 | 362 | // Round ridx + 2 363 | std::fill(bc.begin(), bc.end(), 0x00); 364 | 365 | #if defined __clang__ 366 | #pragma clang loop unroll(enable) 367 | #pragma clang loop vectorize(enable) 368 | #pragma clang loop interleave(enable) 369 | #elif defined __GNUG__ 370 | #pragma GCC unroll 5 371 | #pragma GCC ivdep 372 | #endif 373 | for (size_t i = 0; i < 25; i += 5) { 374 | bc[0] ^= state[i + 0]; 375 | bc[1] ^= state[i + 1]; 376 | bc[2] ^= state[i + 2]; 377 | bc[3] ^= state[i + 3]; 378 | bc[4] ^= state[i + 4]; 379 | } 380 | 381 | d[0] = bc[4] ^ std::rotl(bc[1], 1); 382 | d[1] = bc[0] ^ std::rotl(bc[2], 1); 383 | d[2] = bc[1] ^ std::rotl(bc[3], 1); 384 | d[3] = bc[2] ^ std::rotl(bc[4], 1); 385 | d[4] = bc[3] ^ std::rotl(bc[0], 1); 386 | 387 | bc[0] = state[0] ^ d[0]; 388 | t = state[11] ^ d[1]; 389 | bc[1] = std::rotl(t, ROT[6]); 390 | t = state[22] ^ d[2]; 391 | bc[2] = std::rotl(t, ROT[12]); 392 | t = state[8] ^ d[3]; 393 | bc[3] = std::rotl(t, ROT[18]); 394 | t = state[19] ^ d[4]; 395 | bc[4] = std::rotl(t, ROT[24]); 396 | 397 | state[0] = bc[0] ^ (bc[2] & ~bc[1]) ^ RC[ridx + 2]; 398 | state[11] = bc[1] ^ (bc[3] & ~bc[2]); 399 | state[22] = bc[2] ^ (bc[4] & ~bc[3]); 400 | state[8] = bc[3] ^ (bc[0] & ~bc[4]); 401 | state[19] = bc[4] ^ (bc[1] & ~bc[0]); 402 | 403 | t = state[15] ^ d[0]; 404 | bc[2] = std::rotl(t, ROT[10]); 405 | t = state[1] ^ d[1]; 406 | bc[3] = std::rotl(t, ROT[16]); 407 | t = state[12] ^ d[2]; 408 | bc[4] = std::rotl(t, ROT[22]); 409 | t = state[23] ^ d[3]; 410 | bc[0] = std::rotl(t, ROT[3]); 411 | t = state[9] ^ d[4]; 412 | bc[1] = std::rotl(t, ROT[9]); 413 | 414 | state[15] = bc[0] ^ (bc[2] & ~bc[1]); 415 | state[1] = bc[1] ^ (bc[3] & ~bc[2]); 416 | state[12] = bc[2] ^ (bc[4] & ~bc[3]); 417 | state[23] = bc[3] ^ (bc[0] & ~bc[4]); 418 | state[9] = bc[4] ^ (bc[1] & ~bc[0]); 419 | 420 | t = state[5] ^ d[0]; 421 | bc[4] = std::rotl(t, ROT[20]); 422 | t = state[16] ^ d[1]; 423 | bc[0] = std::rotl(t, ROT[1]); 424 | t = state[2] ^ d[2]; 425 | bc[1] = std::rotl(t, ROT[7]); 426 | t = state[13] ^ d[3]; 427 | bc[2] = std::rotl(t, ROT[13]); 428 | t = state[24] ^ d[4]; 429 | bc[3] = std::rotl(t, ROT[19]); 430 | 431 | state[5] = bc[0] ^ (bc[2] & ~bc[1]); 432 | state[16] = bc[1] ^ (bc[3] & ~bc[2]); 433 | state[2] = bc[2] ^ (bc[4] & ~bc[3]); 434 | state[13] = bc[3] ^ (bc[0] & ~bc[4]); 435 | state[24] = bc[4] ^ (bc[1] & ~bc[0]); 436 | 437 | t = state[20] ^ d[0]; 438 | bc[1] = std::rotl(t, ROT[5]); 439 | t = state[6] ^ d[1]; 440 | bc[2] = std::rotl(t, ROT[11]); 441 | t = state[17] ^ d[2]; 442 | bc[3] = std::rotl(t, ROT[17]); 443 | t = state[3] ^ d[3]; 444 | bc[4] = std::rotl(t, ROT[23]); 445 | t = state[14] ^ d[4]; 446 | bc[0] = std::rotl(t, ROT[4]); 447 | 448 | state[20] = bc[0] ^ (bc[2] & ~bc[1]); 449 | state[6] = bc[1] ^ (bc[3] & ~bc[2]); 450 | state[17] = bc[2] ^ (bc[4] & ~bc[3]); 451 | state[3] = bc[3] ^ (bc[0] & ~bc[4]); 452 | state[14] = bc[4] ^ (bc[1] & ~bc[0]); 453 | 454 | t = state[10] ^ d[0]; 455 | bc[3] = std::rotl(t, ROT[15]); 456 | t = state[21] ^ d[1]; 457 | bc[4] = std::rotl(t, ROT[21]); 458 | t = state[7] ^ d[2]; 459 | bc[0] = std::rotl(t, ROT[2]); 460 | t = state[18] ^ d[3]; 461 | bc[1] = std::rotl(t, ROT[8]); 462 | t = state[4] ^ d[4]; 463 | bc[2] = std::rotl(t, ROT[14]); 464 | 465 | state[10] = bc[0] ^ (bc[2] & ~bc[1]); 466 | state[21] = bc[1] ^ (bc[3] & ~bc[2]); 467 | state[7] = bc[2] ^ (bc[4] & ~bc[3]); 468 | state[18] = bc[3] ^ (bc[0] & ~bc[4]); 469 | state[4] = bc[4] ^ (bc[1] & ~bc[0]); 470 | 471 | // Round ridx + 3 472 | std::fill(bc.begin(), bc.end(), 0x00); 473 | 474 | #if defined __clang__ 475 | #pragma clang loop unroll(enable) 476 | #pragma clang loop vectorize(enable) 477 | #pragma clang loop interleave(enable) 478 | #elif defined __GNUG__ 479 | #pragma GCC unroll 5 480 | #pragma GCC ivdep 481 | #endif 482 | for (size_t i = 0; i < 25; i += 5) { 483 | bc[0] ^= state[i + 0]; 484 | bc[1] ^= state[i + 1]; 485 | bc[2] ^= state[i + 2]; 486 | bc[3] ^= state[i + 3]; 487 | bc[4] ^= state[i + 4]; 488 | } 489 | 490 | d[0] = bc[4] ^ std::rotl(bc[1], 1); 491 | d[1] = bc[0] ^ std::rotl(bc[2], 1); 492 | d[2] = bc[1] ^ std::rotl(bc[3], 1); 493 | d[3] = bc[2] ^ std::rotl(bc[4], 1); 494 | d[4] = bc[3] ^ std::rotl(bc[0], 1); 495 | 496 | bc[0] = state[0] ^ d[0]; 497 | t = state[1] ^ d[1]; 498 | bc[1] = std::rotl(t, ROT[6]); 499 | t = state[2] ^ d[2]; 500 | bc[2] = std::rotl(t, ROT[12]); 501 | t = state[3] ^ d[3]; 502 | bc[3] = std::rotl(t, ROT[18]); 503 | t = state[4] ^ d[4]; 504 | bc[4] = std::rotl(t, ROT[24]); 505 | 506 | state[0] = bc[0] ^ (bc[2] & ~bc[1]) ^ RC[ridx + 3]; 507 | state[1] = bc[1] ^ (bc[3] & ~bc[2]); 508 | state[2] = bc[2] ^ (bc[4] & ~bc[3]); 509 | state[3] = bc[3] ^ (bc[0] & ~bc[4]); 510 | state[4] = bc[4] ^ (bc[1] & ~bc[0]); 511 | 512 | t = state[5] ^ d[0]; 513 | bc[2] = std::rotl(t, ROT[10]); 514 | t = state[6] ^ d[1]; 515 | bc[3] = std::rotl(t, ROT[16]); 516 | t = state[7] ^ d[2]; 517 | bc[4] = std::rotl(t, ROT[22]); 518 | t = state[8] ^ d[3]; 519 | bc[0] = std::rotl(t, ROT[3]); 520 | t = state[9] ^ d[4]; 521 | bc[1] = std::rotl(t, ROT[9]); 522 | 523 | state[5] = bc[0] ^ (bc[2] & ~bc[1]); 524 | state[6] = bc[1] ^ (bc[3] & ~bc[2]); 525 | state[7] = bc[2] ^ (bc[4] & ~bc[3]); 526 | state[8] = bc[3] ^ (bc[0] & ~bc[4]); 527 | state[9] = bc[4] ^ (bc[1] & ~bc[0]); 528 | 529 | t = state[10] ^ d[0]; 530 | bc[4] = std::rotl(t, ROT[20]); 531 | t = state[11] ^ d[1]; 532 | bc[0] = std::rotl(t, ROT[1]); 533 | t = state[12] ^ d[2]; 534 | bc[1] = std::rotl(t, ROT[7]); 535 | t = state[13] ^ d[3]; 536 | bc[2] = std::rotl(t, ROT[13]); 537 | t = state[14] ^ d[4]; 538 | bc[3] = std::rotl(t, ROT[19]); 539 | 540 | state[10] = bc[0] ^ (bc[2] & ~bc[1]); 541 | state[11] = bc[1] ^ (bc[3] & ~bc[2]); 542 | state[12] = bc[2] ^ (bc[4] & ~bc[3]); 543 | state[13] = bc[3] ^ (bc[0] & ~bc[4]); 544 | state[14] = bc[4] ^ (bc[1] & ~bc[0]); 545 | 546 | t = state[15] ^ d[0]; 547 | bc[1] = std::rotl(t, ROT[5]); 548 | t = state[16] ^ d[1]; 549 | bc[2] = std::rotl(t, ROT[11]); 550 | t = state[17] ^ d[2]; 551 | bc[3] = std::rotl(t, ROT[17]); 552 | t = state[18] ^ d[3]; 553 | bc[4] = std::rotl(t, ROT[23]); 554 | t = state[19] ^ d[4]; 555 | bc[0] = std::rotl(t, ROT[4]); 556 | 557 | state[15] = bc[0] ^ (bc[2] & ~bc[1]); 558 | state[16] = bc[1] ^ (bc[3] & ~bc[2]); 559 | state[17] = bc[2] ^ (bc[4] & ~bc[3]); 560 | state[18] = bc[3] ^ (bc[0] & ~bc[4]); 561 | state[19] = bc[4] ^ (bc[1] & ~bc[0]); 562 | 563 | t = state[20] ^ d[0]; 564 | bc[3] = std::rotl(t, ROT[15]); 565 | t = state[21] ^ d[1]; 566 | bc[4] = std::rotl(t, ROT[21]); 567 | t = state[22] ^ d[2]; 568 | bc[0] = std::rotl(t, ROT[2]); 569 | t = state[23] ^ d[3]; 570 | bc[1] = std::rotl(t, ROT[8]); 571 | t = state[24] ^ d[4]; 572 | bc[2] = std::rotl(t, ROT[14]); 573 | 574 | state[20] = bc[0] ^ (bc[2] & ~bc[1]); 575 | state[21] = bc[1] ^ (bc[3] & ~bc[2]); 576 | state[22] = bc[2] ^ (bc[4] & ~bc[3]); 577 | state[23] = bc[3] ^ (bc[0] & ~bc[4]); 578 | state[24] = bc[4] ^ (bc[1] & ~bc[0]); 579 | } 580 | 581 | /** 582 | * Keccak-f[1600] permutation, applying either 12 or 24 rounds (as requested by template argument) of permutation on state of dimension 5 x 5 x 64 ( = 1600 ) 583 | * -bits, using algorithm 7 defined in section 3.3 of SHA3 specification https://dx.doi.org/10.6028/NIST.FIPS.202. 584 | */ 585 | template 586 | forceinline constexpr void 587 | permute(uint64_t state[LANE_CNT]) 588 | requires((num_rounds == 12) || (num_rounds == MAX_NUM_ROUNDS)) 589 | { 590 | constexpr size_t start_at_round = MAX_NUM_ROUNDS - num_rounds; 591 | constexpr size_t STEP_BY = 4; 592 | 593 | static_assert(num_rounds % STEP_BY == 0, "Requested number of keccak-p[1600] rounds need to be a multiple of 4 for manual unrolling to work."); 594 | 595 | for (size_t i = start_at_round; i < MAX_NUM_ROUNDS; i += STEP_BY) { 596 | roundx4(state, i); 597 | } 598 | } 599 | 600 | } 601 | --------------------------------------------------------------------------------