Skip to content

File syn_chacha20poly1305.c

File List > crypto > syn_chacha20poly1305.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_CHACHA20POLY1305) || SYN_USE_CHACHA20POLY1305

#include "../util/syn_pack.h"
#include "syn_chacha20poly1305.h"

#include <string.h>

/* ── Helpers ────────────────────────────────────────────────────────────── */

static inline uint32_t load32_le(const uint8_t *p)
{
    return syn_peek_u32_le(p, 0);
}

static inline void store32_le(uint8_t *p, uint32_t v)
{
    syn_poke_u32_le(v, p, 0);
}

static inline void store64_le(uint8_t *p, uint64_t v)
{
    store32_le(p, (uint32_t)(v));
    store32_le(p + 4, (uint32_t)(v >> 32));
}

static inline uint32_t rotl32(uint32_t x, unsigned n)
{
    return (x << n) | (x >> (32 - n));
}

/* ═══════════════════════════════════════════════════════════════════════════
 *  ChaCha20
 * ═══════════════════════════════════════════════════════════════════════════ */

#define QR(a, b, c, d)     \
    do {                   \
        a += b;            \
        d ^= a;            \
        d = rotl32(d, 16); \
        c += d;            \
        b ^= c;            \
        b = rotl32(b, 12); \
        a += b;            \
        d ^= a;            \
        d = rotl32(d, 8);  \
        c += d;            \
        b ^= c;            \
        b = rotl32(b, 7);  \
    } while (0)

static void chacha20_block_core(uint32_t out[16], const uint32_t in[16])
{
    int i;
    for (i = 0; i < 16; i++)
        out[i] = in[i];

    for (i = 0; i < 10; i++) {
        /* Column rounds */
        QR(out[0], out[4], out[8], out[12]);
        QR(out[1], out[5], out[9], out[13]);
        QR(out[2], out[6], out[10], out[14]);
        QR(out[3], out[7], out[11], out[15]);
        /* Diagonal rounds */
        QR(out[0], out[5], out[10], out[15]);
        QR(out[1], out[6], out[11], out[12]);
        QR(out[2], out[7], out[8], out[13]);
        QR(out[3], out[4], out[9], out[14]);
    }

    for (i = 0; i < 16; i++)
        out[i] += in[i];
}

static void chacha20_init(uint32_t state[16], const uint8_t key[32], const uint8_t nonce[12],
                          uint32_t counter)
{
    /* "expand 32-byte k" */
    state[0] = 0x61707865UL;
    state[1] = 0x3320646EUL;
    state[2] = 0x79622D32UL;
    state[3] = 0x6B206574UL;

    /* Key (8 words) */
    state[4] = load32_le(key + 0);
    state[5] = load32_le(key + 4);
    state[6] = load32_le(key + 8);
    state[7] = load32_le(key + 12);
    state[8] = load32_le(key + 16);
    state[9] = load32_le(key + 20);
    state[10] = load32_le(key + 24);
    state[11] = load32_le(key + 28);

    /* Counter + nonce */
    state[12] = counter;
    state[13] = load32_le(nonce + 0);
    state[14] = load32_le(nonce + 4);
    state[15] = load32_le(nonce + 8);
}

void syn_chacha20_block(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter,
                        uint8_t out[64])
{
    uint32_t state[16], block[16];
    int i;

    chacha20_init(state, key, nonce, counter);
    chacha20_block_core(block, state);

    for (i = 0; i < 16; i++) {
        store32_le(out + i * 4, block[i]);
    }
}

void syn_chacha20_xor(const uint8_t key[32], const uint8_t nonce[12], uint32_t counter,
                      const uint8_t *in, size_t len, uint8_t *out)
{
    uint32_t state[16], block[16];
    uint8_t keystream[64];
    size_t i;

    chacha20_init(state, key, nonce, counter);

    while (len > 0) {
        chacha20_block_core(block, state);

        /* Serialize keystream */
        for (i = 0; i < 16; i++) {
            store32_le(keystream + i * 4, block[i]);
        }

        /* XOR up to 64 bytes */
        size_t chunk = len < 64 ? len : 64;
        for (i = 0; i < chunk; i++) {
            out[i] = in[i] ^ keystream[i];
        }

        in += chunk;
        out += chunk;
        len -= chunk;

        state[12]++; /* Increment counter */
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
 *  Poly1305  (donna-style, 5 × 26-bit limbs)
 * ═══════════════════════════════════════════════════════════════════════════ */

typedef struct {
    uint32_t r[5];   
    uint32_t h[5];   
    uint32_t pad[4]; 
} Poly1305_Ctx;

static void poly1305_init(Poly1305_Ctx *ctx, const uint8_t key[32])
{
    /* r = key[0..15], clamped */
    uint32_t t0 = load32_le(key + 0);
    uint32_t t1 = load32_le(key + 4);
    uint32_t t2 = load32_le(key + 8);
    uint32_t t3 = load32_le(key + 12);

    ctx->r[0] = t0 & 0x3FFFFFFUL;
    ctx->r[1] = ((t0 >> 26) | (t1 << 6)) & 0x3FFFF03UL;
    ctx->r[2] = ((t1 >> 20) | (t2 << 12)) & 0x3FFC0FFUL;
    ctx->r[3] = ((t2 >> 14) | (t3 << 18)) & 0x3F03FFFUL;
    ctx->r[4] = (t3 >> 8) & 0x00FFFFFUL;

    /* h = 0 */
    ctx->h[0] = ctx->h[1] = ctx->h[2] = ctx->h[3] = ctx->h[4] = 0;

    /* pad = key[16..31] */
    ctx->pad[0] = load32_le(key + 16);
    ctx->pad[1] = load32_le(key + 20);
    ctx->pad[2] = load32_le(key + 24);
    ctx->pad[3] = load32_le(key + 28);
}

static void poly1305_blocks(Poly1305_Ctx *ctx, const uint8_t *data, size_t len, uint32_t hibit)
{
    uint32_t r0 = ctx->r[0], r1 = ctx->r[1], r2 = ctx->r[2];
    uint32_t r3 = ctx->r[3], r4 = ctx->r[4];
    uint32_t s1 = r1 * 5, s2 = r2 * 5, s3 = r3 * 5, s4 = r4 * 5;
    uint32_t h0 = ctx->h[0], h1 = ctx->h[1], h2 = ctx->h[2];
    uint32_t h3 = ctx->h[3], h4 = ctx->h[4];

    while (len >= 16) {
        /* h += m[i] */
        uint32_t t0 = load32_le(data + 0);
        uint32_t t1 = load32_le(data + 4);
        uint32_t t2 = load32_le(data + 8);
        uint32_t t3 = load32_le(data + 12);

        h0 += t0 & 0x3FFFFFFUL;
        h1 += ((t0 >> 26) | (t1 << 6)) & 0x3FFFFFFUL;
        h2 += ((t1 >> 20) | (t2 << 12)) & 0x3FFFFFFUL;
        h3 += ((t2 >> 14) | (t3 << 18)) & 0x3FFFFFFUL;
        h4 += (t3 >> 8) | hibit;

        /* h *= r */
        uint64_t d0 = (uint64_t)h0 * r0 + (uint64_t)h1 * s4 + (uint64_t)h2 * s3 +
                      (uint64_t)h3 * s2 + (uint64_t)h4 * s1;
        uint64_t d1 = (uint64_t)h0 * r1 + (uint64_t)h1 * r0 + (uint64_t)h2 * s4 +
                      (uint64_t)h3 * s3 + (uint64_t)h4 * s2;
        uint64_t d2 = (uint64_t)h0 * r2 + (uint64_t)h1 * r1 + (uint64_t)h2 * r0 +
                      (uint64_t)h3 * s4 + (uint64_t)h4 * s3;
        uint64_t d3 = (uint64_t)h0 * r3 + (uint64_t)h1 * r2 + (uint64_t)h2 * r1 +
                      (uint64_t)h3 * r0 + (uint64_t)h4 * s4;
        uint64_t d4 = (uint64_t)h0 * r4 + (uint64_t)h1 * r3 + (uint64_t)h2 * r2 +
                      (uint64_t)h3 * r1 + (uint64_t)h4 * r0;

        /* Carry propagation */
        uint32_t c;
        c = (uint32_t)(d0 >> 26);
        h0 = (uint32_t)d0 & 0x3FFFFFFUL;
        d1 += c;
        c = (uint32_t)(d1 >> 26);
        h1 = (uint32_t)d1 & 0x3FFFFFFUL;
        d2 += c;
        c = (uint32_t)(d2 >> 26);
        h2 = (uint32_t)d2 & 0x3FFFFFFUL;
        d3 += c;
        c = (uint32_t)(d3 >> 26);
        h3 = (uint32_t)d3 & 0x3FFFFFFUL;
        d4 += c;
        c = (uint32_t)(d4 >> 26);
        h4 = (uint32_t)d4 & 0x3FFFFFFUL;
        h0 += c * 5;
        c = h0 >> 26;
        h0 &= 0x3FFFFFFUL;
        h1 += c;

        data += 16;
        len -= 16;
    }

    ctx->h[0] = h0;
    ctx->h[1] = h1;
    ctx->h[2] = h2;
    ctx->h[3] = h3;
    ctx->h[4] = h4;
}

static void poly1305_finish(Poly1305_Ctx *ctx, uint8_t mac[16])
{
    uint32_t h0 = ctx->h[0], h1 = ctx->h[1], h2 = ctx->h[2];
    uint32_t h3 = ctx->h[3], h4 = ctx->h[4];
    uint32_t c, g0, g1, g2, g3, g4, mask;

    /* Full carry chain */
    c = h1 >> 26;
    h1 &= 0x3FFFFFFUL;
    h2 += c;
    c = h2 >> 26;
    h2 &= 0x3FFFFFFUL;
    h3 += c;
    c = h3 >> 26;
    h3 &= 0x3FFFFFFUL;
    h4 += c;
    c = h4 >> 26;
    h4 &= 0x3FFFFFFUL;
    h0 += c * 5;
    c = h0 >> 26;
    h0 &= 0x3FFFFFFUL;
    h1 += c;

    /* Compute h - p = h - (2^130 - 5) */
    g0 = h0 + 5;
    c = g0 >> 26;
    g0 &= 0x3FFFFFFUL;
    g1 = h1 + c;
    c = g1 >> 26;
    g1 &= 0x3FFFFFFUL;
    g2 = h2 + c;
    c = g2 >> 26;
    g2 &= 0x3FFFFFFUL;
    g3 = h3 + c;
    c = g3 >> 26;
    g3 &= 0x3FFFFFFUL;
    g4 = h4 + c - (1UL << 26);

    /* Select h or h-p based on carry (constant-time) */
    mask = (g4 >> 31) - 1; /* 0 if g4 < 0 (keep h), all-1s if g4 >= 0 (use g) */
    /* Correction: mask = all-1s if borrow, 0 if no borrow. We want g when no borrow. */
    g0 &= mask;
    g1 &= mask;
    g2 &= mask;
    g3 &= mask;
    g4 &= mask;
    mask = ~mask;
    h0 = (h0 & mask) | g0;
    h1 = (h1 & mask) | g1;
    h2 = (h2 & mask) | g2;
    h3 = (h3 & mask) | g3;
    h4 = (h4 & mask) | g4;

    /* Reassemble into 4 × 32-bit */
    uint32_t f0 = h0 | (h1 << 26);
    uint32_t f1 = (h1 >> 6) | (h2 << 20);
    uint32_t f2 = (h2 >> 12) | (h3 << 14);
    uint32_t f3 = (h3 >> 18) | (h4 << 8);

    /* mac = (h + pad) mod 2^128 */
    uint64_t t;
    t = (uint64_t)f0 + ctx->pad[0];
    f0 = (uint32_t)t;
    t = (uint64_t)f1 + ctx->pad[1] + (t >> 32);
    f1 = (uint32_t)t;
    t = (uint64_t)f2 + ctx->pad[2] + (t >> 32);
    f2 = (uint32_t)t;
    t = (uint64_t)f3 + ctx->pad[3] + (t >> 32);
    f3 = (uint32_t)t;

    store32_le(mac + 0, f0);
    store32_le(mac + 4, f1);
    store32_le(mac + 8, f2);
    store32_le(mac + 12, f3);
}

/* ═══════════════════════════════════════════════════════════════════════════
 *  AEAD Construction (RFC 8439 §2.8)
 * ═══════════════════════════════════════════════════════════════════════════ */

static void aead_mac(const uint8_t poly_key[32], const uint8_t *aad, size_t aad_len,
                     const uint8_t *ct, size_t ct_len, uint8_t tag[16])
{
    Poly1305_Ctx poly;
    poly1305_init(&poly, poly_key);

    /* AAD */
    if (aad_len > 0) {
        poly1305_blocks(&poly, aad, aad_len & ~15UL, 1UL << 24);
        if (aad_len & 15) {
            uint8_t tmp[16];
            memset(tmp, 0, 16);
            memcpy(tmp, aad + (aad_len & ~15UL), aad_len & 15);
            poly1305_blocks(&poly, tmp, 16, 1UL << 24);
        }
    }

    /* Ciphertext */
    if (ct_len > 0) {
        poly1305_blocks(&poly, ct, ct_len & ~15UL, 1UL << 24);
        if (ct_len & 15) {
            uint8_t tmp[16];
            memset(tmp, 0, 16);
            memcpy(tmp, ct + (ct_len & ~15UL), ct_len & 15);
            poly1305_blocks(&poly, tmp, 16, 1UL << 24);
        }
    }

    /* Lengths (8 bytes each, little-endian) */
    {
        uint8_t lens[16];
        store64_le(lens + 0, (uint64_t)aad_len);
        store64_le(lens + 8, (uint64_t)ct_len);
        poly1305_blocks(&poly, lens, 16, 1UL << 24);
    }

    poly1305_finish(&poly, tag);
}

void syn_aead_encrypt(const uint8_t key[32], const uint8_t nonce[12], const uint8_t *aad,
                      size_t aad_len, const uint8_t *plaintext, size_t pt_len, uint8_t *ciphertext,
                      uint8_t tag[16])
{
    /* Step 1: Generate Poly1305 one-time key (counter = 0) */
    uint8_t poly_key[64];
    syn_chacha20_block(key, nonce, 0, poly_key);
    /* Only first 32 bytes are used as the Poly1305 key */

    /* Step 2: Encrypt plaintext (counter starts at 1) */
    syn_chacha20_xor(key, nonce, 1, plaintext, pt_len, ciphertext);

    /* Step 3: Compute MAC over (AAD || ciphertext) */
    aead_mac(poly_key, aad, aad_len, ciphertext, pt_len, tag);
}

bool syn_aead_decrypt(const uint8_t key[32], const uint8_t nonce[12], const uint8_t *aad,
                      size_t aad_len, const uint8_t *ciphertext, size_t ct_len,
                      const uint8_t tag[16], uint8_t *plaintext)
{
    /* Step 1: Generate Poly1305 one-time key */
    uint8_t poly_key[64];
    syn_chacha20_block(key, nonce, 0, poly_key);

    /* Step 2: Verify MAC */
    uint8_t computed_tag[16];
    aead_mac(poly_key, aad, aad_len, ciphertext, ct_len, computed_tag);

    /* Constant-time compare */
    uint8_t diff = 0;
    unsigned i;
    for (i = 0; i < 16; i++) {
        diff |= computed_tag[i] ^ tag[i];
    }
    if (diff != 0) {
        return false;
    }

    /* Step 3: Decrypt (counter starts at 1) */
    syn_chacha20_xor(key, nonce, 1, ciphertext, ct_len, plaintext);

    return true;
}

void syn_aead_encrypt_inplace(const uint8_t key[32], const uint8_t nonce[12], const uint8_t *aad,
                              size_t aad_len, uint8_t *buf, size_t len, uint8_t tag[16])
{
    syn_aead_encrypt(key, nonce, aad, aad_len, buf, len, buf, tag);
}

bool syn_aead_decrypt_inplace(const uint8_t key[32], const uint8_t nonce[12], const uint8_t *aad,
                              size_t aad_len, uint8_t *buf, size_t len, const uint8_t tag[16])
{
    return syn_aead_decrypt(key, nonce, aad, aad_len, buf, len, tag, buf);
}

#endif /* SYN_USE_CHACHA20POLY1305 */