#include <assert.h>
#include "common.h"
#include "mont.h"

int ge(const uint64_t *x, const uint64_t *y, size_t nw);
unsigned sub(uint64_t *out, const uint64_t *a, const uint64_t *b, size_t nw);
void rsquare(uint64_t *r2, uint64_t *n, size_t nw);
int mod_select(uint64_t *out, const uint64_t *a, const uint64_t *b, unsigned cond, size_t words);

void test_ge(void)
{
    int res;
    uint64_t x[2] = { 1, 2 };
    uint64_t y[2] = { 2, 1 };

    res = ge(x, y, 2);
    assert(res == 1);
    res = ge(x, x, 2);
    assert(res == 1);
    res = ge(y, x, 2);
    assert(res == 0);
}

void test_sub(void)
{
    uint64_t res;
    uint64_t x[2] = { 1, 2 };
    uint64_t y[2] = { 2, 1 };
    uint64_t out[2];

    memset(out, 0xFF, sizeof out);
    res = sub(out, x, x, 2);
    assert(res == 0);
    assert(out[0] == 0 && out[1] == 0);

    memset(out, 0xFF, sizeof out);
    x[0] = 1; x[1] = 2;
    res = sub(out, x, y, 2);
    assert(res == 0);
    assert(out[0] == 0xFFFFFFFFFFFFFFFFUL);
    assert(out[1] == 0);
    
    memset(out, 0xFF, sizeof out);
    x[0] = 1; x[1] = 2;
    res = sub(out, y, x, 2);
    assert(res == 1);
    assert(out[0] == 1);
    assert(out[1] == 0xFFFFFFFFFFFFFFFFUL);
}

void test_rsquare(void)
{
    uint64_t n1[2] = { 1, 0x89 };
    uint64_t r2[2];

    rsquare(r2, n1, 2);
    assert(r2[0] == 0x44169db8eb2b48d8L);
    assert(r2[1] == 0x18);
}

void test_mont_context_init(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulus[] = { 1, 0, 0, 1 };
    uint8_t modulus_even[] = { 1, 0, 0, 2 };

    res = mont_context_init(NULL, modulus, 4);
    assert(res == ERR_NULL);
    
    res = mont_context_init(&ctx, 0, 4);
    assert(res == ERR_NULL);
    
    res = mont_context_init(&ctx, modulus, 0);
    assert(res == ERR_MODULUS);
    
    res = mont_context_init(&ctx, modulus_even, 4);
    assert(res == ERR_MODULUS);

    res = mont_context_init(&ctx, modulus, 4);
    assert(res == 0);

    mont_context_free(ctx);
}

void test_mont_new_from_bytes(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulus[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };   // 0x01000001000000000000000000000001
    uint8_t number[] = { 2, 2 };    // 0x0202
    uint8_t number2[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3 };   // 0x01000001000000000000000000000001 + 0x0202
    uint64_t *output;

    res = mont_context_init(&ctx, modulus, 16);
    assert(res == 0);

    res = mont_new_from_bytes(NULL, number, 2, ctx);
    assert(res == ERR_NULL);
    
    res = mont_new_from_bytes(&output, NULL, 2, ctx);
    assert(res == ERR_NULL);
    
    res = mont_new_from_bytes(&output, number, 2, NULL);
    assert(res == ERR_NULL);
    
    res = mont_new_from_bytes(&output, number, 0, ctx);
    assert(res == ERR_NOT_ENOUGH_DATA);
    
    res = mont_new_from_bytes(&output, number, 2, ctx);
    assert(res == 0);
    assert(output != NULL);
    assert(output[0] == 18446744073709420033UL);
    assert(output[1] == 71492449356218367L);
    free(output);

    res = mont_new_from_bytes(&output, number, sizeof(number), ctx);
    assert(res == 0);
    assert(output != NULL);
    assert(output[0] == 18446744073709420033UL);
    assert(output[1] == 71492449356218367L);
    free(output);

    /** Same as above, but larger than modulus **/
    res = mont_new_from_bytes(&output, number2, sizeof(number2), ctx);
    assert(res == 0);
    assert(output != NULL);
    assert(output[0] == 18446744073709420033UL);
    assert(output[1] == 71492449356218367L);
    free(output);

    /** 0 is still 0 in Montgomery form **/
    number[0] = 0;
    number[1] = 0;
    res = mont_new_from_bytes(&output, number, 2, ctx);
    assert(res == 0);
    assert(output != NULL);
    assert(output[0] == 0);
    assert(output[1] == 0);
    free(output);
    
    mont_context_free(ctx);
}

void test_mont_to_bytes(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulus[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };   // 0x01000001000000000000000000000001
    uint64_t number_mont[2] = { 18446744073709420033UL, 71492449356218367L };
    uint8_t number[16];

    memset(number, 0xAA, 16);

    res = mont_context_init(&ctx, modulus, 16);
    assert(res == 0);
    assert(mont_bytes(ctx) == 16);

    res = mont_to_bytes(NULL, 16, number_mont, ctx);
    assert(res == ERR_NULL);
    res = mont_to_bytes(number, 16, NULL, ctx);
    assert(res == ERR_NULL);
    res = mont_to_bytes(number, 16, number_mont, NULL);
    assert(res == ERR_NULL);

    res = mont_to_bytes(number, 15, number_mont, ctx);
    assert(res == ERR_NOT_ENOUGH_DATA);

    res = mont_to_bytes(number, 16, number_mont, ctx);
    assert(res == 0);
    assert(0 == memcmp(number, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02", 16));
    
    mont_context_free(ctx);
}

void test_mont_add(void)
{
    int res;
    MontContext *ctx;
    uint64_t *tmp;
    uint8_t modulus[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };   // 0x01000001000000000000000000000001
    uint8_t modulus2[16];
    uint64_t a[2] = { UINT64_MAX, UINT64_MAX };
    uint64_t b[2] = { 1, 0 };
    uint64_t out[2];

    mont_context_init(&ctx, modulus, 16);
    mont_new_number(&tmp, 2, ctx);
    
    res = mont_add(NULL, a, b, tmp, ctx);
    assert(res == ERR_NULL);
    res = mont_add(out, NULL, b, tmp, ctx);
    assert(res == ERR_NULL);
    res = mont_add(out, a, NULL, tmp, ctx);
    assert(res == ERR_NULL);
    res = mont_add(out, a, b, NULL, ctx);
    assert(res == ERR_NULL);
    res = mont_add(out, a, b, tmp, NULL);
    assert(res == ERR_NULL);
    
    // 0x100000200000100000000000000000L + 0x100000200000100000000000000000L
    a[0] = 0x10;
    a[1] = 0;
    b[0] = 0x100;
    b[1] = 0;
    res = mont_add(out, a, b, tmp, ctx);
    assert(res == 0);
    assert(out[0] == 0x110);
    assert(out[1] == 0);
 
    // 0xff0000fdffffff0000000000000001 + 0x100
    a[0] = 0x0;
    a[1] = 0x100000100000000UL;
    b[0] = 0xffffffffffff0001L;
    b[1] = 0xff0000ffffffffL;
    res = mont_add(out, a, b, tmp, ctx);
    assert(res == 0);
    assert(out[0] == 0xffffffffffff0000L);
    assert(out[1] == 0xff0000ffffffffL);

    // 0xff0000fdffffff0000000000000001L * 2
    a[0] = 0;
    a[1] = 0x100000100000000L;
    b[0] = 0;
    b[1] = 0x100000100000000L;
    res = mont_add(out, a, b, tmp, ctx);
    assert(res == 0);
    assert(out[0] == 0xffffffffffffffffL);
    assert(out[1] == 0x1000000ffffffffL);

    // Use modulus2, to trigger overflow over R
    mont_context_free(ctx);
    memset(modulus2, 0xFF, 16);
    mont_context_init(&ctx, modulus2, 16);

    // 0xfffffffffffffffffffffffffffffffe * 2
    // (same encoding in Montgomery domain)
    a[0] = 0xfffffffffffffffeL;
    a[1] = 0xffffffffffffffffL;
    b[0] = a[0];
    b[1] = a[1];
    res = mont_add(out, a, b, tmp, ctx);
    assert(res == 0);
    assert(out[0] == 0xfffffffffffffffdL);
    assert(out[1] == 0xffffffffffffffffL);

    free(tmp);
    mont_context_free(ctx);
}

void test_mont_sub(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulus[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };   // 0x01000001000000000000000000000001
    uint64_t a[2] = { 0, 0 };
    uint64_t b[2] = { 1, 0 };
    uint64_t out[3];
    uint64_t *tmp;

    mont_context_init(&ctx, modulus, 16);
    mont_new_number(&tmp, 2, ctx);
    
    res = mont_sub(NULL, a, b, tmp, ctx);
    assert(res == ERR_NULL);
    res = mont_sub(out, NULL, b, tmp, ctx);
    assert(res == ERR_NULL);
    res = mont_sub(out, a, NULL, tmp, ctx);
    assert(res == ERR_NULL);
    res = mont_sub(out, a, b, NULL, ctx);
    assert(res == ERR_NULL);
    res = mont_sub(out, a, b, tmp, NULL);
    assert(res == ERR_NULL);

    out[2] = 0xA;
    res = mont_sub(out, a, b, tmp, ctx);
    assert(res == 0);
    assert(out[0] == 0);
    assert(out[1] == 0x100000100000000);
    assert(out[2] == 0xA);

    res = mont_sub(out, b, a, tmp, ctx);
    assert(res == 0);
    assert(out[0] == 1);
    assert(out[1] == 0);

    free(tmp);
    mont_context_free(ctx);
}

void test_mont_inv_prime(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulus_f6[9] = { 1, 0, 0, 0, 0, 0, 0, 0, 1 }; // F6 = 2^64 + 1
    uint64_t a[2] = { 1, 0 };
    uint64_t out[2];
    uint64_t *p;
    uint8_t buf[16];

    const uint8_t modulus_p521[66] = {0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                      0xFF, 0xFF};
    uint64_t out_p521[9];
    uint8_t buf_p521[66];

    res = mont_context_init(&ctx, modulus_f6, sizeof modulus_f6);
    assert(res == 0);

    res = mont_inv_prime(NULL, a, ctx);
    assert(res == ERR_NULL);
    res = mont_inv_prime(out, NULL, ctx);
    assert(res == ERR_NULL);
    res = mont_inv_prime(out, a, NULL);
    assert(res == ERR_NULL);

    /* 1 == R mod N when N = F6 */
    a[0] = 1;   a[1] = 0;
    out[0] = 1; out[1] = 0;
    res = mont_inv_prime(out, a, ctx);
    assert(res == 0);
    assert(out[0] == 1);
    assert(out[1] == 0);

    assert(sizeof buf == mont_bytes(ctx));

    /* 2^{-1} mod N = 0x8000000000000001 */
    res = mont_new_from_bytes(&p, (uint8_t*)"\x00\x02", 2, ctx);
    assert(res == 0);
    res = mont_inv_prime(out, p, ctx);
    assert(res == 0);
    res = mont_to_bytes(buf, 16, out, ctx);
    assert(res == 0);
    assert(0 == memcmp(buf, (uint8_t*)"\x00\x00\x00\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x01", 16));
    free(p);

    /* 3^{-1} mod N = 0x287cbedc41008ca6 */
    res = mont_new_from_bytes(&p, (uint8_t*)"\x00\x03", 2, ctx);
    assert(res == 0);
    res = mont_inv_prime(out, p, ctx);
    assert(res == 0);
    res = mont_to_bytes(buf, 16, out, ctx);
    assert(res == 0);
    assert(0 == memcmp(buf, (uint8_t*)"\x00\x00\x00\x00\x00\x00\x00\x00\x28\x7c\xbe\xdc\x41\x00\x8c\xa6", 16));
    free(p);

    mont_context_free(ctx);

    /* --- */
    mont_context_init(&ctx, modulus_p521, sizeof modulus_p521);
    res = mont_new_from_bytes(&p, (uint8_t*)"\x01\xE9\xF3\x4F\x60\xAD\x5C\x4B\x98\x8A\xB4\x3A\x0C\x1C\x40\xFB\x5C\xB0\xFD\x1A\x1A\x6F\x4E\x81\xEB\x33\xDE\x7D\x95\x2E\xE2\x62\x0D\x76\x08\x3B\xA2\x28\xCC\x56\xA4\xFE\xD2\xF6\x08\xF3\x17\x1E\x59\x41\xB7\xE1\x6D\x20\x05\xEB\x9F\x55\x6B\x6B\xA1\x36\x0E\xC2\x35\x8C", 66, ctx);
    assert(res == 0);

    res = mont_inv_prime(out_p521, p, ctx);
    assert(res == 0);

    res = mont_to_bytes(buf_p521, 66, out_p521, ctx);
    assert(res == 0);
    assert(0 == memcmp(buf_p521, (uint8_t*)"\x01\xF5\xDD\xE7\xED\xB2\xAD\x9D\x06\x2F\x2C\xAE\x1B\x66\x95\xC0\x9B\xE6\x16\xDA\xEA\x07\x2A\xC8\x2A\xFB\x44\xF4\x21\x79\xE1\x38\x8B\x1C\xEF\x91\xBA\xD3\xEB\x1D\x81\xE5\x45\xEF\x54\x63\xD7\x0A\xED\x39\x70\xFC\xD5\x95\xFF\x1B\xA7\x52\x11\xD3\xC3\x3C\x2C\x14\x42\x51", 66));

    free(p);
    mont_context_free(ctx);
}


void test_mont_set(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulus[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };   // 0x01000001000000000000000000000001
    uint64_t out[2];

    mont_context_init(&ctx, modulus, 16);
    
    res = mont_set(NULL, 0x1000, ctx);
    assert(res == ERR_NULL);
    res = mont_set(out, 0x1000, NULL);
    assert(res == ERR_NULL);

    res = mont_set(out, 0, ctx);
    assert(res == 0);
    assert(out[0] == 0);
    assert(out[1] == 0);
    
    res = mont_set(out, 0x1000, ctx);
    assert(res == 0);
    assert(out[0] == 0xfffffffffff00001UL);
    assert(out[1] == 0xf00000ffffffffUL);

    mont_context_free(ctx);
}

void test_mont_new_from_uint64(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulus[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };   // 0x01000001000000000000000000000001
    uint64_t *out;

    mont_context_init(&ctx, modulus, 16);

    res = mont_new_from_uint64(NULL, 0x1000, ctx);
    assert(res == ERR_NULL);
    res = mont_new_from_uint64(&out, 0x1000, NULL);
    assert(res == ERR_NULL);

    res = mont_new_from_uint64(&out, 0, ctx);
    assert(res == 0);
    assert(out[0] == 0);
    assert(out[1] == 0);
    free(out);

    res = mont_new_from_uint64(&out, 0x1000, ctx);
    assert(res == 0);
    assert(out[0] == 0xfffffffffff00001UL);
    assert(out[1] == 0xf00000ffffffffUL);
    free(out);

    mont_context_free(ctx);
}

void test_mod_select(void)
{
    int res;
    MontContext *ctx;
    uint8_t modulusA[16] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };      // 0x01000001000000000000000000000001
    uint8_t modulusB[17] = { 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3 };   // 0x0301000001000000000000000000000001
    uint64_t a[2] = { 0xFFFFFFFFFFFFFFFFU, 0xFFFFFFFFFFFFFFFFU };
    uint64_t b[2] = { 1, 1 };
    uint64_t c[2];
    uint64_t d[3] = { 0xFFFFFFFFFFFFFFFFU, 0xFFFFFFFFFFFFFFFFU, 3 };
    uint64_t e[3] = { 1, 1, 3 };
    uint64_t f[3];

    mont_context_init(&ctx, modulusA, 16);

    memset(c, 0, sizeof c);
    res = mod_select(c, a, b, 1, ctx->words);
    assert(res == 0);
    assert(memcmp(a, c, sizeof c) == 0);

    memset(c, 0, sizeof c);
    res = mod_select(c, a, b, 10, ctx->words);
    assert(res == 0);
    assert(memcmp(a, c, sizeof c) == 0);

    memset(c, 0, sizeof c);
    res = mod_select(c, a, b, 0, ctx->words);
    assert(res == 0);
    assert(memcmp(b, c, sizeof c) == 0);

    mont_context_free(ctx);

    /* --- */

    mont_context_init(&ctx, modulusB, 17);

    memset(f, 0, sizeof f);
    res = mod_select(f, d, e, 1, ctx->words);
    assert(res == 0);
    assert(memcmp(d, f, sizeof f) == 0);

    memset(f, 0, sizeof f);
    res = mod_select(f, d, e, 0, ctx->words);
    assert(res == 0);
    assert(memcmp(e, f, sizeof f) == 0);

    mont_context_free(ctx);
}

int main(void) {
    test_ge();
    test_sub();
    test_rsquare();
    test_mont_context_init();
    test_mont_new_from_bytes();
    test_mont_to_bytes();
    test_mont_add();
    test_mont_sub();
    test_mont_inv_prime();
    test_mont_set();
    test_mont_new_from_uint64();
    test_mod_select();
    return 0;
}
