
#include <stddef.h>
#include <stdint.h>

#ifndef HAVE_TI_MODE

#include "../scalarmult_curve25519.h"
#include "private/curve25519_ref10.h"
#include "utils.h"
#include "x25519_ref10.h"

/*
Replace (f,g) with (g,f) if b == 1;
replace (f,g) with (f,g) if b == 0.

Preconditions: b in {0,1}.
*/

static void
fe_cswap(fe f, fe g, unsigned int b)
{
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int32_t g0 = g[0];
    int32_t g1 = g[1];
    int32_t g2 = g[2];
    int32_t g3 = g[3];
    int32_t g4 = g[4];
    int32_t g5 = g[5];
    int32_t g6 = g[6];
    int32_t g7 = g[7];
    int32_t g8 = g[8];
    int32_t g9 = g[9];
    int32_t x0 = f0 ^ g0;
    int32_t x1 = f1 ^ g1;
    int32_t x2 = f2 ^ g2;
    int32_t x3 = f3 ^ g3;
    int32_t x4 = f4 ^ g4;
    int32_t x5 = f5 ^ g5;
    int32_t x6 = f6 ^ g6;
    int32_t x7 = f7 ^ g7;
    int32_t x8 = f8 ^ g8;
    int32_t x9 = f9 ^ g9;

    b = (unsigned int)(-(int)b);
    x0 &= b;
    x1 &= b;
    x2 &= b;
    x3 &= b;
    x4 &= b;
    x5 &= b;
    x6 &= b;
    x7 &= b;
    x8 &= b;
    x9 &= b;
    f[0] = f0 ^ x0;
    f[1] = f1 ^ x1;
    f[2] = f2 ^ x2;
    f[3] = f3 ^ x3;
    f[4] = f4 ^ x4;
    f[5] = f5 ^ x5;
    f[6] = f6 ^ x6;
    f[7] = f7 ^ x7;
    f[8] = f8 ^ x8;
    f[9] = f9 ^ x9;
    g[0] = g0 ^ x0;
    g[1] = g1 ^ x1;
    g[2] = g2 ^ x2;
    g[3] = g3 ^ x3;
    g[4] = g4 ^ x4;
    g[5] = g5 ^ x5;
    g[6] = g6 ^ x6;
    g[7] = g7 ^ x7;
    g[8] = g8 ^ x8;
    g[9] = g9 ^ x9;
}

/*
h = f * 121666
Can overlap h with f.

Preconditions:
   |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc.

Postconditions:
   |h| bounded by 1.1*2^25,1.1*2^24,1.1*2^25,1.1*2^24,etc.
*/

static void
fe_mul121666(fe h, const fe f)
{
    int32_t f0 = f[0];
    int32_t f1 = f[1];
    int32_t f2 = f[2];
    int32_t f3 = f[3];
    int32_t f4 = f[4];
    int32_t f5 = f[5];
    int32_t f6 = f[6];
    int32_t f7 = f[7];
    int32_t f8 = f[8];
    int32_t f9 = f[9];
    int64_t h0 = f0 * (int64_t)121666;
    int64_t h1 = f1 * (int64_t)121666;
    int64_t h2 = f2 * (int64_t)121666;
    int64_t h3 = f3 * (int64_t)121666;
    int64_t h4 = f4 * (int64_t)121666;
    int64_t h5 = f5 * (int64_t)121666;
    int64_t h6 = f6 * (int64_t)121666;
    int64_t h7 = f7 * (int64_t)121666;
    int64_t h8 = f8 * (int64_t)121666;
    int64_t h9 = f9 * (int64_t)121666;
    int64_t carry0;
    int64_t carry1;
    int64_t carry2;
    int64_t carry3;
    int64_t carry4;
    int64_t carry5;
    int64_t carry6;
    int64_t carry7;
    int64_t carry8;
    int64_t carry9;

    carry9 = (h9 + ((int64_t)1 << 24)) >> 25;
    h0 += carry9 * 19;
    h9 -= carry9 * ((int64_t)1 << 25);
    carry1 = (h1 + ((int64_t)1 << 24)) >> 25;
    h2 += carry1;
    h1 -= carry1 * ((int64_t)1 << 25);
    carry3 = (h3 + ((int64_t)1 << 24)) >> 25;
    h4 += carry3;
    h3 -= carry3 * ((int64_t)1 << 25);
    carry5 = (h5 + ((int64_t)1 << 24)) >> 25;
    h6 += carry5;
    h5 -= carry5 * ((int64_t)1 << 25);
    carry7 = (h7 + ((int64_t)1 << 24)) >> 25;
    h8 += carry7;
    h7 -= carry7 * ((int64_t)1 << 25);

    carry0 = (h0 + ((int64_t)1 << 25)) >> 26;
    h1 += carry0;
    h0 -= carry0 * ((int64_t)1 << 26);
    carry2 = (h2 + ((int64_t)1 << 25)) >> 26;
    h3 += carry2;
    h2 -= carry2 * ((int64_t)1 << 26);
    carry4 = (h4 + ((int64_t)1 << 25)) >> 26;
    h5 += carry4;
    h4 -= carry4 * ((int64_t)1 << 26);
    carry6 = (h6 + ((int64_t)1 << 25)) >> 26;
    h7 += carry6;
    h6 -= carry6 * ((int64_t)1 << 26);
    carry8 = (h8 + ((int64_t)1 << 25)) >> 26;
    h9 += carry8;
    h8 -= carry8 * ((int64_t)1 << 26);

    h[0] = (int32_t) h0;
    h[1] = (int32_t) h1;
    h[2] = (int32_t) h2;
    h[3] = (int32_t) h3;
    h[4] = (int32_t) h4;
    h[5] = (int32_t) h5;
    h[6] = (int32_t) h6;
    h[7] = (int32_t) h7;
    h[8] = (int32_t) h8;
    h[9] = (int32_t) h9;
}

static int
crypto_scalarmult_curve25519_ref10(unsigned char *q,
                                   const unsigned char *n,
                                   const unsigned char *p)
{
    unsigned char e[32];
    unsigned int  i;
    fe            x1;
    fe            x2;
    fe            z2;
    fe            x3;
    fe            z3;
    fe            tmp0;
    fe            tmp1;
    int           pos;
    unsigned int  swap;
    unsigned int  b;

    for (i = 0; i < 32; ++i) {
        e[i] = n[i];
    }
    e[0] &= 248;
    e[31] &= 127;
    e[31] |= 64;
    fe_frombytes(x1, p);
    fe_1(x2);
    fe_0(z2);
    fe_copy(x3, x1);
    fe_1(z3);

    swap = 0;
    for (pos = 254; pos >= 0; --pos) {
        b = e[pos / 8] >> (pos & 7);
        b &= 1;
        swap ^= b;
        fe_cswap(x2, x3, swap);
        fe_cswap(z2, z3, swap);
        swap = b;
        fe_sub(tmp0, x3, z3);
        fe_sub(tmp1, x2, z2);
        fe_add(x2, x2, z2);
        fe_add(z2, x3, z3);
        fe_mul(z3, tmp0, x2);
        fe_mul(z2, z2, tmp1);
        fe_sq(tmp0, tmp1);
        fe_sq(tmp1, x2);
        fe_add(x3, z3, z2);
        fe_sub(z2, z3, z2);
        fe_mul(x2, tmp1, tmp0);
        fe_sub(tmp1, tmp1, tmp0);
        fe_sq(z2, z2);
        fe_mul121666(z3, tmp1);
        fe_sq(x3, x3);
        fe_add(tmp0, tmp0, z3);
        fe_mul(z3, x1, z2);
        fe_mul(z2, tmp1, tmp0);
    }
    fe_cswap(x2, x3, swap);
    fe_cswap(z2, z3, swap);

    fe_invert(z2, z2);
    fe_mul(x2, x2, z2);
    fe_tobytes(q, x2);

    return 0;
}

static void
edwards_to_montgomery(fe montgomeryX, const fe edwardsY, const fe edwardsZ)
{
    fe tempX;
    fe tempZ;

    fe_add(tempX, edwardsZ, edwardsY);
    fe_sub(tempZ, edwardsZ, edwardsY);
    fe_invert(tempZ, tempZ);
    fe_mul(montgomeryX, tempX, tempZ);
}

static int
crypto_scalarmult_curve25519_ref10_base(unsigned char *q,
                                        const unsigned char *n)
{
    unsigned char e[32];
    ge_p3         A;
    fe            pk;
    unsigned int  i;

    for (i = 0; i < 32; ++i) {
        e[i] = n[i];
    }
    e[0] &= 248;
    e[31] &= 127;
    e[31] |= 64;
    ge_scalarmult_base(&A, e);
    edwards_to_montgomery(pk, A.Y, A.Z);
    fe_tobytes(q, pk);

    return 0;
}

struct crypto_scalarmult_curve25519_implementation
    crypto_scalarmult_curve25519_ref10_implementation = {
        SODIUM_C99(.mult =) crypto_scalarmult_curve25519_ref10,
        SODIUM_C99(.mult_base =) crypto_scalarmult_curve25519_ref10_base
    };

#endif
