crypton-2.0.0: cbits/aes/gcm_x86_asm.c
/*
* Copyright (c) 2026 Kazu Yamamoto <kazu@iij.ad.jp>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of his contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* What the stitched AES-GCM assembly in cbits/asm needs in order to be
* called: the two pieces of state it reads are laid out the way OpenSSL
* lays them out, which is not the way crypton does, and neither is worth
* changing the rest of the library for. Both are built here, per message,
* from the key schedule and the H that crypton already has.
*/
#include "crypton_cpu.h"
#ifdef WITH_X86_GCM_ASM
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <wmmintrin.h>
#include <crypton_aes.h>
#include <aes/gcm_x86_asm.h>
#ifdef WITH_TARGET_ATTRIBUTES
#define TARGET_PCLMUL __attribute__((target("sse4.1,pclmul")))
#else
#define TARGET_PCLMUL
#endif
#define ALIGNMENT(n) __attribute__((aligned(n)))
/*
* cbits/asm/aesni-gcm-x86_64-*.S. Both answer how many bytes they got
* through, which is a multiple of six blocks and is zero if the message is
* shorter than they are willing to start on.
*/
size_t crypton_gcm_asm_encrypt(const void *in, void *out, size_t len,
const void *key, uint8_t ivec[16], void *Xi);
size_t crypton_gcm_asm_decrypt(const void *in, void *out, size_t len,
const void *key, uint8_t ivec[16], void *Xi);
/*
* The key schedule as the assembly reads it: the encryption round keys,
* and at offset 240 the number of rounds less one, which is the count
* OpenSSL's AES-NI key setup leaves there -- 9, 11 and 13 -- and what the
* assembly compares against to tell the three key sizes apart.
*/
struct asm_key {
uint8_t rd_key[240];
uint32_t rounds;
};
/*
* The assembly reads the running tag from the front of this and the powers
* of H from 32 bytes in, which is where they sit in OpenSSL's GCM context
* -- the 16 bytes between them hold H itself there and nothing here.
* Powers up to the sixth are used, since the loop takes six blocks at a
* time, and each pair of them is followed by the halves the Karatsuba
* multiplication would otherwise have to add up again.
*/
struct asm_gcm {
block128 xi;
block128 unused;
block128 htable[9];
};
/*
* H, and every power of it, is kept shifted up by one bit: GCM numbers the
* bits of a field element the other way round from the way the carry-less
* multiply does, and pre-shifting the operand is what saves the correction
* after each multiply. The bit that falls off the top is the one that the
* polynomial reduces.
*/
TARGET_PCLMUL
static __m128i twist(__m128i h)
{
const __m128i poly = _mm_set_epi64x(0xc200000000000000ULL, 1);
__m128i carried = _mm_slli_si128(_mm_srli_epi64(h, 63), 8);
__m128i top = _mm_shuffle_epi32(h, 0xff);
__m128i reduce = _mm_cmpgt_epi32(_mm_setzero_si128(), top);
h = _mm_or_si128(_mm_slli_epi64(h, 1), carried);
return _mm_xor_si128(h, _mm_and_si128(reduce, poly));
}
/* the two halves of a value added together, which is the term Karatsuba
* needs and which does not depend on what it is multiplied by */
TARGET_PCLMUL
static __m128i fold(__m128i a)
{
return _mm_xor_si128(a, _mm_unpackhi_epi64(a, a));
}
/*
* The table the assembly reads: the first six powers of H, each shifted up
* by one, and after each pair the two halves of both of them added
* together, which is the term the Karatsuba multiplication would otherwise
* work out for itself every time.
*
* The powers are not computed here. crypton's own table already holds
* H^1 to H^8, in the byte order the multiply wants and unshifted, so
* twisting each one is the whole of the work -- which is why this is worth
* doing per message rather than keeping a second table in the context.
*/
TARGET_PCLMUL
static void init_htable(struct asm_gcm *st, const aes_gcm *gcm)
{
int i;
for (i = 0; i < 3; i++) {
__m128i odd = twist(_mm_loadu_si128(
(const __m128i *) &gcm->htable[2 * i]));
__m128i even = twist(_mm_loadu_si128(
(const __m128i *) &gcm->htable[2 * i + 1]));
_mm_storeu_si128((__m128i *) &st->htable[3 * i + 0], odd);
_mm_storeu_si128((__m128i *) &st->htable[3 * i + 1], even);
_mm_storeu_si128((__m128i *) &st->htable[3 * i + 2],
_mm_unpacklo_epi64(fold(odd), fold(even)));
}
}
/*
* The counter block, whose bottom 32 bits are what counts, as GCM has it.
* crypton keeps the value it last used and the assembly wants the one it
* is to use next, so this steps between the two conventions at each end.
*/
static void ctr32_bump(uint8_t ivec[16], uint32_t delta)
{
uint32_t c = ((uint32_t) ivec[12] << 24) | ((uint32_t) ivec[13] << 16)
| ((uint32_t) ivec[14] << 8) | (uint32_t) ivec[15];
c += delta;
ivec[12] = (uint8_t) (c >> 24);
ivec[13] = (uint8_t) (c >> 16);
ivec[14] = (uint8_t) (c >> 8);
ivec[15] = (uint8_t) c;
}
/*
* How much of the message to hand over. The assembly works in groups of
* six blocks, and what it leaves behind goes to a loop that works in groups
* of eight and then one at a time. Handing over every group it could take
* often leaves two or four blocks to go through one at a time, which at a
* multiply apiece costs more than the three groups it takes to line the
* remainder up on eight. So the length is rounded down to whichever number
* of six-block groups within reach leaves the least behind, modulo eight.
*/
static uint32_t handover(uint32_t blocks)
{
uint32_t groups = blocks / 6;
uint32_t best = groups;
uint32_t least = (blocks - 6 * groups) % 8;
uint32_t i;
for (i = 1; i <= 3 && groups >= i; i++) {
uint32_t left = (blocks - 6 * (groups - i)) % 8;
if (left < least) {
least = left;
best = groups - i;
}
}
return best * 6 * 16;
}
int crypton_gcm_asm_usable(void)
{
static int resolved = 0;
static int usable = 0;
if (!resolved) {
const uint32_t need = CRYPTON_X86_AVX | CRYPTON_X86_MOVBE
| CRYPTON_X86_PCLMUL;
usable = (crypton_x86_simd_features() & need) == need;
resolved = 1;
}
return usable;
}
TARGET_PCLMUL
static uint32_t bulk(int encrypt, uint8_t *output, aes_gcm *gcm, aes_key *key,
const uint8_t *input, uint32_t length)
{
struct asm_gcm st ALIGNMENT(16);
struct asm_key k ALIGNMENT(16);
uint8_t ivec[16] ALIGNMENT(16);
uint32_t hand;
size_t done;
if (!crypton_gcm_asm_usable())
return 0;
/* below its own minimum the assembly does nothing, so in that case
* give it everything and let it decide */
hand = handover(length / 16);
if (hand < (encrypt ? 0x60 * 3 : 0x60))
hand = length;
memcpy(k.rd_key, key->data, 16 * (size_t) (key->nbr + 1));
k.rounds = (uint32_t) key->nbr - 1;
memcpy(&st.xi, &gcm->tag, 16);
memcpy(ivec, &gcm->civ, 16);
ctr32_bump(ivec, 1);
init_htable(&st, gcm);
done = encrypt
? crypton_gcm_asm_encrypt(input, output, hand, &k, ivec, &st.xi)
: crypton_gcm_asm_decrypt(input, output, hand, &k, ivec, &st.xi);
if (done > 0) {
ctr32_bump(ivec, 0xffffffff);
memcpy(&gcm->tag, &st.xi, 16);
memcpy(&gcm->civ, ivec, 16);
}
return (uint32_t) done;
}
uint32_t crypton_gcm_asm_bulk_encrypt(uint8_t *output, aes_gcm *gcm, aes_key *key,
const uint8_t *input, uint32_t length)
{
return bulk(1, output, gcm, key, input, length);
}
uint32_t crypton_gcm_asm_bulk_decrypt(uint8_t *output, aes_gcm *gcm, aes_key *key,
const uint8_t *input, uint32_t length)
{
return bulk(0, output, gcm, key, input, length);
}
#endif