/*---------------------------------------------------------------------------*/
/* ecc.cpp                                                                   */
/* Copyright (c) innovaphone 2015                                            */
/*                                                                           */
/* Elliptic curve cryptography                                               */
/*---------------------------------------------------------------------------*/

#include <platform.h>
#include "uECC.h"
#include "common/ilib/ecc.h"

USING_NAMESPACE

static int rng(byte * out, unsigned len) { 
    byte rand[4];
    byte * o = (byte *)out;

    for (dword i = 0; i < len; i++) {
        int pos = i % 4;
        if (!pos) {
            do {
                dword r = platform_random();
                WRITE_DWORD_NET(rand, r);
            } while (!rand[0] || !rand[1] || !rand[2] || !rand[3]);
        }
        o[i] = rand[pos];
    }
    return 1; 
}
static bool rng_initialized = false;
static void rng_init() { if (!rng_initialized) { uECC_set_rng(rng); rng_initialized = true; } }

static uECC_Curve get_curve(elliptic_curve_t curve)
{
    if (curve == EC_SECP256R1) return uECC_secp256r1();
    return 0;
}

bool ecc::elliptic_curve_supported(elliptic_curve_t curve)
{
    return curve == EC_SECP256R1;
}

size_t ecc::elliptic_curve_size(elliptic_curve_t curve)
{
    if (curve == EC_SECP256R1) return 32;
    return 0;
}

size_t ecc::signature_size(elliptic_curve_t curve)
{
    return elliptic_curve_size(curve) * 2;
}

size_t ecc::pkix_signature_size(elliptic_curve_t curve)
{
    return elliptic_curve_size(curve) * 2 + 6;
}

bool ecc::make_key(byte * public_key, byte * private_key, elliptic_curve_t curve)
{
    rng_init();
    uECC_Curve c = get_curve(curve);
    if (!c) return false;
    return uECC_make_key(public_key, private_key, c) == 1;
}

bool ecc::shared_secret(byte * shared_secret, const byte * public_key, const byte * private_key, elliptic_curve_t curve)
{
    uECC_Curve c = get_curve(curve);
    if (!c) return false;
    return uECC_shared_secret(public_key, private_key, shared_secret, c) == 1;
}

bool ecc::sign(byte * signature, const byte * hash, size_t hash_len, const byte * private_key, elliptic_curve_t curve)
{
    rng_init();
    uECC_Curve c = get_curve(curve);
    if (!c) return false;
    return uECC_sign(private_key, hash, (unsigned)hash_len, signature, c) == 1;
}

bool ecc::verify(const byte * signature, const byte * hash, size_t hash_len, const byte * public_key, elliptic_curve_t curve)
{
    uECC_Curve c = get_curve(curve);
    if (!c) return false;
    return uECC_verify(public_key, hash, (unsigned)hash_len, signature, c) == 1;
}

bool ecc::pkix_sign(byte * signature, const byte * hash, size_t hash_len, const byte * private_key, elliptic_curve_t curve)
{
    size_t size = elliptic_curve_size(curve);
    byte * sig = (byte *)alloca(size * 2);
    if (sign(sig, hash, hash_len, private_key, curve)) {
        signature[0] = 0x30;
        signature[1] = (byte)(size * 2 + 4);
        signature[2] = 0x02;
        signature[3] = (byte)size;
        signature[4 + size] = 0x02;
        signature[5 + size] = (byte)size;
        memcpy(signature + 4, sig, size);
        memcpy(signature + 6 + size, sig + size, size);
        return true;
    }
    return false;
}

bool ecc::pkix_verify(const byte * signature, size_t signature_len, const byte * hash, size_t hash_len, const byte * public_key, elliptic_curve_t curve)
{
    if (signature_len >= pkix_signature_size(curve) &&
        signature[0] == 0x30 &&
        signature[1] + 2 == (int)signature_len &&
        signature[2] == 0x02)
    {
        size_t size = elliptic_curve_size(curve);
        byte * sig = (byte *)alloca(size * 2);
        size_t len = signature[3];
        size_t pos = 4;
        memset(sig, 0, size * 2);

        if (pos + len > signature_len) return false;
        while (len > size) {
            if (signature[pos]) return false;
            ++pos;
            --len;
        }

        memcpy(sig + size - len, signature + pos, len);
        pos += len;

        if (signature[pos] != 0x02) return false;

        len = signature[pos + 1];
        pos += 2;

        if (pos + len > signature_len) return false;
        while (len > size) {
            if (signature[pos]) return false;
            ++pos;
            --len;
        }

        memcpy(sig + size + size - len, signature + pos, len);
        return verify(sig, hash, hash_len, public_key, curve);
    }
    return false;
}
