Revision control
Copy as Markdown
Other Tools
/*
* (C) 2026 Jack Lloyd
*
* Botan is released under the Simplified BSD License (see license.txt)
*/
#include <botan/internal/idea.h>
#include <botan/internal/ct_utils.h>
#include <botan/internal/isa_extn.h>
#include <immintrin.h>
namespace Botan {
namespace {
// NOLINTBEGIN(portability-simd-intrinsics)
/*
* SIMD type of 16 16-bit elements
*/
class SIMD_16x16 final {
public:
using native_type = __m256i;
SIMD_16x16(const SIMD_16x16&) = default;
SIMD_16x16& operator=(const SIMD_16x16&) = default;
SIMD_16x16(SIMD_16x16&&) = default;
SIMD_16x16& operator=(SIMD_16x16&&) = default;
~SIMD_16x16() = default;
BOTAN_FN_ISA_AVX2 explicit SIMD_16x16(native_type x) : m_simd(x) {}
static SIMD_16x16 BOTAN_FN_ISA_AVX2 load_le(const uint8_t in[]) {
return SIMD_16x16(_mm256_loadu_si256(reinterpret_cast<const __m256i*>(in)));
}
void BOTAN_FN_ISA_AVX2 store_le(uint8_t out[]) const {
_mm256_storeu_si256(reinterpret_cast<__m256i*>(out), m_simd);
}
static SIMD_16x16 BOTAN_FN_ISA_AVX2 load_be(const uint8_t in[]) { return load_le(in).bswap(); }
void BOTAN_FN_ISA_AVX2 store_be(uint8_t out[]) const { bswap().store_le(out); }
SIMD_16x16 BOTAN_FN_ISA_AVX2 bswap() const {
// clang-format off
const auto bswap_tbl = _mm256_set_epi8(
14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1,
14, 15, 12, 13, 10, 11, 8, 9, 6, 7, 4, 5, 2, 3, 0, 1);
// clang-format on
return SIMD_16x16(_mm256_shuffle_epi8(m_simd, bswap_tbl));
}
SIMD_16x16 BOTAN_FN_ISA_AVX2 operator-(const SIMD_16x16& o) const {
return SIMD_16x16(_mm256_sub_epi16(m_simd, o.m_simd));
}
SIMD_16x16 BOTAN_FN_ISA_AVX2 operator^(const SIMD_16x16& o) const {
return SIMD_16x16(_mm256_xor_si256(m_simd, o.m_simd));
}
void BOTAN_FN_ISA_AVX2 operator+=(const SIMD_16x16& o) { m_simd = _mm256_add_epi16(m_simd, o.m_simd); }
void BOTAN_FN_ISA_AVX2 operator+=(uint16_t v) { m_simd = _mm256_add_epi16(m_simd, _mm256_set1_epi16(v)); }
void BOTAN_FN_ISA_AVX2 operator^=(const SIMD_16x16& o) { m_simd = _mm256_xor_si256(m_simd, o.m_simd); }
static inline BOTAN_FN_ISA_AVX2 SIMD_16x16 mul_mod_65537(SIMD_16x16 X, uint16_t K_16) {
const auto zeros = SIMD_16x16::splat(0);
const auto ones = SIMD_16x16::splat(1);
const auto K = SIMD_16x16::splat(K_16);
// If X == 0 or K == 0 then P == X * K == 0
const auto P_is_zero = SIMD_16x16(
_mm256_or_si256(_mm256_cmpeq_epi16(X.raw(), zeros.raw()), _mm256_cmpeq_epi16(K.raw(), zeros.raw())));
// Return value if P == 0: 1 - X - K
const auto R0 = ones - X - K;
const auto mul_lo = SIMD_16x16(_mm256_mullo_epi16(X.raw(), K.raw()));
const auto mul_hi = SIMD_16x16(_mm256_mulhi_epu16(X.raw(), K.raw()));
// AVX2 doesn't have unsigned comparisons so emulate with a signed compare by flipping the sign bit
const auto sign_bit = SIMD_16x16::splat(0x8000);
const auto borrow = SIMD_16x16(_mm256_cmpgt_epi16((mul_hi ^ sign_bit).raw(), (mul_lo ^ sign_bit).raw()));
// R1 = mul_lo - mul_hi + (mul_hi > mul_lo ? 1 : 0)
const auto R1 = mul_lo - mul_hi - borrow;
return SIMD_16x16(_mm256_blendv_epi8(R1.raw(), R0.raw(), P_is_zero.raw()));
}
/*
* 4x16 matrix transpose
*/
static void BOTAN_FN_ISA_AVX2 transpose_in(SIMD_16x16& B0, SIMD_16x16& B1, SIMD_16x16& B2, SIMD_16x16& B3) {
auto B0r = _mm256_shuffle_epi32(B0.raw(), _MM_SHUFFLE(3, 1, 2, 0));
auto B1r = _mm256_shuffle_epi32(B1.raw(), _MM_SHUFFLE(3, 1, 2, 0));
auto B2r = _mm256_shuffle_epi32(B2.raw(), _MM_SHUFFLE(3, 1, 2, 0));
auto B3r = _mm256_shuffle_epi32(B3.raw(), _MM_SHUFFLE(3, 1, 2, 0));
B0r = _mm256_shufflelo_epi16(B0r, _MM_SHUFFLE(3, 1, 2, 0));
B1r = _mm256_shufflelo_epi16(B1r, _MM_SHUFFLE(3, 1, 2, 0));
B2r = _mm256_shufflelo_epi16(B2r, _MM_SHUFFLE(3, 1, 2, 0));
B3r = _mm256_shufflelo_epi16(B3r, _MM_SHUFFLE(3, 1, 2, 0));
B0r = _mm256_shufflehi_epi16(B0r, _MM_SHUFFLE(3, 1, 2, 0));
B1r = _mm256_shufflehi_epi16(B1r, _MM_SHUFFLE(3, 1, 2, 0));
B2r = _mm256_shufflehi_epi16(B2r, _MM_SHUFFLE(3, 1, 2, 0));
B3r = _mm256_shufflehi_epi16(B3r, _MM_SHUFFLE(3, 1, 2, 0));
const auto T0 = _mm256_unpacklo_epi32(B0r, B1r);
const auto T1 = _mm256_unpackhi_epi32(B0r, B1r);
const auto T2 = _mm256_unpacklo_epi32(B2r, B3r);
const auto T3 = _mm256_unpackhi_epi32(B2r, B3r);
B0 = SIMD_16x16(_mm256_unpacklo_epi64(T0, T2));
B1 = SIMD_16x16(_mm256_unpackhi_epi64(T0, T2));
B2 = SIMD_16x16(_mm256_unpacklo_epi64(T1, T3));
B3 = SIMD_16x16(_mm256_unpackhi_epi64(T1, T3));
}
/*
* 4x16 matrix transpose (inverse)
*/
static void BOTAN_FN_ISA_AVX2 transpose_out(SIMD_16x16& B0, SIMD_16x16& B1, SIMD_16x16& B2, SIMD_16x16& B3) {
auto T0 = _mm256_unpacklo_epi64(B0.raw(), B1.raw());
auto T1 = _mm256_unpacklo_epi64(B2.raw(), B3.raw());
auto T2 = _mm256_unpackhi_epi64(B0.raw(), B1.raw());
auto T3 = _mm256_unpackhi_epi64(B2.raw(), B3.raw());
T0 = _mm256_shuffle_epi32(T0, _MM_SHUFFLE(3, 1, 2, 0));
T1 = _mm256_shuffle_epi32(T1, _MM_SHUFFLE(3, 1, 2, 0));
T2 = _mm256_shuffle_epi32(T2, _MM_SHUFFLE(3, 1, 2, 0));
T3 = _mm256_shuffle_epi32(T3, _MM_SHUFFLE(3, 1, 2, 0));
T0 = _mm256_shufflehi_epi16(T0, _MM_SHUFFLE(3, 1, 2, 0));
T1 = _mm256_shufflehi_epi16(T1, _MM_SHUFFLE(3, 1, 2, 0));
T2 = _mm256_shufflehi_epi16(T2, _MM_SHUFFLE(3, 1, 2, 0));
T3 = _mm256_shufflehi_epi16(T3, _MM_SHUFFLE(3, 1, 2, 0));
T0 = _mm256_shufflelo_epi16(T0, _MM_SHUFFLE(3, 1, 2, 0));
T1 = _mm256_shufflelo_epi16(T1, _MM_SHUFFLE(3, 1, 2, 0));
T2 = _mm256_shufflelo_epi16(T2, _MM_SHUFFLE(3, 1, 2, 0));
T3 = _mm256_shufflelo_epi16(T3, _MM_SHUFFLE(3, 1, 2, 0));
B0 = SIMD_16x16(_mm256_unpacklo_epi32(T0, T1));
B1 = SIMD_16x16(_mm256_unpackhi_epi32(T0, T1));
B2 = SIMD_16x16(_mm256_unpacklo_epi32(T2, T3));
B3 = SIMD_16x16(_mm256_unpackhi_epi32(T2, T3));
}
native_type BOTAN_FN_ISA_AVX2 raw() const { return m_simd; }
private:
static SIMD_16x16 BOTAN_FN_ISA_AVX2 splat(uint16_t v) { return SIMD_16x16(_mm256_set1_epi16(v)); }
native_type m_simd;
};
// NOLINTEND(portability-simd-intrinsics)
} // namespace
BOTAN_FN_ISA_AVX2 void IDEA::avx2_idea_op_16(const uint8_t in[128], uint8_t out[128], const uint16_t EK[52]) {
CT::poison(in, 128);
CT::poison(out, 128);
CT::poison(EK, 52);
auto B0 = SIMD_16x16::load_be(in + 0);
auto B1 = SIMD_16x16::load_be(in + 32);
auto B2 = SIMD_16x16::load_be(in + 64);
auto B3 = SIMD_16x16::load_be(in + 96);
SIMD_16x16::transpose_in(B0, B1, B2, B3);
for(size_t i = 0; i != 8; ++i) {
B0 = SIMD_16x16::mul_mod_65537(B0, EK[6 * i + 0]);
B1 += EK[6 * i + 1];
B2 += EK[6 * i + 2];
B3 = SIMD_16x16::mul_mod_65537(B3, EK[6 * i + 3]);
const auto T0 = B2;
B2 ^= B0;
B2 = SIMD_16x16::mul_mod_65537(B2, EK[6 * i + 4]);
const auto T1 = B1;
B1 ^= B3;
B1 += B2;
B1 = SIMD_16x16::mul_mod_65537(B1, EK[6 * i + 5]);
B2 += B1;
B0 ^= B1;
B1 ^= T0;
B3 ^= B2;
B2 ^= T1;
}
B0 = SIMD_16x16::mul_mod_65537(B0, EK[48]);
B1 += EK[50];
B2 += EK[49];
B3 = SIMD_16x16::mul_mod_65537(B3, EK[51]);
SIMD_16x16::transpose_out(B0, B2, B1, B3);
B0.store_be(out + 0);
B2.store_be(out + 32);
B1.store_be(out + 64);
B3.store_be(out + 96);
CT::unpoison(in, 128);
CT::unpoison(out, 128);
CT::unpoison(EK, 52);
}
} // namespace Botan