/**********************************************************************
 * Copyright (c) 2017 Jonas Nick                                      *
 * Distributed under the MIT software license, see the accompanying   *
 * file COPYING or http://www.opensource.org/licenses/mit-license.php.*
 **********************************************************************/
#include <stdio.h>

#include "../include/secp256k1.h"

#include "../include/secp256k1_whitelist.h"
#include "util.h"
#include "bench.h"
#include "hash_impl.h"
#include "int128_impl.h"
#include "scalar_impl.h"
#include "testrand_impl.h"

#define MAX_N_KEYS 30

typedef struct {
    secp256k1_context* ctx;
    unsigned char online_seckey[MAX_N_KEYS][32];
    unsigned char summed_seckey[MAX_N_KEYS][32];
    secp256k1_pubkey online_pubkeys[MAX_N_KEYS];
    secp256k1_pubkey offline_pubkeys[MAX_N_KEYS];
    unsigned char csub[32];
    secp256k1_pubkey sub_pubkey;
    secp256k1_whitelist_signature sig;
    size_t n_keys;
} bench_data;

static void bench_whitelist(void* arg, int iters) {
    bench_data* data = (bench_data*)arg;
    int i;
    for (i = 0; i < iters; i++) {
        CHECK(secp256k1_whitelist_verify(data->ctx, &data->sig, data->online_pubkeys, data->offline_pubkeys, data->n_keys, &data->sub_pubkey) == 1);
    }
}

static void bench_whitelist_setup(void* arg) {
    bench_data* data = (bench_data*)arg;
    int i = 0;
    CHECK(secp256k1_whitelist_sign(data->ctx, &data->sig, data->online_pubkeys, data->offline_pubkeys, data->n_keys, &data->sub_pubkey, data->online_seckey[i], data->summed_seckey[i], i));
}

static void run_test(bench_data* data, int iters) {
    char str[32];
    sprintf(str, "whitelist_%i", (int)data->n_keys);
    run_benchmark(str, bench_whitelist, bench_whitelist_setup, NULL, data, 100, iters);
}

static void random_scalar_order(secp256k1_scalar *num) {
    do {
        unsigned char b32[32];
        int overflow = 0;
        secp256k1_testrand256(b32);
        secp256k1_scalar_set_b32(num, b32, &overflow);
        if (overflow || secp256k1_scalar_is_zero(num)) {
            continue;
        }
        break;
    } while(1);
}

int main(void) {
    bench_data data;
    size_t i;
    size_t n_keys = 30;
    secp256k1_scalar ssub;
    int iters = get_iters(5);

    data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);

    /* Start with subkey */
    random_scalar_order(&ssub);
    secp256k1_scalar_get_b32(data.csub, &ssub);
    CHECK(secp256k1_ec_seckey_verify(data.ctx, data.csub) == 1);
    CHECK(secp256k1_ec_pubkey_create(data.ctx, &data.sub_pubkey, data.csub) == 1);
    /* Then offline and online whitelist keys */
    for (i = 0; i < n_keys; i++) {
        secp256k1_scalar son, soff;

        /* Create two keys */
        random_scalar_order(&son);
        secp256k1_scalar_get_b32(data.online_seckey[i], &son);
        CHECK(secp256k1_ec_seckey_verify(data.ctx, data.online_seckey[i]) == 1);
        CHECK(secp256k1_ec_pubkey_create(data.ctx, &data.online_pubkeys[i], data.online_seckey[i]) == 1);

        random_scalar_order(&soff);
        secp256k1_scalar_get_b32(data.summed_seckey[i], &soff);
        CHECK(secp256k1_ec_seckey_verify(data.ctx, data.summed_seckey[i]) == 1);
        CHECK(secp256k1_ec_pubkey_create(data.ctx, &data.offline_pubkeys[i], data.summed_seckey[i]) == 1);

        /* Make summed_seckey correspond to the sum of offline_pubkey and sub_pubkey */
        secp256k1_scalar_add(&soff, &soff, &ssub);
        secp256k1_scalar_get_b32(data.summed_seckey[i], &soff);
        CHECK(secp256k1_ec_seckey_verify(data.ctx, data.summed_seckey[i]) == 1);
    }

    /* Run test */
    for (i = 1; i <= n_keys; ++i) {
        data.n_keys = i;
        run_test(&data, iters);
    }

    secp256k1_context_destroy(data.ctx);
    return(0);
}
