chacha

The ChaCha20 stream cipher (docs.ppad.tech/chacha).
git clone git://git.ppad.tech/chacha.git
Log | Files | Refs | README | LICENSE

commit c36cd59073c97a3cde999102ffd5dca7ccd7c02f
parent 254e76a830d56e383fada03a588fdb9e919f46a9
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 31 Oct 2026 14:29:49 +0400

cbits: add an x86 SSE2 kernel

On x86-64, chacha20_block_native and chacha20_cipher_native now use
SSE2, with the same intra-block layout as the NEON kernel: one
register per row of the state matrix, so that a column round is one
set of element-wise operations, and PSHUFD to rotate rows into and out
of diagonal position.  Rotate-by-16 swaps 16-bit halves with
PSHUFLW/PSHUFHW; the other rotations are shift-shift-or pairs.

SSE2 is part of the x86-64 baseline, so chacha20_native_available
returns 1 there without a runtime check.  The constants row is now
shared between the ARM and x86 kernels, and the stubs only serve other
architectures.

Diffstat:
Mcbits/chacha20_native.c | 181++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mlib/Crypto/Cipher/ChaCha20/Native.hs | 4++--
2 files changed, 177 insertions(+), 8 deletions(-)

diff --git a/cbits/chacha20_native.c b/cbits/chacha20_native.c @@ -6,6 +6,22 @@ #include <arm_neon.h> +#elif defined(__x86_64__) + +#include <emmintrin.h> + +#endif + +#if defined(__aarch64__) || defined(__x86_64__) + +static const uint32_t chacha_constants[4] = { + 0x61707865u, 0x3320646eu, 0x79622d32u, 0x6b206574u +}; + +#endif + +#if defined(__aarch64__) + /* * ChaCha20 NEON kernel using intra-block parallelism. The 16-word * state matrix @@ -72,10 +88,6 @@ static inline void chacha20_core(uint32x4_t *v0, uint32x4_t *v1, *v3 = vaddq_u32(d, s3); } -static const uint32_t chacha_constants[4] = { - 0x61707865u, 0x3320646eu, 0x79622d32u, 0x6b206574u -}; - /* Set up the constant rows of the state from key + nonce. s3 * (counter + nonce) varies per block and is built inside the loop. */ static inline void chacha20_setup(const uint8_t key[32], @@ -174,10 +186,167 @@ int chacha20_native_available(void) { return 1; } +#elif defined(__x86_64__) + +/* + * ChaCha20 SSE2 kernel, using the same intra-block layout as the NEON + * kernel: the state matrix is held in four 128-bit registers, one per + * row, so that a column round is one set of element-wise vector + * operations. Diagonal rounds are reached by left-rotating rows 1, 2 + * and 3 by 1, 2 and 3 lanes respectively with PSHUFD, then rotating + * back. SSE2 is part of the x86-64 baseline, so no runtime check is + * needed. + */ + +/* 32-bit left rotations. Rotate-by-16 swaps the 16-bit halves of + * each lane with PSHUFLW/PSHUFHW; the others are a shift-shift-or + * pair. */ +#define ROTL32_16(x) \ + _mm_shufflehi_epi16(_mm_shufflelo_epi16((x), 0xb1), 0xb1) +#define ROTL32_N(x, n) \ + _mm_or_si128(_mm_slli_epi32((x), (n)), _mm_srli_epi32((x), 32 - (n))) + +#define QUARTER(v0, v1, v2, v3) \ + do { \ + v0 = _mm_add_epi32(v0, v1); \ + v3 = _mm_xor_si128(v3, v0); v3 = ROTL32_16(v3); \ + v2 = _mm_add_epi32(v2, v3); \ + v1 = _mm_xor_si128(v1, v2); v1 = ROTL32_N(v1, 12); \ + v0 = _mm_add_epi32(v0, v1); \ + v3 = _mm_xor_si128(v3, v0); v3 = ROTL32_N(v3, 8); \ + v2 = _mm_add_epi32(v2, v3); \ + v1 = _mm_xor_si128(v1, v2); v1 = ROTL32_N(v1, 7); \ + } while (0) + +/* 20-round ChaCha20 core: 10 iterations of (column + diagonal). */ +static inline void chacha20_core(__m128i *v0, __m128i *v1, + __m128i *v2, __m128i *v3, + __m128i s0, __m128i s1, + __m128i s2, __m128i s3) { + __m128i a = s0, b = s1, c = s2, d = s3; + for (int i = 0; i < 10; i++) { + QUARTER(a, b, c, d); + /* shift rows: row 1 left 1, row 2 left 2, row 3 left 3. */ + b = _mm_shuffle_epi32(b, _MM_SHUFFLE(0, 3, 2, 1)); + c = _mm_shuffle_epi32(c, _MM_SHUFFLE(1, 0, 3, 2)); + d = _mm_shuffle_epi32(d, _MM_SHUFFLE(2, 1, 0, 3)); + QUARTER(a, b, c, d); + /* shift back. */ + b = _mm_shuffle_epi32(b, _MM_SHUFFLE(2, 1, 0, 3)); + c = _mm_shuffle_epi32(c, _MM_SHUFFLE(1, 0, 3, 2)); + d = _mm_shuffle_epi32(d, _MM_SHUFFLE(0, 3, 2, 1)); + } + *v0 = _mm_add_epi32(a, s0); + *v1 = _mm_add_epi32(b, s1); + *v2 = _mm_add_epi32(c, s2); + *v3 = _mm_add_epi32(d, s3); +} + +/* Set up the constant rows of the state from key + nonce. s3 + * (counter + nonce) varies per block and is built inside the loop. */ +static inline void chacha20_setup(const uint8_t key[32], + const uint8_t nonce[12], + __m128i *s0, __m128i *s1, + __m128i *s2, + uint32_t *n0, uint32_t *n1, + uint32_t *n2) { + *s0 = _mm_loadu_si128((const __m128i *)chacha_constants); + *s1 = _mm_loadu_si128((const __m128i *)key); + *s2 = _mm_loadu_si128((const __m128i *)(key + 16)); + memcpy(n0, nonce + 0, 4); + memcpy(n1, nonce + 4, 4); + memcpy(n2, nonce + 8, 4); +} + +/* Build row 3 (counter + nonce) for one block. */ +static inline __m128i chacha20_row3(uint32_t counter, uint32_t n0, + uint32_t n1, uint32_t n2) { + return _mm_setr_epi32((int)counter, (int)n0, (int)n1, (int)n2); +} + +/* + * Generate one 64-byte ChaCha20 keystream block at 'out'. + */ +void chacha20_block_native(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], uint8_t out[64]) { + __m128i s0, s1, s2; + uint32_t n0, n1, n2; + chacha20_setup(key, nonce, &s0, &s1, &s2, &n0, &n1, &n2); + + __m128i s3 = chacha20_row3(counter, n0, n1, n2); + __m128i v0, v1, v2, v3; + chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3); + + _mm_storeu_si128((__m128i *)(out + 0), v0); + _mm_storeu_si128((__m128i *)(out + 16), v1); + _mm_storeu_si128((__m128i *)(out + 32), v2); + _mm_storeu_si128((__m128i *)(out + 48), v3); +} + +/* + * Encrypt/decrypt 'inlen' bytes at 'in' into 'out' using ChaCha20 + * with the given key, starting counter, and nonce. Stream cipher, + * so the same routine decrypts. + */ +void chacha20_cipher_native(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], + const uint8_t *in, uint8_t *out, + size_t inlen) { + __m128i s0, s1, s2; + uint32_t n0, n1, n2; + chacha20_setup(key, nonce, &s0, &s1, &s2, &n0, &n1, &n2); + + size_t pos = 0; + while (pos + 64 <= inlen) { + __m128i s3 = chacha20_row3(counter, n0, n1, n2); + __m128i v0, v1, v2, v3; + chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3); + + __m128i i0 = _mm_loadu_si128((const __m128i *)(in + pos + 0)); + __m128i i1 = _mm_loadu_si128((const __m128i *)(in + pos + 16)); + __m128i i2 = _mm_loadu_si128((const __m128i *)(in + pos + 32)); + __m128i i3 = _mm_loadu_si128((const __m128i *)(in + pos + 48)); + + _mm_storeu_si128((__m128i *)(out + pos + 0), + _mm_xor_si128(i0, v0)); + _mm_storeu_si128((__m128i *)(out + pos + 16), + _mm_xor_si128(i1, v1)); + _mm_storeu_si128((__m128i *)(out + pos + 32), + _mm_xor_si128(i2, v2)); + _mm_storeu_si128((__m128i *)(out + pos + 48), + _mm_xor_si128(i3, v3)); + + pos += 64; + counter++; + } + + /* trailing partial block (< 64 bytes) */ + if (pos < inlen) { + __m128i s3 = chacha20_row3(counter, n0, n1, n2); + __m128i v0, v1, v2, v3; + chacha20_core(&v0, &v1, &v2, &v3, s0, s1, s2, s3); + + uint8_t block[64]; + _mm_storeu_si128((__m128i *)(block + 0), v0); + _mm_storeu_si128((__m128i *)(block + 16), v1); + _mm_storeu_si128((__m128i *)(block + 32), v2); + _mm_storeu_si128((__m128i *)(block + 48), v3); + + size_t remaining = inlen - pos; + for (size_t i = 0; i < remaining; i++) { + out[pos + i] = in[pos + i] ^ block[i]; + } + } +} + +int chacha20_native_available(void) { + return 1; +} + #else -/* stubs for non-aarch64 builds; never reached because dispatch is - * gated on 'chacha20_native_available' returning 0 */ +/* stubs for other architectures; never reached because dispatch is + * gated on 'chacha20_native_available' returning 0 */ void chacha20_block_native(const uint8_t *key, uint32_t counter, const uint8_t *nonce, uint8_t *out) { diff --git a/lib/Crypto/Cipher/ChaCha20/Native.hs b/lib/Crypto/Cipher/ChaCha20/Native.hs @@ -7,7 +7,7 @@ -- License: MIT -- Maintainer: Jared Tobin <jared@ppad.tech> -- --- ARM NEON support for the ChaCha20 stream cipher. +-- Vectorised ChaCha20, via ARM NEON or x86 SSE2. module Crypto.Cipher.ChaCha20.Native ( chacha20_native_available @@ -45,7 +45,7 @@ fi = fromIntegral -- api ------------------------------------------------------------------------ --- | Are ARM NEON extensions available? +-- | Is a vectorised kernel available? chacha20_native_available :: Bool chacha20_native_available = unsafeDupablePerformIO c_chacha20_native_available /= 0