Botan 3.11.1
Crypto and TLS for C&
whirlpool_avx512.cpp
Go to the documentation of this file.
1/*
2* (C) 2026 Jack Lloyd
3*
4* Botan is released under the Simplified BSD License (see license.txt)
5*/
6
7#include <botan/internal/whirlpool.h>
8
9#include <botan/internal/isa_extn.h>
10#include <immintrin.h>
11
12namespace Botan {
13
14namespace WhirlpoolAVX512 {
15
16namespace {
17
18// NOLINTBEGIN(portability-simd-intrinsics)
19
20class WhirlpoolState {
21 public:
22 BOTAN_FN_ISA_AVX512
23 WhirlpoolState() : m_v(_mm512_setzero_si512()) {}
24
25 BOTAN_FN_ISA_AVX512
26 explicit WhirlpoolState(__m512i v) : m_v(v) {}
27
28 WhirlpoolState(const WhirlpoolState& other) = default;
29 WhirlpoolState(WhirlpoolState&& other) = default;
30 WhirlpoolState& operator=(const WhirlpoolState& other) = default;
31 WhirlpoolState& operator=(WhirlpoolState&& other) = default;
32 ~WhirlpoolState() = default;
33
34 // Load 64 bytes of message data
35 BOTAN_FN_ISA_AVX512
36 static WhirlpoolState load_bytes(const uint8_t src[64]) { return WhirlpoolState(_mm512_loadu_si512(src)); }
37
38 BOTAN_FN_ISA_AVX512
39 static WhirlpoolState load_be(const uint64_t src[8]) { return WhirlpoolState(_mm512_loadu_si512(src)).bswap(); }
40
41 BOTAN_FN_ISA_AVX512
42 void store_be(uint64_t dst[8]) const { _mm512_storeu_si512(dst, bswap().m_v); }
43
44 BOTAN_FN_ISA_AVX512
45 inline friend WhirlpoolState operator^(WhirlpoolState a, WhirlpoolState b) {
46 return WhirlpoolState(_mm512_xor_si512(a.m_v, b.m_v));
47 }
48
49 BOTAN_FN_ISA_AVX512
50 inline WhirlpoolState& operator^=(WhirlpoolState other) {
51 m_v = _mm512_xor_si512(m_v, other.m_v);
52 return *this;
53 }
54
55 /*
56 * The Whirlpool 8-bit Sbox is built out of 4-bit sboxes, which can be
57 * individually computed using pshufb-style shuffles.
58 */
59 BOTAN_FN_ISA_AVX512
60 inline WhirlpoolState sub_bytes() const {
61 const __m512i Ebox =
62 _mm512_broadcast_i32x4(_mm_setr_epi8(1, 11, 9, 12, 13, 6, 15, 3, 14, 8, 7, 4, 10, 2, 5, 0));
63 const __m512i Eibox =
64 _mm512_broadcast_i32x4(_mm_setr_epi8(15, 0, 13, 7, 11, 14, 5, 10, 9, 2, 12, 1, 3, 4, 8, 6));
65 const __m512i Rbox =
66 _mm512_broadcast_i32x4(_mm_setr_epi8(7, 12, 11, 13, 14, 4, 9, 15, 6, 3, 8, 10, 2, 5, 1, 0));
67
68 const __m512i lo_mask = _mm512_set1_epi8(0x0F);
69
70 const __m512i lo_nib = _mm512_and_si512(m_v, lo_mask);
71 const __m512i hi_nib = _mm512_and_si512(_mm512_srli_epi16(m_v, 4), lo_mask);
72
73 // L = Ebox[hi], R = Eibox[lo], T = Rbox[L ^ R]
74 const __m512i L = _mm512_shuffle_epi8(Ebox, hi_nib);
75 const __m512i R = _mm512_shuffle_epi8(Eibox, lo_nib);
76 const __m512i T = _mm512_shuffle_epi8(Rbox, _mm512_xor_si512(L, R));
77
78 // result = (Ebox[L ^ T] << 4) | Eibox[R ^ T]
79 const __m512i out_hi = _mm512_shuffle_epi8(Ebox, _mm512_xor_si512(L, T));
80 const __m512i out_lo = _mm512_shuffle_epi8(Eibox, _mm512_xor_si512(R, T));
81
82 return WhirlpoolState(_mm512_or_si512(_mm512_slli_epi16(out_hi, 4), _mm512_and_si512(out_lo, lo_mask)));
83 }
84
85 /*
86 * ShiftColumns: column j is cyclically shifted down by j positions.
87 *
88 * For output row r, column c: source = row (r - c + 8) % 8, column c.
89 * Implemented as a single vpermb with a fixed 64-byte permutation.
90 */
91 BOTAN_FN_ISA_AVX512
92 inline WhirlpoolState shift_columns() const {
93 // Register byte for (row r, col c) = r*8 + c
94 // Source byte = ((r - c + 8) % 8) * 8 + c
95 alignas(64) static constexpr uint8_t perm[64] = {
96 // clang-format off
97 0*8+0, 7*8+1, 6*8+2, 5*8+3, 4*8+4, 3*8+5, 2*8+6, 1*8+7,
98 1*8+0, 0*8+1, 7*8+2, 6*8+3, 5*8+4, 4*8+5, 3*8+6, 2*8+7,
99 2*8+0, 1*8+1, 0*8+2, 7*8+3, 6*8+4, 5*8+5, 4*8+6, 3*8+7,
100 3*8+0, 2*8+1, 1*8+2, 0*8+3, 7*8+4, 6*8+5, 5*8+6, 4*8+7,
101 4*8+0, 3*8+1, 2*8+2, 1*8+3, 0*8+4, 7*8+5, 6*8+6, 5*8+7,
102 5*8+0, 4*8+1, 3*8+2, 2*8+3, 1*8+4, 0*8+5, 7*8+6, 6*8+7,
103 6*8+0, 5*8+1, 4*8+2, 3*8+3, 2*8+4, 1*8+5, 0*8+6, 7*8+7,
104 7*8+0, 6*8+1, 5*8+2, 4*8+3, 3*8+4, 2*8+5, 1*8+6, 0*8+7,
105 // clang-format on
106 };
107 return WhirlpoolState(_mm512_permutexvar_epi8(_mm512_load_si512(perm), m_v));
108 }
109
110 /*
111 * MixRows: MDS circulant [1, 1, 4, 1, 8, 5, 2, 9] over GF(2^8) mod 0x11D
112 *
113 * Since the MDS coefficients are so small we can easily compute them using
114 * a few xtimes plus additions (aka XOR)
115 */
116 BOTAN_FN_ISA_AVX512
117 inline WhirlpoolState mix_rows() const {
118 /*
119 Constants for quadword rotations by X bytes.
120
121 Could use _mm512_rol_epi64 for this, but it's oddly slower even though
122 all documentation suggests that both instructions have the same latency
123 and throughput.
124 */
125 const __m512i rot1 =
126 _mm512_broadcast_i32x4(_mm_setr_epi8(7, 0, 1, 2, 3, 4, 5, 6, 15, 8, 9, 10, 11, 12, 13, 14));
127 const __m512i rot2 =
128 _mm512_broadcast_i32x4(_mm_setr_epi8(6, 7, 0, 1, 2, 3, 4, 5, 14, 15, 8, 9, 10, 11, 12, 13));
129 const __m512i rot3 =
130 _mm512_broadcast_i32x4(_mm_setr_epi8(5, 6, 7, 0, 1, 2, 3, 4, 13, 14, 15, 8, 9, 10, 11, 12));
131 const __m512i rot4 =
132 _mm512_broadcast_i32x4(_mm_setr_epi8(4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15, 8, 9, 10, 11));
133 const __m512i rot5 =
134 _mm512_broadcast_i32x4(_mm_setr_epi8(3, 4, 5, 6, 7, 0, 1, 2, 11, 12, 13, 14, 15, 8, 9, 10));
135 const __m512i rot6 =
136 _mm512_broadcast_i32x4(_mm_setr_epi8(2, 3, 4, 5, 6, 7, 0, 1, 10, 11, 12, 13, 14, 15, 8, 9));
137 const __m512i rot7 =
138 _mm512_broadcast_i32x4(_mm_setr_epi8(1, 2, 3, 4, 5, 6, 7, 0, 9, 10, 11, 12, 13, 14, 15, 8));
139
140 const __m512i x2 = xtime(m_v);
141 const __m512i x4 = xtime(x2);
142 const __m512i x8 = xtime(x4);
143 const __m512i x5 = _mm512_xor_si512(x4, m_v);
144 const __m512i x9 = _mm512_xor_si512(x8, m_v);
145
146 const __m512i t01 = _mm512_xor_si512(m_v, _mm512_shuffle_epi8(m_v, rot1));
147 const __m512i t23 = _mm512_xor_si512(_mm512_shuffle_epi8(x4, rot2), _mm512_shuffle_epi8(m_v, rot3));
148 const __m512i t45 = _mm512_xor_si512(_mm512_shuffle_epi8(x8, rot4), _mm512_shuffle_epi8(x5, rot5));
149 const __m512i t67 = _mm512_xor_si512(_mm512_shuffle_epi8(x2, rot6), _mm512_shuffle_epi8(x9, rot7));
150
151 return WhirlpoolState(_mm512_xor_si512(_mm512_xor_si512(t01, t23), _mm512_xor_si512(t45, t67)));
152 }
153
154 /*
155 * Whirlpool round: SubBytes -> ShiftColumns -> MixRows
156 */
157 BOTAN_FN_ISA_AVX512
158 inline WhirlpoolState round() const { return sub_bytes().shift_columns().mix_rows(); }
159
160 // Round constant
161 BOTAN_FN_ISA_AVX512
162 static inline WhirlpoolState rc(uint64_t v) { return WhirlpoolState(_mm512_set_epi64(0, 0, 0, 0, 0, 0, 0, v)); }
163
164 private:
165 BOTAN_FN_ISA_AVX512
166 WhirlpoolState bswap() const {
167 const __m512i tbl = _mm512_broadcast_i32x4(_mm_set_epi8(8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7));
168
169 return WhirlpoolState(_mm512_shuffle_epi8(m_v, tbl));
170 }
171
172 // Packed 16-wide doubling in GF(2^8) mod 0x11D
173 BOTAN_FN_ISA_AVX512
174 static __m512i xtime(__m512i a) {
175 const __m512i poly = _mm512_set1_epi8(0x1D);
176 const __mmask64 top_bits = _mm512_movepi8_mask(a);
177 const __m512i shifted = _mm512_add_epi8(a, a); // no 8-bit shift in AVX512
178 return _mm512_mask_blend_epi8(top_bits, shifted, _mm512_xor_si512(shifted, poly));
179 }
180
181 __m512i m_v;
182};
183
184// NOLINTEND(portability-simd-intrinsics)
185
186} // namespace
187
188} // namespace WhirlpoolAVX512
189
190BOTAN_FN_ISA_AVX512
191void Whirlpool::compress_n_avx512(digest_type& digest, std::span<const uint8_t> input, size_t blocks) {
192 using WhirlpoolAVX512::WhirlpoolState;
193
194 auto H = WhirlpoolState::load_be(digest.data());
195
196 for(size_t i = 0; i != blocks; ++i) {
197 const auto M = WhirlpoolState::load_bytes(input.data() + i * 64);
198
199 auto K = H;
200 H ^= M;
201 auto B = H; // B = M ^ K
202
203 K = K.round() ^ WhirlpoolState::rc(0x4F01B887E8C62318);
204 B = B.round() ^ K;
205
206 K = K.round() ^ WhirlpoolState::rc(0x52916F79F5D2A636);
207 B = B.round() ^ K;
208
209 K = K.round() ^ WhirlpoolState::rc(0x357B0CA38E9BBC60);
210 B = B.round() ^ K;
211
212 K = K.round() ^ WhirlpoolState::rc(0x57FE4B2EC2D7E01D);
213 B = B.round() ^ K;
214
215 K = K.round() ^ WhirlpoolState::rc(0xDA4AF09FE5377715);
216 B = B.round() ^ K;
217
218 K = K.round() ^ WhirlpoolState::rc(0x856BA0B10A29C958);
219 B = B.round() ^ K;
220
221 K = K.round() ^ WhirlpoolState::rc(0x67053ECBF4105DBD);
222 B = B.round() ^ K;
223
224 K = K.round() ^ WhirlpoolState::rc(0xD8957DA78B4127E4);
225 B = B.round() ^ K;
226
227 K = K.round() ^ WhirlpoolState::rc(0x9E4717DD667CEEFB);
228 B = B.round() ^ K;
229
230 K = K.round() ^ WhirlpoolState::rc(0x33835AAD07BF2DCA);
231 B = B.round() ^ K;
232
233 H ^= B;
234 }
235
236 H.store_be(digest.data());
237}
238
239} // namespace Botan
OctetString operator^(const OctetString &k1, const OctetString &k2)
Definition symkey.cpp:109
std::vector< uint8_t, Alloc > & operator^=(std::vector< uint8_t, Alloc > &out, const std::vector< uint8_t, Alloc2 > &in)
Definition mem_ops.h:445
constexpr auto store_be(ParamTs &&... params)
Definition loadstor.h:745
constexpr auto load_be(ParamTs &&... params)
Definition loadstor.h:504