#include "crypto_verify_32.h"
#include "cleanup.h"
#include "fe.h"
#include "fe25519.h"

/*
p = 2^255 - 19
*/
static const fe p = {0xffffffed, 0xffffffff, 0xffffffff, 0xffffffff,
                     0xffffffff, 0xffffffff, 0xffffffff, 0x7fffffff};

/*
p3 = 3 * p
*/
static const crypto_uint64 p3[8] = {
    0x2ffffffc7ULL, 0x2fffffffdULL, 0x2fffffffdULL, 0x2fffffffdULL,
    0x2fffffffdULL, 0x2fffffffdULL, 0x2fffffffdULL, 0x17ffffffdULL};

/*
reduction modulo p: 16 limbs -> 8 limbs
*/
static void fe25519_reducebig(fe o, fel t) {

    crypto_uint64 u = 0;
    long long i;

    for (i = 0; i < 7; ++i) {
        u += t[i] + 38ULL * t[i + 8];
        t[i] = u & 0xffffffff;
        u >>= 32;
    }
    u += t[i] + 38ULL * t[i + 8];
    t[i] = u & 0x7fffffff;
    u >>= 31;
    u *= 19ULL;
    for (i = 0; i < 8; ++i) {
        u += t[i];
        o[i] = u & 0xffffffff;
        u >>= 32;
    }
}

/*
o = (a * b) % p
*/
void fe25519_mul(fe o, const fe a, const fe b) {

    fel t;

    fe_mul_(t, a, b);
    fe25519_reducebig(o, t);

    cleanup(t);
}

/*
o = (a ^ 2) % p
*/
void fe25519_sq(fe o, const fe a) {

    fel t;

    fe_sq_(t, a);
    fe25519_reducebig(o, t);

    cleanup(t);
}

/*
o = (121666 * f) % p;
*/
void fe25519_mul121666(fe o, const fe f) {

    crypto_uint64 u = 0;
    long long i;

    for (i = 0; i < 7; ++i) {
        u += (crypto_uint64) 121666 * (crypto_uint64) f[i];
        o[i] = u & 0xffffffff;
        u >>= 32;
    }
    u += (crypto_uint64) 121666 * (crypto_uint64) f[i];
    o[i] = u & 0x7fffffff;
    u >>= 31;
    u *= 19ULL;
    for (i = 0; i < 8; ++i) {
        u += o[i];
        o[i] = u & 0xffffffff;
        u >>= 32;
    }
}

/*
o = (x + y) % p
*/
void fe25519_add(fe o, const fe x, const fe y) {

    crypto_uint64 u = 0;
    long long i;

    for (i = 0; i < 7; ++i) {
        u += (crypto_uint64) x[i] + (crypto_uint64) y[i];
        o[i] = u & 0xffffffff;
        u >>= 32;
    }
    u += (crypto_uint64) x[i] + (crypto_uint64) y[i];
    o[i] = u & 0x7fffffff;
    u >>= 31;
    u *= 19ULL;
    for (i = 0; i < 8; ++i) {
        u += o[i];
        o[i] = u & 0xffffffff;
        u >>= 32;
    }
}

/*
o = (x - y) % p
*/
void fe25519_sub(fe o, const fe x, const fe y) {

    crypto_uint64 u = 0;
    long long i;

    for (i = 0; i < 7; ++i) {
        u += p3[i] - (crypto_uint64) y[i] + (crypto_uint64) x[i];
        o[i] = u & 0xffffffff;
        u >>= 32;
    }
    u += p3[i] - (crypto_uint64) y[i] + (crypto_uint64) x[i];
    o[i] = u & 0x7fffffff;
    u >>= 31;
    u *= 19ULL;
    for (i = 0; i < 8; ++i) {
        u += o[i];
        o[i] = u & 0xffffffff;
        u >>= 32;
    }
}

/*
o = -x % p
*/
void fe25519_neg(fe o, const fe x) {

    fe t;

    fe_0(t);
    fe25519_sub(o, t, x);
}

/*
o = (1 / z) % p
... using Fermat's Little Theorem
*/
void fe25519_inv(fe o, const fe z) {

    fe t0, t1, t2, t3;
    long long i;

    fe25519_sq(t0, z);
    for (i = 1; i < 1; ++i) fe25519_sq(t0, t0);
    fe25519_sq(t1, t0);
    for (i = 1; i < 2; ++i) fe25519_sq(t1, t1);
    fe25519_mul(t1, z, t1);
    fe25519_mul(t0, t0, t1);
    fe25519_sq(t2, t0);
    for (i = 1; i < 1; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t1, t1, t2);
    fe25519_sq(t2, t1);
    for (i = 1; i < 5; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t1, t2, t1);
    fe25519_sq(t2, t1);
    for (i = 1; i < 10; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t2, t2, t1);
    fe25519_sq(t3, t2);
    for (i = 1; i < 20; ++i) fe25519_sq(t3, t3);
    fe25519_mul(t2, t3, t2);
    fe25519_sq(t2, t2);
    for (i = 1; i < 10; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t1, t2, t1);
    fe25519_sq(t2, t1);
    for (i = 1; i < 50; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t2, t2, t1);
    fe25519_sq(t3, t2);
    for (i = 1; i < 100; ++i) fe25519_sq(t3, t3);
    fe25519_mul(t2, t3, t2);
    fe25519_sq(t2, t2);
    for (i = 1; i < 50; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t1, t2, t1);
    fe25519_sq(t1, t1);
    for (i = 1; i < 5; ++i) fe25519_sq(t1, t1);
    fe25519_mul(o, t1, t0);

    cleanup(t0);
    cleanup(t1);
    cleanup(t2);
    cleanup(t3);
}

void fe25519_pow22523(fe out, const fe z) {

    fe t0, t1, t2;
    long long i;

    fe25519_sq(t0, z);
    for (i = 1; i < 1; ++i) fe25519_sq(t0, t0);
    fe25519_sq(t1, t0);
    for (i = 1; i < 2; ++i) fe25519_sq(t1, t1);
    fe25519_mul(t1, z, t1);
    fe25519_mul(t0, t0, t1);
    fe25519_sq(t0, t0);
    for (i = 1; i < 1; ++i) fe25519_sq(t0, t0);
    fe25519_mul(t0, t1, t0);
    fe25519_sq(t1, t0);
    for (i = 1; i < 5; ++i) fe25519_sq(t1, t1);
    fe25519_mul(t0, t1, t0);
    fe25519_sq(t1, t0);
    for (i = 1; i < 10; ++i) fe25519_sq(t1, t1);
    fe25519_mul(t1, t1, t0);
    fe25519_sq(t2, t1);
    for (i = 1; i < 20; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t1, t2, t1);
    fe25519_sq(t1, t1);
    for (i = 1; i < 10; ++i) fe25519_sq(t1, t1);
    fe25519_mul(t0, t1, t0);
    fe25519_sq(t1, t0);
    for (i = 1; i < 50; ++i) fe25519_sq(t1, t1);
    fe25519_mul(t1, t1, t0);
    fe25519_sq(t2, t1);
    for (i = 1; i < 100; ++i) fe25519_sq(t2, t2);
    fe25519_mul(t1, t2, t1);
    fe25519_sq(t1, t1);
    for (i = 1; i < 50; ++i) fe25519_sq(t1, t1);
    fe25519_mul(t0, t1, t0);
    fe25519_sq(t0, t0);
    for (i = 1; i < 2; ++i) fe25519_sq(t0, t0);
    fe25519_mul(out, t0, z);

    cleanup(t0);
    cleanup(t1);
    cleanup(t2);
}

/*
converts field-element into byte-array
*/
void fe25519_tobytes(unsigned char *out, const fe in) {

    long long i;
    fe x;

    fe_copy(x, in);
    fe_reducesmall(x, p, 0);
    for (i = 0; i < 8; ++i) crypto_uint32_store(out + 4 * i, x[i]);
    cleanup(x);
}

/*
converts byte-array into field-element
*/
void fe25519_frombytes(fe out, const unsigned char *in) {

    long long i;

    for (i = 0; i < 8; ++i) out[i] = crypto_uint32_load(in + 4 * i);
    out[7] &= 0x7fffffff;
}

/*
if (f == 0) return 0;
else return -1;
*/
static const unsigned char zero[32] = {0};
int fe25519_isnonzero(const fe f) {
    unsigned char s[32];
    int r;
    fe25519_tobytes(s, f);
    r = crypto_verify_32(s, zero);
    cleanup(s);
    return r;
}

/*
if (f >= 0) return 0;
else return -1;
*/
int fe25519_isnegative(const fe f) {
    unsigned char s[32];
    int r;
    fe25519_tobytes(s, f);
    r = s[0] & 1;
    cleanup(s);
    return r;
}
