std1.0.0-beta-7 SafeC docs

Source crypto/aes.sc

View in the repository

// SafeC Standard Library — AES Block Cipher
#pragma once
#include <std/stddef.h>
#include <std/crypto/aes.h>
#include <std/mem.h>

namespace std {

extern unsigned char* memcpy(unsigned char* d, const unsigned char* s, size_t n);

// ── AES S-box and inverse S-box ───────────────────────────────────────────────

static unsigned char aes_sbox_[256] = {
    0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
    0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
    0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
    0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
    0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
    0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
    0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
    0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
    0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
    0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
    0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
    0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
    0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
    0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
    0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
    0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16
};

static unsigned char aes_inv_sbox_[256] = {
    0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb,
    0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb,
    0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e,
    0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25,
    0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92,
    0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84,
    0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06,
    0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b,
    0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73,
    0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e,
    0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b,
    0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4,
    0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f,
    0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef,
    0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61,
    0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d
};

// Rcon (round constants for key expansion): Rcon[i] = 2^(i-1) in GF(2^8)
static unsigned char aes_rcon_[11] = {
    0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36
};

// ── GF(2^8) multiply (xtime) ──────────────────────────────────────────────────
static unsigned char xtime_(unsigned char b) {
    return (unsigned char)(((unsigned int)b << 1) ^
                            (((unsigned int)b >> 7) & 1 ? 0x1b : 0x00));
}

static unsigned char gmul_(unsigned char a, unsigned char b) {
    unsigned char p = (unsigned char)0;
    int i = 0;
    while (i < 8) {
        if ((b & (unsigned char)1) != (unsigned char)0) { p = p ^ a; }
        int hi = (a & (unsigned char)0x80) != (unsigned char)0 ? 1 : 0;
        a = (unsigned char)(a << 1);
        if (hi != 0) { a = a ^ (unsigned char)0x1b; }
        b = b >> 1;
        i = i + 1;
    }
    return p;
}

// ── Key expansion helpers ─────────────────────────────────────────────────────

static unsigned int sub_word_(unsigned int w) {
    return ((unsigned int)aes_sbox_[(w >> 24) & 0xFF] << 24)
         | ((unsigned int)aes_sbox_[(w >> 16) & 0xFF] << 16)
         | ((unsigned int)aes_sbox_[(w >>  8) & 0xFF] <<  8)
         | ((unsigned int)aes_sbox_[w & 0xFF]);
}

static unsigned int rot_word_(unsigned int w) {
    return (w << 8) | (w >> 24);
}

static void expand_key_(const unsigned char* key, int nk, int nr,
                          unsigned int* ks) {
    int i = 0;
    while (i < nk) {
        unsafe {
            ks[i] = ((unsigned int)key[4*i]   << 24)
                  | ((unsigned int)key[4*i+1] << 16)
                  | ((unsigned int)key[4*i+2] <<  8)
                  | ((unsigned int)key[4*i+3]);
        }
        i = i + 1;
    }
    while (i < 4 * (nr + 1)) {
        unsafe {
            unsigned int tmp = ks[i - 1];
            if (i % nk == 0) {
                tmp = sub_word_(rot_word_(tmp)) ^ ((unsigned int)aes_rcon_[i / nk] << 24);
            } else if (nk > 6 && i % nk == 4) {
                tmp = sub_word_(tmp);
            }
            ks[i] = ks[i - nk] ^ tmp;
        }
        i = i + 1;
    }
}

// ── AddRoundKey ───────────────────────────────────────────────────────────────

static void add_round_key_(unsigned char* state, const unsigned int* rk) {
    int i = 0;
    while (i < 4) {
        int j = 0;
        while (j < 4) {
            unsafe {
                // state is column-major (state[c*4 + r], FIPS-197 input
                // order): column i takes round-key word i, row j its byte j.
                state[i*4+j] = state[i*4+j] ^
                    (unsigned char)((rk[i] >> (unsigned int)(24 - 8*j)) & 0xFF);
            }
            j = j + 1;
        }
        i = i + 1;
    }
}

// ── SubBytes ──────────────────────────────────────────────────────────────────

static void sub_bytes_(unsigned char* state) {
    int i = 0;
    while (i < 16) {
        unsafe { state[i] = aes_sbox_[state[i]]; }
        i = i + 1;
    }
}

static void inv_sub_bytes_(unsigned char* state) {
    int i = 0;
    while (i < 16) {
        unsafe { state[i] = aes_inv_sbox_[state[i]]; }
        i = i + 1;
    }
}

// ── ShiftRows ─────────────────────────────────────────────────────────────────

static void shift_rows_(unsigned char* s) {
    unsafe {
        unsigned char t;
        // Row 1: shift left 1
        t=s[1]; s[1]=s[5]; s[5]=s[9]; s[9]=s[13]; s[13]=t;
        // Row 2: shift left 2
        t=s[2]; s[2]=s[10]; s[10]=t;
        t=s[6]; s[6]=s[14]; s[14]=t;
        // Row 3: shift left 3 (= right 1)
        t=s[15]; s[15]=s[11]; s[11]=s[7]; s[7]=s[3]; s[3]=t;
    }
}

static void inv_shift_rows_(unsigned char* s) {
    unsafe {
        unsigned char t;
        t=s[13]; s[13]=s[9]; s[9]=s[5]; s[5]=s[1]; s[1]=t;
        t=s[2]; s[2]=s[10]; s[10]=t;
        t=s[6]; s[6]=s[14]; s[14]=t;
        t=s[3]; s[3]=s[7]; s[7]=s[11]; s[11]=s[15]; s[15]=t;
    }
}

// ── MixColumns ────────────────────────────────────────────────────────────────

static void mix_columns_(unsigned char* s) {
    int c = 0;
    while (c < 4) {
        unsafe {
            unsigned char a0=s[c*4+0]; unsigned char a1=s[c*4+1]; unsigned char a2=s[c*4+2]; unsigned char a3=s[c*4+3];
            s[c*4+0] = gmul_(2,a0)^gmul_(3,a1)^a2^a3;
            s[c*4+1] = a0^gmul_(2,a1)^gmul_(3,a2)^a3;
            s[c*4+2] = a0^a1^gmul_(2,a2)^gmul_(3,a3);
            s[c*4+3] = gmul_(3,a0)^a1^a2^gmul_(2,a3);
        }
        c = c + 1;
    }
}

static void inv_mix_columns_(unsigned char* s) {
    int c = 0;
    while (c < 4) {
        unsafe {
            unsigned char a0=s[c*4+0]; unsigned char a1=s[c*4+1]; unsigned char a2=s[c*4+2]; unsigned char a3=s[c*4+3];
            s[c*4+0]=gmul_(14,a0)^gmul_(11,a1)^gmul_(13,a2)^gmul_(9,a3);
            s[c*4+1]=gmul_(9,a0) ^gmul_(14,a1)^gmul_(11,a2)^gmul_(13,a3);
            s[c*4+2]=gmul_(13,a0)^gmul_(9,a1) ^gmul_(14,a2)^gmul_(11,a3);
            s[c*4+3]=gmul_(11,a0)^gmul_(13,a1)^gmul_(9,a2) ^gmul_(14,a3);
        }
        c = c + 1;
    }
}

// ── Core encrypt / decrypt ────────────────────────────────────────────────────

void AesCtx::encrypt_block_n_(&unsigned char block) {
    // Copy input into the column-major state (byte order is already column-major)
    unsigned char state[16];
    int i = 0;
    while (i < 4) {
        int j = 0;
        while (j < 4) {
            unsafe { state[i*4+j] = block[i*4+j]; }
            j = j + 1;
        }
        i = i + 1;
    }

    add_round_key_(state, self.ks);

    int r = 1;
    while (r < self.rounds) {
        sub_bytes_(state);
        shift_rows_(state);
        mix_columns_(state);
        unsafe { add_round_key_(state, self.ks + r * 4); }
        r = r + 1;
    }
    sub_bytes_(state);
    shift_rows_(state);
    unsafe { add_round_key_(state, self.ks + self.rounds * 4); }

    // Write back (same column-major byte order)
    i = 0;
    while (i < 4) {
        int j = 0;
        while (j < 4) {
            unsafe { block[i*4+j] = state[i*4+j]; }
            j = j + 1;
        }
        i = i + 1;
    }
}

void AesCtx::encrypt_block([]unsigned char block_s) {
    &unsigned char block = crypto_out_(block_s, 16UL);
    self.encrypt_block_n_(block);
}

void AesCtx::decrypt_block_n_(&unsigned char block) {
    unsigned char state[16];
    int i = 0;
    while (i < 4) {
        int j = 0;
        while (j < 4) {
            unsafe { state[i*4+j] = block[i*4+j]; }
            j = j + 1;
        }
        i = i + 1;
    }

    unsafe { add_round_key_(state, self.ks + self.rounds * 4); }

    int r = self.rounds - 1;
    while (r >= 1) {
        inv_shift_rows_(state);
        inv_sub_bytes_(state);
        unsafe { add_round_key_(state, self.ks + r * 4); }
        inv_mix_columns_(state);
        r = r - 1;
    }
    inv_shift_rows_(state);
    inv_sub_bytes_(state);
    add_round_key_(state, self.ks);

    i = 0;
    while (i < 4) {
        int j = 0;
        while (j < 4) {
            unsafe { block[i*4+j] = state[i*4+j]; }
            j = j + 1;
        }
        i = i + 1;
    }
}

void AesCtx::decrypt_block([]unsigned char block_s) {
    &unsigned char block = crypto_out_(block_s, 16UL);
    self.decrypt_block_n_(block);
}

inline void AesCtx::set_iv_n_(const &unsigned char iv) {
    unsafe { memcpy((unsigned char*)self.iv, (const unsigned char*)iv, (unsigned long)AES_BLOCK_SIZE); }
}

inline void AesCtx::set_iv([]const unsigned char iv_s) {
    const &unsigned char iv = crypto_in_(iv_s, 16UL);
    self.set_iv_n_(iv);
}

inline void AesCtx::destroy() { self.drop(); }

inline void AesCtx::drop() {
    unsafe {
        mem_zero_secure_raw_((unsigned char*)self.ks, (unsigned long)AES_KS_WORDS * (unsigned long)sizeof(unsigned int));
        mem_zero_secure_raw_((unsigned char*)self.iv, (unsigned long)AES_BLOCK_SIZE);
    }
    self.rounds = 0;
}

inline void AesCtx::cbc_encrypt_n_(&unsigned char data, unsigned long len) {
    unsigned long blocks = len / (unsigned long)AES_BLOCK_SIZE;
    unsigned long b = (unsigned long)0;
    while (b < blocks) {
        unsafe {
            unsigned char* blk = (unsigned char*)data + b * (unsigned long)AES_BLOCK_SIZE;
            int k = 0;
            while (k < AES_BLOCK_SIZE) {
                blk[k] = blk[k] ^ self.iv[k];
                k = k + 1;
            }
            self.encrypt_block_n_((&unsigned char)blk);
            memcpy((unsigned char*)self.iv, (const unsigned char*)blk, (unsigned long)AES_BLOCK_SIZE);
        }
        b = b + (unsigned long)1;
    }
}

inline void AesCtx::cbc_encrypt([]unsigned char data_s) {
    unsigned long len = data_s.len;
    &unsigned char data = crypto_out_(data_s, 0UL);
    self.cbc_encrypt_n_(data, len);
}

inline void AesCtx::cbc_decrypt_n_(&unsigned char data, unsigned long len) {
    unsigned long blocks = len / (unsigned long)AES_BLOCK_SIZE;
    unsigned long b = (unsigned long)0;
    while (b < blocks) {
        unsafe {
            unsigned char* blk = (unsigned char*)data + b * (unsigned long)AES_BLOCK_SIZE;
            unsigned char prev[16];
            memcpy((unsigned char*)prev, (const unsigned char*)blk, (unsigned long)AES_BLOCK_SIZE);
            self.decrypt_block_n_((&unsigned char)blk);
            int k = 0;
            while (k < AES_BLOCK_SIZE) {
                blk[k] = blk[k] ^ self.iv[k];
                k = k + 1;
            }
            memcpy((unsigned char*)self.iv, (const unsigned char*)prev, (unsigned long)AES_BLOCK_SIZE);
        }
        b = b + (unsigned long)1;
    }
}

inline void AesCtx::cbc_decrypt([]unsigned char data_s) {
    unsigned long len = data_s.len;
    &unsigned char data = crypto_out_(data_s, 0UL);
    self.cbc_decrypt_n_(data, len);
}


// ── GCM (NIST SP 800-38D) ─────────────────────────────────────────────────────
// GHASH multiplies in GF(2^128) with the bit-reflected convention of the
// spec (bit 0 = the MSB of byte 0). Blocks are held as two big-endian
// 64-bit halves. The multiply is branch-free in the data (masks, not ifs),
// so its timing doesn't depend on H or the message.

static unsigned long long gcm_load64_(const unsigned char* p) {
    unsigned long long v = 0ULL;
    int i = 0;
    unsafe { while (i < 8) { v = (v << 8ULL) | (unsigned long long)p[i]; i = i + 1; } }
    return v;
}

static void gcm_store64_(unsigned char* p, unsigned long long v) {
    int i = 7;
    unsafe { while (i >= 0) { p[i] = (unsigned char)(v & 0xFFULL); v = v >> 8ULL; i = i - 1; } }
}

// x := x * h in GF(2^128).
static void gcm_mul_(unsigned long long* x, unsigned long long hh, unsigned long long hl) {
    unsigned long long zh = 0ULL;
    unsigned long long zl = 0ULL;
    unsigned long long vh = hh;
    unsigned long long vl = hl;
    unsafe {
        int i = 0;
        while (i < 128) {
            unsigned long long word = i < 64 ? x[0] : x[1];
            unsigned long long bit = (word >> (unsigned long long)(63 - (i & 63))) & 1ULL;
            unsigned long long m = 0ULL - bit;
            zh = zh ^ (vh & m);
            zl = zl ^ (vl & m);
            unsigned long long lsb = 0ULL - (vl & 1ULL);
            vl = (vl >> 1ULL) | (vh << 63ULL);
            vh = (vh >> 1ULL) ^ (0xE100000000000000ULL & lsb);
            i = i + 1;
        }
        x[0] = zh;
        x[1] = zl;
    }
}

// Absorb len bytes (zero-padded to whole blocks) into the GHASH state y.
static void gcm_ghash_(unsigned long long* y, unsigned long long hh, unsigned long long hl,
                       const unsigned char* p, unsigned long len) {
    unsigned long off = 0UL;
    unsafe {
        while (off < len) {
            unsigned char blk[16];
            unsigned long n = len - off < 16UL ? len - off : 16UL;
            unsigned long k = 0UL;
            while (k < 16UL) { blk[k] = k < n ? p[off + k] : (unsigned char)0; k = k + 1UL; }
            y[0] = y[0] ^ gcm_load64_((const unsigned char*)&blk[0]);
            y[1] = y[1] ^ gcm_load64_((const unsigned char*)&blk[8]);
            gcm_mul_(y, hh, hl);
            off = off + n;
        }
    }
}

// CTR mode starting at counter block 'ctr' (its last 32 bits incremented
// before each block, as GCM's inc32 does), XORed into data in place.
static void gcm_ctr_(&AesCtx ctx, const unsigned char* j0, unsigned char* data, unsigned long len) {
    unsafe {
        unsigned char ctr[16];
        memcpy((unsigned char*)ctr, (const unsigned char*)j0, 16UL);
        unsigned long off = 0UL;
        while (off < len) {
            unsigned int c = ((unsigned int)ctr[12] << 24) | ((unsigned int)ctr[13] << 16)
                           | ((unsigned int)ctr[14] << 8) | (unsigned int)ctr[15];
            c = c + 1u;
            ctr[12] = (unsigned char)(c >> 24); ctr[13] = (unsigned char)(c >> 16);
            ctr[14] = (unsigned char)(c >> 8);  ctr[15] = (unsigned char)c;
            unsigned char ks[16];
            memcpy((unsigned char*)ks, (const unsigned char*)ctr, 16UL);
            ctx.encrypt_block_n_((&unsigned char)&ks[0]);
            unsigned long k = 0UL;
            while (k < 16UL && off + k < len) { data[off + k] = data[off + k] ^ ks[k]; k = k + 1UL; }
            off = off + 16UL;
        }
    }
}

static void gcm_j0_(const unsigned char* iv, unsigned char* j0) {
    unsafe {
        memcpy((unsigned char*)j0, (const unsigned char*)iv, 12UL);
        j0[12] = (unsigned char)0; j0[13] = (unsigned char)0; j0[14] = (unsigned char)0; j0[15] = (unsigned char)1;
    }
}

// The tag over (aad, ciphertext): GHASH, then XOR with E(K, J0).
static void gcm_tag_(&AesCtx ctx, const unsigned char* iv, const unsigned char* aad, unsigned long aad_len,
                     const unsigned char* ct, unsigned long len, unsigned char* tag) {
    unsafe {
        unsigned char h[16];
        int i = 0;
        while (i < 16) { h[i] = (unsigned char)0; i = i + 1; }
        ctx.encrypt_block_n_((&unsigned char)&h[0]);
        unsigned long long hh = gcm_load64_((const unsigned char*)&h[0]);
        unsigned long long hl = gcm_load64_((const unsigned char*)&h[8]);
        unsigned long long y[2];
        y[0] = 0ULL;
        y[1] = 0ULL;
        if (aad_len > 0UL) { gcm_ghash_((unsigned long long*)y, hh, hl, aad, aad_len); }
        gcm_ghash_((unsigned long long*)y, hh, hl, ct, len);
        y[0] = y[0] ^ ((unsigned long long)aad_len * 8ULL);
        y[1] = y[1] ^ ((unsigned long long)len * 8ULL);
        gcm_mul_((unsigned long long*)y, hh, hl);
        unsigned char j0[16];
        gcm_j0_(iv, (unsigned char*)j0);
        ctx.encrypt_block_n_((&unsigned char)&j0[0]);
        gcm_store64_(tag, y[0]);
        gcm_store64_(tag + 8, y[1]);
        i = 0;
        while (i < 16) { tag[i] = tag[i] ^ j0[i]; i = i + 1; }
        mem_zero_secure_raw_((unsigned char*)h, 16UL);
    }
}

void AesCtx::gcm_encrypt_n_(const &unsigned char iv, const ?&unsigned char aad, unsigned long aad_len,
                         &unsigned char data, unsigned long len, &unsigned char tag) {
    unsafe {
        unsigned char j0[16];
        gcm_j0_((const unsigned char*)iv, (unsigned char*)j0);
        gcm_ctr_(self, (const unsigned char*)j0, (unsigned char*)data, len);
        gcm_tag_(self, (const unsigned char*)iv, (const unsigned char*)aad, aad_len,
                 (const unsigned char*)data, len, (unsigned char*)tag);
    }
}

void AesCtx::gcm_encrypt([]const unsigned char iv_s, []const unsigned char aad_s, []unsigned char data_s, []unsigned char tag_s) {
    const &unsigned char iv = crypto_in_(iv_s, 12UL);
    unsigned long aad_len = aad_s.len;
    const ?&unsigned char aad = crypto_opt_(aad_s);
    unsigned long len = data_s.len;
    &unsigned char data = crypto_out_(data_s, 0UL);
    &unsigned char tag = crypto_out_(tag_s, 16UL);
    self.gcm_encrypt_n_(iv, aad, aad_len, data, len, tag);
}

int AesCtx::gcm_decrypt_n_(const &unsigned char iv, const ?&unsigned char aad, unsigned long aad_len,
                        &unsigned char data, unsigned long len, const &unsigned char tag) {
    unsigned char want[16];
    unsigned int diff = 0u;
    unsafe {
        gcm_tag_(self, (const unsigned char*)iv, (const unsigned char*)aad, aad_len,
                 (const unsigned char*)data, len, (unsigned char*)want);
        int i = 0;
        while (i < 16) { diff = diff | (unsigned int)(want[i] ^ tag[i]); i = i + 1; }
    }
    if (diff != 0u) { return 0; }
    unsafe {
        unsigned char j0[16];
        gcm_j0_((const unsigned char*)iv, (unsigned char*)j0);
        gcm_ctr_(self, (const unsigned char*)j0, (unsigned char*)data, len);
    }
    return 1;
}

int AesCtx::gcm_decrypt([]const unsigned char iv_s, []const unsigned char aad_s, []unsigned char data_s, []const unsigned char tag_s) {
    const &unsigned char iv = crypto_in_(iv_s, 12UL);
    unsigned long aad_len = aad_s.len;
    const ?&unsigned char aad = crypto_opt_(aad_s);
    unsigned long len = data_s.len;
    &unsigned char data = crypto_out_(data_s, 0UL);
    const &unsigned char tag = crypto_in_(tag_s, 16UL);
    return self.gcm_decrypt_n_(iv, aad, aad_len, data, len, tag);
}

// ── Constructors ──────────────────────────────────────────────────────────────

inline struct AesCtx AesCtx::new_128_n_(const &unsigned char key) {
    struct AesCtx ctx;
    ctx.rounds = AES128_ROUNDS;
    unsafe { expand_key_((const unsigned char*)key, 4, AES128_ROUNDS, ctx.ks); }
    unsafe { memset_byte_((unsigned char*)ctx.iv, 0, AES_BLOCK_SIZE); }
    return ctx;
}

inline struct AesCtx AesCtx::new_128([]const unsigned char key_s) {
    const &unsigned char key = crypto_in_(key_s, 16UL);
    return AesCtx::new_128_n_(key);
}

inline struct AesCtx AesCtx::new_256_n_(const &unsigned char key) {
    struct AesCtx ctx;
    ctx.rounds = AES256_ROUNDS;
    unsafe { expand_key_((const unsigned char*)key, 8, AES256_ROUNDS, ctx.ks); }
    unsafe { memset_byte_((unsigned char*)ctx.iv, 0, AES_BLOCK_SIZE); }
    return ctx;
}

inline struct AesCtx AesCtx::new_256([]const unsigned char key_s) {
    const &unsigned char key = crypto_in_(key_s, 32UL);
    return AesCtx::new_256_n_(key);
}

// Simple zero-init helper for IV
static void memset_byte_(unsigned char* p, unsigned char v, int n) {
    int i = 0;
    while (i < n) {
        unsafe { p[i] = v; }
        i = i + 1;
    }
}

} // namespace std