Coverage Report

Created: 2024-06-15 20:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/include/cthash/sha3/common.hpp
Line
Count
Source
1
#ifndef CTHASH_SHA3_COMMON_HPP
2
#define CTHASH_SHA3_COMMON_HPP
3
4
#include "keccak.hpp"
5
#include "../hasher.hpp"
6
#include "../internal/bit.hpp"
7
#include "../internal/convert.hpp"
8
#include "../simple.hpp"
9
#include "../value.hpp"
10
#include <cstdint>
11
12
namespace cthash {
13
14
template <typename T, typename Y> concept castable_to = requires(T val) { {static_cast<Y>(val)} -> std::same_as<Y>; };
15
16
template <size_t N> struct keccak_suffix {
17
    unsigned bits;
18
    std::array<std::byte, N> values;
19
20
    constexpr keccak_suffix(unsigned b, castable_to<std::byte> auto... v) noexcept: bits{b}, values{static_cast<std::byte>(v)...} { }
21
};
22
23
template <castable_to<std::byte>... Ts> keccak_suffix(unsigned, Ts...) -> keccak_suffix<sizeof...(Ts)>;
24
25
template <typename T> struct identify;
26
27
0
template <typename T, byte_like Byte> constexpr auto convert_prefix_into_aligned(std::span<const Byte> input, unsigned pos) noexcept -> std::array<std::byte, sizeof(T)> {
28
0
    assert(input.size() <= sizeof(T));
29
0
    assert(pos <= sizeof(T));
30
0
    assert((input.size() + pos) <= sizeof(T));
31
0
32
0
    std::array<std::byte, sizeof(T)> buffer{};
33
0
34
0
    std::fill(buffer.begin(), buffer.end(), std::byte{0});
35
0
    std::transform(input.data(), input.data() + input.size(), buffer.data() + pos, [](auto v) { return static_cast<std::byte>(v); });
36
0
37
0
    return buffer;
38
0
}
39
40
0
template <typename T, byte_like Byte> constexpr auto convert_prefix_into_value(std::span<const Byte> input, unsigned pos) noexcept -> uint64_t {
41
0
    const auto tmp = convert_prefix_into_aligned<T, Byte>(input, pos);
42
0
    return cast_from_le_bytes<T>(std::span<const std::byte, 8>(tmp));
43
0
}
44
45
template <typename Config> struct basic_keccak_hasher {
46
    static_assert(Config::digest_length_bit % 8u == 0u);
47
    static_assert(Config::rate_bit % 8u == 0u);
48
    static_assert(Config::capacity_bit % 8u == 0u);
49
50
    static_assert((Config::rate_bit + Config::capacity_bit) == 1600u, "Only Keccak 1600 is implemented");
51
52
    static constexpr size_t digest_length = Config::digest_length_bit / 8u;
53
    static constexpr size_t rate = Config::rate_bit / 8u;
54
    static constexpr size_t capacity = Config::capacity_bit / 8u;
55
56
    using result_t = cthash::tagged_hash_value<Config>;
57
    using digest_span_t = std::span<std::byte, digest_length>;
58
59
    keccak::state_1600 internal_state{};
60
    uint8_t position{0u};
61
62
73
    constexpr basic_keccak_hasher() noexcept {
63
73
        std::fill(internal_state.begin(), internal_state.end(), uint64_t{0});
64
73
    }
65
66
11.9k
    template <byte_like T> constexpr size_t xor_overwrite_block(std::span<const T> input) noexcept {
67
11.9k
        using value_t = keccak::state_1600::value_type;
68
69
11.9k
        if ((std::is_constant_evaluated() | (std::endian::native != std::endian::little))) {
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
  Branch (69:7): [Folded - Ignored]
70
0
            assert((size_t(position) + input.size()) <= rate);
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
  Branch (70:4): [True: 0.00%, False: 0.00%]
71
72
            // unaligned prefix (by copying from left to right it should be little endian)
73
0
            if (position % sizeof(value_t) != 0u) {
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
  Branch (73:8): [True: 0.00%, False: 0.00%]
74
                // xor unaligned value and move to aligned if possible
75
0
                const size_t prefix_size = std::min(input.size(), sizeof(value_t) - (position % sizeof(value_t)));
76
0
                internal_state[position / sizeof(uint64_t)] ^= convert_prefix_into_value<value_t>(input.first(prefix_size), static_cast<unsigned>(position % sizeof(value_t)));
77
0
                position += static_cast<uint8_t>(prefix_size);
78
0
                input = input.subspan(prefix_size);
79
0
            }
80
81
            // aligned blocks
82
0
            while (input.size() >= sizeof(value_t)) {
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
  Branch (82:11): [True: 0.00%, False: 0.00%]
83
                // xor aligned value and move to next
84
0
                internal_state[position / sizeof(value_t)] ^= cast_from_le_bytes<value_t>(input.template first<sizeof(value_t)>());
85
0
                position += static_cast<uint8_t>(sizeof(value_t));
86
0
                input = input.subspan(sizeof(value_t));
87
0
            }
88
89
            // unaligned suffix
90
0
            if (not input.empty()) {
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
  Branch (90:8): [True: 0.00%, False: 0.00%]
91
                // xor and finish
92
0
                internal_state[position / sizeof(value_t)] ^= convert_prefix_into_value<value_t>(input, 0u);
93
0
                position += static_cast<uint8_t>(input.size());
94
0
            }
95
96
0
            return position;
97
11.9k
        } else {
98
11.9k
            const auto buffer = std::as_writable_bytes(std::span<uint64_t>(internal_state));
99
11.9k
            const auto remaining = buffer.subspan(position);
100
11.9k
            const auto place = remaining.first(std::min(input.size(), remaining.size()));
101
102
132k
            std::transform(place.data(), place.data() + place.size(), input.data(), place.data(), [](std::byte lhs, auto rhs) { return lhs ^ static_cast<std::byte>(rhs); });
103
104
11.9k
            position += static_cast<uint8_t>(place.size());
105
11.9k
            return position;
106
11.9k
        }
107
11.9k
    }
108
109
10.9k
    template <byte_like T> constexpr auto update(std::span<const T> input) noexcept {
110
10.9k
        assert(position < rate);
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
  Branch (110:3): [True: 0.00%, False: 100.00%]
111
10.9k
        const size_t remaining_in_buffer = rate - position;
112
113
10.9k
        if (remaining_in_buffer > input.size()) {
  Branch (113:7): [True: 100.00%, False: 0.00%]
  Branch (113:7): [True: 93.29%, False: 6.71%]
  Branch (113:7): [True: 100.00%, False: 0.00%]
  Branch (113:7): [True: 91.66%, False: 8.34%]
  Branch (113:7): [True: 100.00%, False: 0.00%]
  Branch (113:7): [True: 91.97%, False: 8.03%]
  Branch (113:7): [True: 100.00%, False: 0.00%]
  Branch (113:7): [True: 100.00%, False: 0.00%]
  Branch (113:7): [True: 94.57%, False: 5.43%]
  Branch (113:7): [True: 100.00%, False: 0.00%]
114
            // xor overwrite as much as we can, and that's all
115
10.2k
            xor_overwrite_block(input);
116
10.2k
            assert(position < rate);
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
  Branch (116:4): [True: 0.00%, False: 100.00%]
117
10.2k
            return;
118
10.2k
        }
119
120
        // finish block and call keccak :)
121
783
        const auto first_part = input.first(remaining_in_buffer);
122
783
        input = input.subspan(remaining_in_buffer);
123
783
        xor_overwrite_block(first_part);
124
783
        assert(position == rate);
  Branch (124:3): [True: 0.00%, False: 0.00%]
  Branch (124:3): [True: 0.00%, False: 100.00%]
  Branch (124:3): [True: 0.00%, False: 0.00%]
  Branch (124:3): [True: 0.00%, False: 100.00%]
  Branch (124:3): [True: 0.00%, False: 0.00%]
  Branch (124:3): [True: 0.00%, False: 100.00%]
  Branch (124:3): [True: 0.00%, False: 0.00%]
  Branch (124:3): [True: 0.00%, False: 0.00%]
  Branch (124:3): [True: 0.00%, False: 100.00%]
  Branch (124:3): [True: 0.00%, False: 0.00%]
125
783
        keccak_f(internal_state);
126
783
        position = 0u;
127
128
        // for each full block we can absorb directly
129
1.10k
        while (input.size() >= rate) {
  Branch (129:10): [True: 0.00%, False: 0.00%]
  Branch (129:10): [True: 29.18%, False: 70.82%]
  Branch (129:10): [True: 0.00%, False: 0.00%]
  Branch (129:10): [True: 27.41%, False: 72.59%]
  Branch (129:10): [True: 0.00%, False: 0.00%]
  Branch (129:10): [True: 27.51%, False: 72.49%]
  Branch (129:10): [True: 0.00%, False: 0.00%]
  Branch (129:10): [True: 0.00%, False: 0.00%]
  Branch (129:10): [True: 32.39%, False: 67.61%]
  Branch (129:10): [True: 0.00%, False: 0.00%]
130
317
            const auto block = input.template first<rate>();
131
317
            input = input.subspan(rate);
132
317
            assert(position == 0u);
  Branch (132:4): [True: 0.00%, False: 0.00%]
  Branch (132:4): [True: 0.00%, False: 100.00%]
  Branch (132:4): [True: 0.00%, False: 0.00%]
  Branch (132:4): [True: 0.00%, False: 100.00%]
  Branch (132:4): [True: 0.00%, False: 0.00%]
  Branch (132:4): [True: 0.00%, False: 100.00%]
  Branch (132:4): [True: 0.00%, False: 0.00%]
  Branch (132:4): [True: 0.00%, False: 0.00%]
  Branch (132:4): [True: 0.00%, False: 100.00%]
  Branch (132:4): [True: 0.00%, False: 0.00%]
133
317
            xor_overwrite_block<T>(block);
134
317
            keccak_f(internal_state);
135
317
            position = 0u;
136
317
        }
137
138
        // xor overwrite internal state with current remainder, and set position to end of it
139
783
        if (not input.empty()) {
  Branch (139:7): [True: 0.00%, False: 0.00%]
  Branch (139:7): [True: 85.16%, False: 14.84%]
  Branch (139:7): [True: 0.00%, False: 0.00%]
  Branch (139:7): [True: 91.42%, False: 8.58%]
  Branch (139:7): [True: 0.00%, False: 0.00%]
  Branch (139:7): [True: 90.18%, False: 9.82%]
  Branch (139:7): [True: 0.00%, False: 0.00%]
  Branch (139:7): [True: 0.00%, False: 0.00%]
  Branch (139:7): [True: 74.31%, False: 25.69%]
  Branch (139:7): [True: 0.00%, False: 0.00%]
140
677
            assert(position == 0u);
  Branch (140:4): [True: 0.00%, False: 0.00%]
  Branch (140:4): [True: 0.00%, False: 100.00%]
  Branch (140:4): [True: 0.00%, False: 0.00%]
  Branch (140:4): [True: 0.00%, False: 100.00%]
  Branch (140:4): [True: 0.00%, False: 0.00%]
  Branch (140:4): [True: 0.00%, False: 100.00%]
  Branch (140:4): [True: 0.00%, False: 0.00%]
  Branch (140:4): [True: 0.00%, False: 0.00%]
  Branch (140:4): [True: 0.00%, False: 100.00%]
  Branch (140:4): [True: 0.00%, False: 0.00%]
141
677
            xor_overwrite_block(input);
142
677
            assert(position < rate);
  Branch (142:4): [True: 0.00%, False: 0.00%]
  Branch (142:4): [True: 0.00%, False: 100.00%]
  Branch (142:4): [True: 0.00%, False: 0.00%]
  Branch (142:4): [True: 0.00%, False: 100.00%]
  Branch (142:4): [True: 0.00%, False: 0.00%]
  Branch (142:4): [True: 0.00%, False: 100.00%]
  Branch (142:4): [True: 0.00%, False: 0.00%]
  Branch (142:4): [True: 0.00%, False: 0.00%]
  Branch (142:4): [True: 0.00%, False: 100.00%]
  Branch (142:4): [True: 0.00%, False: 0.00%]
143
677
        }
144
783
    }
145
146
    // pad the message
147
69
    constexpr void xor_padding_block() noexcept {
148
69
        assert(position < rate);
  Branch (148:3): [True: 0.00%, False: 100.00%]
  Branch (148:3): [True: 0.00%, False: 100.00%]
  Branch (148:3): [True: 0.00%, False: 100.00%]
  Branch (148:3): [True: 0.00%, False: 100.00%]
  Branch (148:3): [True: 0.00%, False: 100.00%]
  Branch (148:3): [True: 0.00%, False: 100.00%]
149
150
69
        constexpr const auto & suffix = Config::suffix;
151
69
        constexpr std::byte suffix_and_start_of_padding = (suffix.values[0] | (std::byte{0b0000'0001u} << suffix.bits));
152
153
69
        internal_state[position / sizeof(uint64_t)] ^= uint64_t(suffix_and_start_of_padding) << ((position % sizeof(uint64_t)) * 8u);
154
69
        internal_state[(rate - 1u) / sizeof(uint64_t)] ^= 0x8000000000000000ull; // last bit
155
69
    }
156
157
68
    constexpr void final_absorb() noexcept {
158
68
        xor_padding_block();
159
68
        keccak_f(internal_state);
160
68
    }
161
162
    // get resulting hash
163
24
    constexpr void squeeze(std::span<std::byte> output) noexcept {
164
24
        using value_t = keccak::state_1600::value_type;
165
166
24
        static_assert((rate % sizeof(value_t)) == 0u);
167
24
        auto r = std::span<const value_t>(internal_state).first(rate / sizeof(value_t));
168
169
        // aligned results will be processed here...
170
180
        while ((output.size() >= sizeof(value_t))) {
  Branch (170:10): [True: 86.67%, False: 13.33%]
  Branch (170:10): [True: 86.67%, False: 13.33%]
171
            // if we ran out of `rate` part, we need to squeeze another block
172
156
            if (r.empty()) {
  Branch (172:8): [True: 2.56%, False: 97.44%]
  Branch (172:8): [True: 2.56%, False: 97.44%]
173
4
                keccak_f(internal_state);
174
4
                r = std::span<const value_t>(internal_state).first(rate / sizeof(value_t));
175
4
            }
176
177
            // look at current to process
178
156
            const value_t current = r.front();
179
156
            const auto part = output.first<sizeof(value_t)>();
180
181
            // convert
182
156
            unwrap_littleendian_number<value_t>{part} = current;
183
184
            // move to next
185
156
            r = r.subspan(1u);
186
156
            output = output.subspan(sizeof(value_t));
187
156
        }
188
189
        // unaligned result is here
190
24
        if (!output.empty()) {
  Branch (190:7): [True: 33.33%, False: 66.67%]
  Branch (190:7): [True: 33.33%, False: 66.67%]
191
            // if we ran out of `rate` part, we need to squeeze another block
192
8
            if (r.empty()) {
  Branch (192:8): [True: 0.00%, False: 100.00%]
  Branch (192:8): [True: 0.00%, False: 100.00%]
193
0
                keccak_f(internal_state);
194
0
                r = std::span<const value_t>(internal_state).first(rate / sizeof(value_t));
195
0
            }
196
197
8
            const value_t current = r.front();
198
199
            // convert
200
8
            std::array<std::byte, sizeof(value_t)> tmp;
201
8
            unwrap_littleendian_number<value_t>{tmp} = current;
202
8
            assert(tmp.size() > output.size());
  Branch (202:4): [True: 0.00%, False: 100.00%]
  Branch (202:4): [True: 0.00%, False: 100.00%]
203
8
            std::copy_n(tmp.data(), output.size(), output.data());
204
8
        }
205
24
    }
206
207
    constexpr void squeeze(digest_span_t output_fixed) noexcept
208
        requires((digest_length < rate) && digest_length != 0u)
209
44
    {
210
44
        auto output = std::span<std::byte>(output_fixed);
211
212
        // we don't need to squeeze anything
213
44
        using value_t = keccak::state_1600::value_type;
214
215
44
        static_assert((rate % sizeof(value_t)) == 0u);
216
44
        auto r = std::span<const value_t>(internal_state).first(rate / sizeof(value_t));
217
218
        // aligned results will be processed here...
219
265
        while ((output.size() >= sizeof(value_t))) {
  Branch (219:10): [True: 80.00%, False: 20.00%]
  Branch (219:10): [True: 85.71%, False: 14.29%]
  Branch (219:10): [True: 75.00%, False: 25.00%]
  Branch (219:10): [True: 88.89%, False: 11.11%]
220
221
            assert(!r.empty());
  Branch (220:4): [True: 0.00%, False: 100.00%]
  Branch (220:4): [True: 0.00%, False: 100.00%]
  Branch (220:4): [True: 0.00%, False: 100.00%]
  Branch (220:4): [True: 0.00%, False: 100.00%]
221
            // look at current to process
222
221
            const value_t current = r.front();
223
221
            const auto part = output.template first<sizeof(value_t)>();
224
225
            // convert
226
221
            unwrap_littleendian_number<value_t>{part} = current;
227
228
            // move to next
229
221
            r = r.subspan(1u);
230
221
            output = output.subspan(sizeof(value_t));
231
221
        }
232
233
44
        if constexpr ((output_fixed.size() % sizeof(value_t)) != 0u) {
234
            // unaligned result is here
235
9
            assert(!output.empty());
  Branch (235:4): [True: 0.00%, False: 100.00%]
236
9
            assert(!r.empty());
  Branch (236:4): [True: 0.00%, False: 100.00%]
237
238
9
            const value_t current = r.front();
239
240
            // convert
241
9
            std::array<std::byte, sizeof(value_t)> tmp;
242
9
            unwrap_littleendian_number<value_t>{tmp} = current;
243
9
            assert(tmp.size() > output.size());
  Branch (243:4): [True: 0.00%, False: 100.00%]
244
9
            std::copy_n(tmp.data(), output.size(), output.data());
245
9
        }
246
44
    }
247
248
    constexpr void final(digest_span_t digest) noexcept
249
        requires(digest_length != 0u)
250
44
    {
251
44
        final_absorb();
252
44
        squeeze(digest);
253
44
    }
254
255
    constexpr result_t final() noexcept
256
        requires(digest_length != 0u)
257
44
    {
258
44
        result_t output;
259
44
        final(output);
260
44
        return output;
261
44
    }
262
263
    template <size_t N> constexpr auto final() noexcept
264
        requires(digest_length == 0u)
265
24
    {
266
24
        static_assert(N % 8u == 0u, "Only whole bytes are supported!");
267
24
        using result_type = typename Config::template variable_digest<N>;
268
24
        result_type output;
269
24
        final_absorb();
270
24
        squeeze(output);
271
24
        return output;
272
24
    }
273
};
274
275
template <typename Config> struct keccak_hasher: basic_keccak_hasher<Config> {
276
    using super = basic_keccak_hasher<Config>;
277
    using result_t = typename super::result_t;
278
    using digest_span_t = typename super::digest_span_t;
279
280
73
    constexpr keccak_hasher() noexcept: super() { }
281
    constexpr keccak_hasher(const keccak_hasher &) noexcept = default;
282
    constexpr keccak_hasher(keccak_hasher &&) noexcept = default;
283
    constexpr ~keccak_hasher() noexcept = default;
284
285
4
    constexpr keccak_hasher & update(std::span<const std::byte> input) noexcept {
286
4
        super::update(input);
287
4
        return *this;
288
4
    }
289
290
942
    template <convertible_to_byte_span T> constexpr keccak_hasher & update(const T & something) noexcept {
291
942
        using value_type = typename decltype(std::span(something))::value_type;
292
942
        super::update(std::span<const value_type>(something));
293
942
        return *this;
294
942
    }
295
296
2.50k
    template <one_byte_char CharT> constexpr keccak_hasher & update(std::basic_string_view<CharT> in) noexcept {
297
2.50k
        super::update(std::span(in.data(), in.size()));
298
2.50k
        return *this;
299
2.50k
    }
300
301
7.53k
    template <string_literal T> constexpr keccak_hasher & update(const T & lit) noexcept {
302
7.53k
        super::update(std::span(lit, std::size(lit) - 1u));
303
7.53k
        return *this;
304
7.53k
    }
305
306
    // TODO: any range with value convertible to byte
307
308
    using super::final;
309
};
310
311
} // namespace cthash
312
313
#endif