#include "api.h"
#include "crypto_sort_uint32.h"
#include "crypto_stream_aes256ctr.h"
#include "params.h"
#include "randombytes.h"
#include "sha2.h"
#include <stdlib.h> /* for abort() in case of OpenSSL failures */


#define int8 int8_t
#define int16 int16_t
#define int32 int32_t
#define uint16 uint16_t
#define uint32 uint32_t
#define uint64 uint64_t

/* ----- masks */

/* return -1 if x<0; otherwise return 0 */
static int int16_negative_mask(int16 x) {
    uint16 u = (uint16) x;
    u >>= 15;
    return -(int) u;
    /* alternative with gcc -fwrapv: */
    /* x>>15 compiles to CPU's arithmetic right shift */
}

/* ----- arithmetic mod 3 */

typedef int8 small;
/* F3 is always represented as -1,0,1 */

/* ----- arithmetic mod q */

#define q12 ((q-1)/2)
typedef int16 Fq;

/* works for -7000000 < x < 7000000 if q in 4591, 4621, 5167, 6343, 7177, 7879 */
/* assumes twos complement; use, e.g., gcc -fwrapv */
static Fq Fq_freeze(int32 x) {
    x -= (int32) (q * ((q18 * x) >> 18));
    x -= (int32) (q * ((q27 * x + 67108864) >> 27));
    return (Fq) x;
}

/* works for all uint32 x */
static Fq Fq_bigfreeze(uint32 x) {
    x -= (uint32) (q * ((x * (uint64)q31) >> 31));
    x -= (uint32) (q * ((x * (uint64)q31) >> 31));
    x -= q;
    x += (~(x >> 31) + 1) & (uint32)q;
    return (Fq) x;
}

/* ----- Top and Right */

static int8 Top(Fq C) {
    return (int8) ((tau1 * (int32)(C + tau0) + 16384) >> 15);
}

static Fq Right(int8 T) {
    return Fq_freeze(tau3 * (int32)T - tau2);
}

/* ----- polynomials mod q */

/* h = h*g in the ring Rq */
static void Rq_mult_small(Fq *h, const small *g) {
    crypto_encode_pxint16((unsigned char *) h, h);
    crypto_core_mult((unsigned char *) h, (const unsigned char *) h, (const unsigned char *) g);
    crypto_decode_pxint16(h, (const unsigned char *) h);
}

/* ----- sorting to generate short polynomial */

static void Short_fromlist(small *out, const uint32 *in) {
    uint32 L[ppadsort];
    int i;

    for (i = 0; i < w; ++i) {
        L[i] = in[i] & (uint32) - 2;
    }
    for (i = w; i < p; ++i) {
        L[i] = (in[i] & (uint32) - 3) | 1;
    }
    for (i = p; i < ppadsort; ++i) {
        L[i] = 0xffffffff;
    }
    PQCLEAN_NTRULPR857_CLEAN_crypto_sort_uint32(L, ppadsort);
    for (i = 0; i < p; ++i) {
        out[i] = (small) ((L[i] & 3) - 1);
    }
}

/* ----- underlying hash function */

#define Hash_bytes 32

static void Hash(unsigned char *out, const unsigned char *in, int inlen) {
    unsigned char h[64];
    int i;
    sha512(h, in, (size_t) inlen);
    for (i = 0; i < 32; ++i) {
        out[i] = h[i];
    }
}

/* ----- higher-level randomness */

static void Short_random(small *out) {
    uint32 L[p];

    randombytes((unsigned char *) L, sizeof L);
    crypto_decode_pxint32(L, (unsigned char *) L);
    Short_fromlist(out, L);
}

/* ----- Inputs, Generator */

typedef int8 Inputs[I]; /* passed by reference */

static const unsigned char aes_nonce[16] = {0};

/* G = Generator(pk) */
static void Generator(Fq *G, const unsigned char *pk) {
    uint32 L[p];
    int i;

    if (PQCLEAN_NTRULPR857_CLEAN_crypto_stream_aes256ctr((unsigned char *) L, 4 * p, aes_nonce, pk) != 0) {
        abort();
    }
    crypto_decode_pxint32(L, (unsigned char *) L);
    for (i = 0; i < p; ++i) {
        G[i] = Fq_bigfreeze(L[i]) - q12;
    }
}

/* ----- NTRU LPRime */

#define Seeds_bytes 32
#define Ciphertexts_bytes (Rounded_bytes+Top_bytes)
#define SecretKeys_bytes Small_bytes
#define PublicKeys_bytes (Seeds_bytes+Rounded_bytes)
#define Confirm_bytes 32

/* c,r_enc[1:] = Hide(r,pk,cache); cache is Hash4(pk) */
static void Hide(unsigned char *c, unsigned char *r_enc, const Inputs r, const unsigned char *pk, const unsigned char *cache) {
    small b[p];
    int i;

    Inputs_encode(r_enc + 1, r);
    {
        unsigned char h[Hash_bytes];
        uint32 L[p];
        {
            unsigned char s[1 + Inputs_bytes];
            Inputs_encode(s + 1, r);
            s[0] = 5;
            Hash(h, s, sizeof s);
        }
        if (PQCLEAN_NTRULPR857_CLEAN_crypto_stream_aes256ctr((unsigned char *) L, 4 * p, aes_nonce, h) != 0) {
            abort();
        }
        crypto_decode_pxint32(L, (unsigned char *) L);
        Short_fromlist(b, L);
    }
    {
        Fq bG[p];
        Generator(bG, pk);
        Rq_mult_small(bG, b);
        Round_and_encode(c, bG);
        c += Rounded_bytes;
    }
    {
        Fq bA[p];
        int8 T[I];
        Rounded_decode(bA, pk + Seeds_bytes);
        Rq_mult_small(bA, b);
        for (i = 0; i < I; ++i) {
            T[i] = Top(Fq_freeze(bA[i] + r[i] * q12));
        }
        Top_encode(c, T);
        c += Top_bytes;
    }
    {
        unsigned char x[1 + Inputs_bytes + Hash_bytes];
        for (i = 0; i < Inputs_bytes; ++i) {
            x[1 + i] = r_enc[1 + i];
        }
        for (i = 0; i < Hash_bytes; ++i) {
            x[1 + Inputs_bytes + i] = cache[i];
        }
        x[0] = 2;
        Hash(c, x, sizeof x);
    }
}


int PQCLEAN_NTRULPR857_CLEAN_crypto_kem_keypair(uint8_t *pk, uint8_t *sk) {
    Fq aG[p];
    int i;
    randombytes(pk, Seeds_bytes);
    Generator(aG, pk);
    {
        small a[p];
        Short_random(a);
        Rq_mult_small(aG, a);
        Small_encode(sk, a);
    }
    Round_and_encode(pk + Seeds_bytes, aG);
    {
        unsigned char sksave = sk[SecretKeys_bytes - 1];
        for (i = 0; i < PublicKeys_bytes; ++i) {
            sk[SecretKeys_bytes + i] = pk[i];
        }
        sk[SecretKeys_bytes - 1] = 4;
        Hash(sk + SecretKeys_bytes + PublicKeys_bytes + Inputs_bytes, sk + SecretKeys_bytes - 1, 1 + PublicKeys_bytes);
        sk[SecretKeys_bytes - 1] = sksave;
        randombytes(sk + SecretKeys_bytes + PublicKeys_bytes, Inputs_bytes);
    }
    return 0;
}

int PQCLEAN_NTRULPR857_CLEAN_crypto_kem_enc(uint8_t *c, uint8_t *k, const uint8_t *pk) {
    int i;
    unsigned char cache[Hash_bytes];
    {
        unsigned char y[1 + PublicKeys_bytes];
        for (i = 0; i < PublicKeys_bytes; ++i) {
            y[1 + i] = pk[i];
        }
        y[0] = 4;
        Hash(cache, y, sizeof y);
    }
    Inputs r;
    {
        unsigned char s[Inputs_bytes];
        randombytes(s, sizeof s);
        Inputs_decode(r, s);
    }
    {
        unsigned char x[1 + Inputs_bytes + Ciphertexts_bytes + Confirm_bytes];
        Hide(c, x, r, pk, cache);
        for (i = 0; i < Ciphertexts_bytes + Confirm_bytes; ++i) {
            x[1 + Inputs_bytes + i] = c[i];
        }
        x[0] = 1;
        Hash(k, x, sizeof x);
    }
    return 0;
}

int PQCLEAN_NTRULPR857_CLEAN_crypto_kem_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk) {
    const unsigned char *pk = sk + SecretKeys_bytes;
    const unsigned char *rho = pk + PublicKeys_bytes;
    const unsigned char *cache = rho + Inputs_bytes;
    Inputs r;
    int i;
    {
        Fq aB[p];
        Rounded_decode(aB, c);
        {
            small a[p];
            Small_decode(a, sk);
            Rq_mult_small(aB, a);
        }
        {
            int8 T[I];
            Top_decode(T, c + Rounded_bytes);
            for (i = 0; i < I; ++i) {
                r[i] = (int8) - int16_negative_mask(Fq_freeze(Right(T[i]) - aB[i] + 4 * w + 1));
            }
        }
    }
    {
        unsigned char cnew[Ciphertexts_bytes + Confirm_bytes];
        int mask;
        unsigned char x[1 + Inputs_bytes + Ciphertexts_bytes + Confirm_bytes];
        Hide(cnew, x, r, pk, cache);
        mask = crypto_verify_clen(c, cnew);
        for (i = 0; i < Inputs_bytes; ++i) {
            x[1 + i] ^= (unsigned char) (mask & (x[1 + i] ^ rho[i]));
        }
        for (i = 0; i < Ciphertexts_bytes + Confirm_bytes; ++i) {
            x[1 + Inputs_bytes + i] = c[i];
        }
        x[0] = (unsigned char) (1 + mask);
        Hash(k, x, sizeof x);
    }
    return 0;
}
