Botan 3.10.0
Crypto and TLS for C&
sponge_processing.h
Go to the documentation of this file.
1/*
2* Byte-oriented Sponge processing helpers
3* (C) 2025 Jack Lloyd
4* 2025 René Meusel
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#ifndef BOTAN_SPONGE_PROCESSING_H_
10#define BOTAN_SPONGE_PROCESSING_H_
11
12#include <botan/exceptn.h>
13#include <botan/internal/loadstor.h>
14#include <botan/internal/stl_util.h>
15#include <array>
16#include <span>
17
18namespace Botan {
19
20namespace detail {
21
22template <typename T>
23concept SpongeLike = std::unsigned_integral<decltype(T::word_bytes)> && requires(T a) {
24 typename T::word_t;
25 typename T::state_t;
26 { a.state() } -> std::same_as<typename T::state_t&>;
27 { a._cursor() } -> std::same_as<size_t&>;
28 { a.byte_rate() } -> std::same_as<size_t>;
29};
30
31template <typename T>
32concept SpongeLikeWithTrivialPermute = SpongeLike<T> && requires(T a) {
33 { a.permute() } -> std::same_as<void>;
34};
35
36/**
37* Represents the bounds of partial byte-oriented data within a word of
38* the sponge state. Downstream algorithms can use this to conveniently
39* modify the passed in partial state word with data written or read
40* from an input or output byte buffer.
41*/
42template <SpongeLike SpongeT>
43class PartialWordBounds final {
44 public:
45 size_t offset; // NOLINT(*-non-private-member-*)
46 size_t length; // NOLINT(*-non-private-member-*)
47
48 private:
49 using word_t = typename SpongeT::word_t;
50 constexpr static auto word_bytes = SpongeT::word_bytes;
51
52 public:
53 /**
54 * Reads '.length' bytes from the provided slicer and places them
55 * within a word at the specified '.offset' in little-endian order.
56 */
57 word_t read_from(BufferSlicer& slicer) const {
58 std::array<uint8_t, word_bytes> partial_word_bytes{};
59 slicer.copy_into(std::span{partial_word_bytes}.subspan(offset, length));
60 return load_le(partial_word_bytes);
61 }
62
63 /**
64 * Writes '.length' bytes from the provided word at the specified
65 * '.offset' into the provided stuffer in little-endian order.
66 */
67 void write_into(BufferStuffer& stuffer, word_t partial_word) const {
68 const auto partial_word_bytes = store_le(partial_word);
69 stuffer.append(std::span{partial_word_bytes}.subspan(offset, length));
70 }
71
72 /**
73 * Assigns the bits in 'partial_input_word' to their corresponding
74 * bits in 'state_word' at the specified '.offset' and '.length'
75 * while leaving all other bits in 'state_word' unchanged.
76 */
77 word_t masked_assignment(word_t state_word, word_t partial_input_word) const {
79 const auto mask = ((word_t(0) - 1) >> ((word_bytes - length) * 8)) << (offset * 8);
80 return (state_word & ~mask) | (partial_input_word & mask);
81 }
82};
83
84/**
85* A drop-in replacement for `PartialWordBounds` that is optimized for
86* handling full words where no masking or offsetting is necessary.
87*/
88template <SpongeLike SpongeT>
89class FullWordBounds final {
90 private:
91 using word_t = typename SpongeT::word_t;
92 constexpr static auto word_bytes = SpongeT::word_bytes;
93
94 public:
95 word_t read_from(BufferSlicer& slicer) const { return load_le(slicer.take<word_bytes>()); }
96
97 void write_into(BufferStuffer& stuffer, word_t full_word) const { stuffer.append(store_le(full_word)); }
98
99 word_t masked_assignment(word_t, word_t full_input_word) const { return full_input_word; }
100};
101
102template <typename T>
103concept PermutationFn = std::invocable<T> || std::same_as<T, void()>;
104
105template <typename T, typename SpongeT, typename ModifierT>
106concept BaseModifierFn = requires(T fn, typename SpongeT::word_t word, ModifierT bounds) {
107 { std::invoke(fn, word, bounds) } -> std::same_as<typename SpongeT::word_t>;
108};
109
110template <typename T, typename SpongeT>
111concept ModifierFn =
113
114} // namespace detail
115
116/**
117* Performs the core processing loop for ingesting or extracting data into/from
118* the sponge state in a byte-oriented manner for the given number of
119* @p bytes_to_process. The provided @p word_modifier_fn is called for each
120* (partial) word of the sponge state that needs to be modified or read.
121*
122* The processing loop ensures efficient handling of unaligned input and output
123* data. For that, it calls the provided permutation function either with an
124* instance of `PartialWordBounds` or `FullWordBounds`. Hence @p word_modifier_fn
125* must be able to handle both types of bounds and should use their respective
126* methods to read from or write into input or output buffers.
127*
128* @param sponge the sponge instance to process data into or from
129* @param bytes_to_process the number of sponge state bytes to traverse
130* @param permutation_fn a function that performs the sponge's permutation
131* @param modifier_fn a function that modifies the sponge state words
132*/
133template <detail::SpongeLike SpongeT>
135 size_t bytes_to_process,
136 const detail::PermutationFn auto& permutation_fn,
137 const detail::ModifierFn<SpongeT> auto& modifier_fn) {
138 if(bytes_to_process == 0) {
139 return;
140 }
141
142 constexpr auto word_bytes = SpongeT::word_bytes;
143 const auto byte_rate = sponge.byte_rate();
144 auto& S = sponge.state();
145 auto& cursor = sponge._cursor();
146
147 // If necessary, try to get aligned with the sponge state's words array
148 const auto bytes_out_of_word_alignment = static_cast<size_t>(cursor % word_bytes);
149 if(bytes_out_of_word_alignment > 0) {
150 const auto bytes_until_word_alignment = word_bytes - bytes_out_of_word_alignment;
151 const auto bytes_from_input = std::min(bytes_to_process, bytes_until_word_alignment);
152 BOTAN_DEBUG_ASSERT(bytes_from_input < word_bytes);
153
154 S[cursor / word_bytes] = modifier_fn(S[cursor / word_bytes],
156 .offset = bytes_out_of_word_alignment,
157 .length = bytes_from_input,
158 });
159 cursor += bytes_from_input;
160 bytes_to_process -= bytes_from_input;
161
162 if(cursor == byte_rate) {
163 permutation_fn();
164 cursor = 0;
165 }
166 }
167
168 // If we didn't exhaust the bytes to process for this invocation, we should
169 // be word-aligned with the sponge state now
170 BOTAN_DEBUG_ASSERT(bytes_to_process == 0 || cursor % word_bytes == 0);
171
172 // Block-wise incorporation of the input data into the sponge state until
173 // all input bytes are processed
174 while(bytes_to_process >= word_bytes) {
175 // Process full words until we either run out of data or reach the
176 // end of the current sponge state block
177 while(bytes_to_process >= word_bytes && cursor < byte_rate) {
178 S[cursor / word_bytes] = modifier_fn(S[cursor / word_bytes], detail::FullWordBounds<SpongeT>{});
179 cursor += word_bytes;
180 bytes_to_process -= word_bytes;
181 }
182
183 if(cursor == byte_rate) {
184 permutation_fn();
185 cursor = 0;
186 }
187 }
188
189 // Process the remaining bytes that don't fill an entire word.
190 // Therefore, leaving the sponge state in an unaligned state that won't
191 // need another permutation until the next call to process().
192 BOTAN_DEBUG_ASSERT(bytes_to_process < word_bytes && cursor < byte_rate);
193 if(bytes_to_process > 0) {
194 S[cursor / word_bytes] = modifier_fn(S[cursor / word_bytes],
196 .offset = 0,
197 .length = bytes_to_process,
198 });
199 cursor += bytes_to_process;
200 }
201}
202
203template <detail::SpongeLikeWithTrivialPermute SpongeT>
204inline void process_bytes_in_sponge(SpongeT& sponge,
205 size_t bytes_to_process,
206 const detail::ModifierFn<SpongeT> auto& modifier_fn) {
208 sponge, bytes_to_process, [&sponge] { sponge.permute(); }, modifier_fn);
209}
210
211/**
212* Absorbs @p input data into the @p sponge state.
213*
214* @param sponge The sponge state to absorb data into.
215* @param input The input data to absorb.
216* @param permutation_fn The function to call for the sponge's permutation.
217*/
218template <detail::SpongeLike SpongeT>
219inline void absorb_into_sponge(SpongeT& sponge,
220 std::span<const uint8_t> input,
221 const detail::PermutationFn auto& permutation_fn) {
222 using word_t = typename SpongeT::word_t;
223
224 BufferSlicer input_slicer(input);
225 process_bytes_in_sponge(sponge, input.size(), permutation_fn, [&](word_t state_word, auto bounds) {
226 return state_word ^ bounds.read_from(input_slicer);
227 });
228 BOTAN_ASSERT_NOMSG(input_slicer.empty());
229}
230
231inline void absorb_into_sponge(detail::SpongeLikeWithTrivialPermute auto& sponge, std::span<const uint8_t> input) {
232 absorb_into_sponge(sponge, input, [&sponge] { sponge.permute(); });
233}
234
235/**
236* Squeezes @p output data from the @p sponge state.
237*
238* @param sponge The sponge state to squeeze data from.
239* @param output The output buffer to write the squeezed data into.
240* @param permutation_fn The function to call for the sponge's permutation.
241*/
242template <detail::SpongeLike SpongeT>
243inline void squeeze_from_sponge(SpongeT& sponge,
244 std::span<uint8_t> output,
245 const detail::PermutationFn auto& permutation_fn) {
246 using word_t = typename SpongeT::word_t;
247
248 BufferStuffer output_stuffer(output);
249 process_bytes_in_sponge(sponge, output.size(), permutation_fn, [&](word_t state_word, auto bounds) {
250 bounds.write_into(output_stuffer, state_word);
251 return state_word;
252 });
253 BOTAN_ASSERT_NOMSG(output_stuffer.full());
254}
255
256inline void squeeze_from_sponge(detail::SpongeLikeWithTrivialPermute auto& sponge, std::span<uint8_t> output) {
257 squeeze_from_sponge(sponge, output, [&sponge] { sponge.permute(); });
258}
259
260} // namespace Botan
261
262#endif
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_DEBUG_ASSERT(expr)
Definition assert.h:129
void copy_into(std::span< uint8_t > sink)
Definition stl_util.h:111
bool empty() const
Definition stl_util.h:120
std::span< const uint8_t > take(const size_t count)
Definition stl_util.h:89
Helper class to ease in-place marshalling of concatenated fixed-length values.
Definition stl_util.h:133
constexpr void append(std::span< const uint8_t > buffer)
Definition stl_util.h:168
constexpr bool full() const
Definition stl_util.h:178
word_t masked_assignment(word_t, word_t full_input_word) const
word_t read_from(BufferSlicer &slicer) const
void write_into(BufferStuffer &stuffer, word_t full_word) const
word_t masked_assignment(word_t state_word, word_t partial_input_word) const
void write_into(BufferStuffer &stuffer, word_t partial_word) const
word_t read_from(BufferSlicer &slicer) const
#define BOTAN_FORCE_INLINE
Definition compiler.h:87
BOTAN_FORCE_INLINE void process_bytes_in_sponge(SpongeT &sponge, size_t bytes_to_process, const detail::PermutationFn auto &permutation_fn, const detail::ModifierFn< SpongeT > auto &modifier_fn)
void squeeze_from_sponge(SpongeT &sponge, std::span< uint8_t > output, const detail::PermutationFn auto &permutation_fn)
constexpr auto store_le(ParamTs &&... params)
Definition loadstor.h:736
constexpr auto load_le(ParamTs &&... params)
Definition loadstor.h:495
void absorb_into_sponge(SpongeT &sponge, std::span< const uint8_t > input, const detail::PermutationFn auto &permutation_fn)
std::conditional_t< HasNative64BitRegisters, std::uint64_t, uint32_t > word
Definition types.h:119