Botan 3.11.0
Crypto and TLS for C&
idea_avx2.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/idea.h>
8
9#include <botan/internal/ct_utils.h>
10#include <botan/internal/isa_extn.h>
11#include <immintrin.h>
12
13namespace Botan {
14
15namespace {
16
17// NOLINTBEGIN(portability-simd-intrinsics)
18
19/*
20* SIMD type of 16 16-bit elements
21*/
22class SIMD_16x16 final {
23 public:
24 using native_type = __m256i;
25
26 SIMD_16x16(const SIMD_16x16&) = default;
27 SIMD_16x16& operator=(const SIMD_16x16&) = default;
28 SIMD_16x16(SIMD_16x16&&) = default;
29 SIMD_16x16& operator=(SIMD_16x16&&) = default;
30 ~SIMD_16x16() = default;
31
32 BOTAN_FN_ISA_AVX2 explicit SIMD_16x16(native_type x) : m_simd(x) {}
33
34 static SIMD_16x16 BOTAN_FN_ISA_AVX2 load_le(const uint8_t in[]) {
35 return SIMD_16x16(_mm256_loadu_si256(reinterpret_cast<const __m256i*>(in)));
36 }
37
38 void BOTAN_FN_ISA_AVX2 store_le(uint8_t out[]) const {
39 _mm256_storeu_si256(reinterpret_cast<__m256i*>(out), m_simd);
40 }
41
42 static SIMD_16x16 BOTAN_FN_ISA_AVX2 load_be(const uint8_t in[]) { return load_le(in).bswap(); }
43
44 void BOTAN_FN_ISA_AVX2 store_be(uint8_t out[]) const { bswap().store_le(out); }
45
46 SIMD_16x16 BOTAN_FN_ISA_AVX2 bswap() const {
47 // clang-format off
48 const auto bswap_tbl = _mm256_set_epi8(
49 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1,
50 14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
51 // clang-format on
52 return SIMD_16x16(_mm256_shuffle_epi8(m_simd, bswap_tbl));
53 }
54
55 SIMD_16x16 BOTAN_FN_ISA_AVX2 operator-(const SIMD_16x16& o) const {
56 return SIMD_16x16(_mm256_sub_epi16(m_simd, o.m_simd));
57 }
58
59 SIMD_16x16 BOTAN_FN_ISA_AVX2 operator^(const SIMD_16x16& o) const {
60 return SIMD_16x16(_mm256_xor_si256(m_simd, o.m_simd));
61 }
62
63 void BOTAN_FN_ISA_AVX2 operator+=(const SIMD_16x16& o) { m_simd = _mm256_add_epi16(m_simd, o.m_simd); }
64
65 void BOTAN_FN_ISA_AVX2 operator+=(uint16_t v) { m_simd = _mm256_add_epi16(m_simd, _mm256_set1_epi16(v)); }
66
67 void BOTAN_FN_ISA_AVX2 operator^=(const SIMD_16x16& o) { m_simd = _mm256_xor_si256(m_simd, o.m_simd); }
68
69 static inline BOTAN_FN_ISA_AVX2 SIMD_16x16 mul_mod_65537(SIMD_16x16 X, uint16_t K_16) {
70 const auto zeros = SIMD_16x16::splat(0);
71 const auto ones = SIMD_16x16::splat(1);
72 const auto K = SIMD_16x16::splat(K_16);
73
74 // Each u16 of the output is set to all-1 mask if == 0, or otherwise 0
75 const auto X_is_zero = SIMD_16x16(_mm256_cmpeq_epi16(X.raw(), zeros.raw()));
76 const auto K_is_zero = SIMD_16x16(_mm256_cmpeq_epi16(K.raw(), zeros.raw()));
77
78 const auto ml = SIMD_16x16(_mm256_mullo_epi16(X.raw(), K.raw()));
79 const auto mh = SIMD_16x16(_mm256_mulhi_epu16(X.raw(), K.raw()));
80
81 // AVX2 doesn't have unsigned comparisons for whatever dumb reason
82 const auto bias = SIMD_16x16::splat(0x8000);
83 const auto borrow = (mh ^ bias).cmpgt(ml ^ bias);
84
85 // T = ml - mh + (mh > ml ? 1 : 0)
86 auto T = ml - mh - borrow;
87
88 // Set to 1-K or 1-X to handle the exceptional cases
89 T = T.select_u16(ones - K, X_is_zero);
90 T = T.select_u16(ones - X, K_is_zero);
91
92 return T;
93 }
94
95 /*
96 * 4x16 matrix transpose
97 */
98 static void BOTAN_FN_ISA_AVX2 transpose_in(SIMD_16x16& B0, SIMD_16x16& B1, SIMD_16x16& B2, SIMD_16x16& B3) {
99 auto B0r = _mm256_shuffle_epi32(B0.raw(), _MM_SHUFFLE(3, 1, 2, 0));
100 auto B1r = _mm256_shuffle_epi32(B1.raw(), _MM_SHUFFLE(3, 1, 2, 0));
101 auto B2r = _mm256_shuffle_epi32(B2.raw(), _MM_SHUFFLE(3, 1, 2, 0));
102 auto B3r = _mm256_shuffle_epi32(B3.raw(), _MM_SHUFFLE(3, 1, 2, 0));
103
104 B0r = _mm256_shufflelo_epi16(B0r, _MM_SHUFFLE(3, 1, 2, 0));
105 B1r = _mm256_shufflelo_epi16(B1r, _MM_SHUFFLE(3, 1, 2, 0));
106 B2r = _mm256_shufflelo_epi16(B2r, _MM_SHUFFLE(3, 1, 2, 0));
107 B3r = _mm256_shufflelo_epi16(B3r, _MM_SHUFFLE(3, 1, 2, 0));
108
109 B0r = _mm256_shufflehi_epi16(B0r, _MM_SHUFFLE(3, 1, 2, 0));
110 B1r = _mm256_shufflehi_epi16(B1r, _MM_SHUFFLE(3, 1, 2, 0));
111 B2r = _mm256_shufflehi_epi16(B2r, _MM_SHUFFLE(3, 1, 2, 0));
112 B3r = _mm256_shufflehi_epi16(B3r, _MM_SHUFFLE(3, 1, 2, 0));
113
114 const auto T0 = _mm256_unpacklo_epi32(B0r, B1r);
115 const auto T1 = _mm256_unpackhi_epi32(B0r, B1r);
116 const auto T2 = _mm256_unpacklo_epi32(B2r, B3r);
117 const auto T3 = _mm256_unpackhi_epi32(B2r, B3r);
118
119 B0 = SIMD_16x16(_mm256_unpacklo_epi64(T0, T2));
120 B1 = SIMD_16x16(_mm256_unpackhi_epi64(T0, T2));
121 B2 = SIMD_16x16(_mm256_unpacklo_epi64(T1, T3));
122 B3 = SIMD_16x16(_mm256_unpackhi_epi64(T1, T3));
123 }
124
125 /*
126 * 4x16 matrix transpose (inverse)
127 */
128 static void BOTAN_FN_ISA_AVX2 transpose_out(SIMD_16x16& B0, SIMD_16x16& B1, SIMD_16x16& B2, SIMD_16x16& B3) {
129 auto T0 = _mm256_unpacklo_epi64(B0.raw(), B1.raw());
130 auto T1 = _mm256_unpacklo_epi64(B2.raw(), B3.raw());
131 auto T2 = _mm256_unpackhi_epi64(B0.raw(), B1.raw());
132 auto T3 = _mm256_unpackhi_epi64(B2.raw(), B3.raw());
133
134 T0 = _mm256_shuffle_epi32(T0, _MM_SHUFFLE(3, 1, 2, 0));
135 T1 = _mm256_shuffle_epi32(T1, _MM_SHUFFLE(3, 1, 2, 0));
136 T2 = _mm256_shuffle_epi32(T2, _MM_SHUFFLE(3, 1, 2, 0));
137 T3 = _mm256_shuffle_epi32(T3, _MM_SHUFFLE(3, 1, 2, 0));
138
139 T0 = _mm256_shufflehi_epi16(T0, _MM_SHUFFLE(3, 1, 2, 0));
140 T1 = _mm256_shufflehi_epi16(T1, _MM_SHUFFLE(3, 1, 2, 0));
141 T2 = _mm256_shufflehi_epi16(T2, _MM_SHUFFLE(3, 1, 2, 0));
142 T3 = _mm256_shufflehi_epi16(T3, _MM_SHUFFLE(3, 1, 2, 0));
143
144 T0 = _mm256_shufflelo_epi16(T0, _MM_SHUFFLE(3, 1, 2, 0));
145 T1 = _mm256_shufflelo_epi16(T1, _MM_SHUFFLE(3, 1, 2, 0));
146 T2 = _mm256_shufflelo_epi16(T2, _MM_SHUFFLE(3, 1, 2, 0));
147 T3 = _mm256_shufflelo_epi16(T3, _MM_SHUFFLE(3, 1, 2, 0));
148
149 B0 = SIMD_16x16(_mm256_unpacklo_epi32(T0, T1));
150 B1 = SIMD_16x16(_mm256_unpackhi_epi32(T0, T1));
151 B2 = SIMD_16x16(_mm256_unpacklo_epi32(T2, T3));
152 B3 = SIMD_16x16(_mm256_unpackhi_epi32(T2, T3));
153 }
154
155 native_type BOTAN_FN_ISA_AVX2 raw() const { return m_simd; }
156
157 private:
158 static SIMD_16x16 BOTAN_FN_ISA_AVX2 splat(uint16_t v) { return SIMD_16x16(_mm256_set1_epi16(v)); }
159
160 SIMD_16x16 BOTAN_FN_ISA_AVX2 cmpgt(const SIMD_16x16& o) const {
161 return SIMD_16x16(_mm256_cmpgt_epi16(m_simd, o.m_simd));
162 }
163
164 SIMD_16x16 BOTAN_FN_ISA_AVX2 select_u16(const SIMD_16x16& other, const SIMD_16x16& mask) const {
165 return SIMD_16x16(_mm256_blendv_epi8(m_simd, other.m_simd, mask.m_simd));
166 }
167
168 native_type m_simd;
169};
170
171// NOLINTEND(portability-simd-intrinsics)
172
173} // namespace
174
175BOTAN_FN_ISA_AVX2 void IDEA::avx2_idea_op_16(const uint8_t in[128], uint8_t out[128], const uint16_t EK[52]) {
176 CT::poison(in, 128);
177 CT::poison(out, 128);
178 CT::poison(EK, 52);
179
180 auto B0 = SIMD_16x16::load_be(in + 0);
181 auto B1 = SIMD_16x16::load_be(in + 32);
182 auto B2 = SIMD_16x16::load_be(in + 64);
183 auto B3 = SIMD_16x16::load_be(in + 96);
184
185 SIMD_16x16::transpose_in(B0, B1, B2, B3);
186
187 for(size_t i = 0; i != 8; ++i) {
188 B0 = SIMD_16x16::mul_mod_65537(B0, EK[6 * i + 0]);
189 B1 += EK[6 * i + 1];
190 B2 += EK[6 * i + 2];
191 B3 = SIMD_16x16::mul_mod_65537(B3, EK[6 * i + 3]);
192
193 const auto T0 = B2;
194 B2 ^= B0;
195 B2 = SIMD_16x16::mul_mod_65537(B2, EK[6 * i + 4]);
196
197 const auto T1 = B1;
198
199 B1 ^= B3;
200 B1 += B2;
201 B1 = SIMD_16x16::mul_mod_65537(B1, EK[6 * i + 5]);
202
203 B2 += B1;
204
205 B0 ^= B1;
206 B1 ^= T0;
207 B3 ^= B2;
208 B2 ^= T1;
209 }
210
211 B0 = SIMD_16x16::mul_mod_65537(B0, EK[48]);
212 B1 += EK[50];
213 B2 += EK[49];
214 B3 = SIMD_16x16::mul_mod_65537(B3, EK[51]);
215
216 SIMD_16x16::transpose_out(B0, B2, B1, B3);
217
218 B0.store_be(out + 0);
219 B2.store_be(out + 32);
220 B1.store_be(out + 64);
221 B3.store_be(out + 96);
222
223 CT::unpoison(in, 128);
224 CT::unpoison(out, 128);
225 CT::unpoison(EK, 52);
226}
227
228} // namespace Botan
constexpr void unpoison(const T *p, size_t n)
Definition ct_utils.h:67
constexpr void poison(const T *p, size_t n)
Definition ct_utils.h:56
OctetString operator^(const OctetString &k1, const OctetString &k2)
Definition symkey.cpp:109
BigInt operator-(const BigInt &x, const BigInt &y)
Definition bigint.h:1111
constexpr auto store_le(ParamTs &&... params)
Definition loadstor.h:736
std::vector< uint8_t, Alloc > & operator^=(std::vector< uint8_t, Alloc > &out, const std::vector< uint8_t, Alloc2 > &in)
Definition mem_ops.h:445
std::vector< T, Alloc > & operator+=(std::vector< T, Alloc > &out, const std::vector< T, Alloc2 > &in)
Definition secmem.h:90
constexpr auto load_le(ParamTs &&... params)
Definition loadstor.h:495
constexpr auto store_be(ParamTs &&... params)
Definition loadstor.h:745
constexpr auto load_be(ParamTs &&... params)
Definition loadstor.h:504