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