/* * (C) 2026 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include #include #include #include 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(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