/**********************************************************************
 * Copyright (c) 2014-2015 Pieter Wuille                              *
 * Distributed under the MIT software license, see the accompanying   *
 * file COPYING or http://www.opensource.org/licenses/mit-license.php.*
 **********************************************************************/

#ifndef SECP256K1_MODULE_SCHNORR_TESTS
#define SECP256K1_MODULE_SCHNORR_TESTS

void test_schnorr_end_to_end(void) {
    unsigned char privkey[32];
    unsigned char message[32];
    unsigned char schnorr_signature[64];
    secp256k1_pubkey_t pubkey, recpubkey;

    /* Generate a random key and message. */
    {
        secp256k1_scalar_t key;
        random_scalar_order_test(&key);
        secp256k1_scalar_get_b32(privkey, &key);
        secp256k1_rand256_test(message);
    }

    /* Construct and verify corresponding public key. */
    CHECK(secp256k1_ec_seckey_verify(ctx, privkey) == 1);
    CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, privkey) == 1);

    /* Schnorr sign. */
    CHECK(secp256k1_schnorr_sign(ctx, message, schnorr_signature, privkey, NULL, NULL) == 1);
    CHECK(secp256k1_schnorr_verify(ctx, message, schnorr_signature, &pubkey) == 1);
    CHECK(secp256k1_schnorr_recover(ctx, message, schnorr_signature, &recpubkey) == 1);
    CHECK(memcmp(&pubkey, &recpubkey, sizeof(pubkey)) == 0);
    /* Destroy signature and verify again. */
    schnorr_signature[secp256k1_rand32() % 64] += 1 + (secp256k1_rand32() % 255);
    CHECK(secp256k1_schnorr_verify(ctx, message, schnorr_signature, &pubkey) == 0);
    CHECK(secp256k1_schnorr_recover(ctx, message, schnorr_signature, &recpubkey) != 1 ||
          memcmp(&pubkey, &recpubkey, sizeof(pubkey)) != 0);
}

/** Horribly broken hash function. Do not use for anything but tests. */
void test_schnorr_hash(unsigned char *h32, const unsigned char *r32, const unsigned char *msg32) {
    int i;
    for (i = 0; i < 32; i++) {
        h32[i] = r32[i] ^ msg32[i];
    }
}

void test_schnorr_sign_verify(void) {
    unsigned char msg32[32];
    unsigned char sig64[3][64];
    secp256k1_gej_t pubkeyj[3];
    secp256k1_ge_t pubkey[3];
    secp256k1_scalar_t nonce[3], key[3];
    int i = 0;
    int k;

    secp256k1_rand256_test(msg32);

    for (k = 0; k < 3; k++) {
        random_scalar_order_test(&key[k]);

        do {
            random_scalar_order_test(&nonce[k]);
            if (secp256k1_schnorr_sig_sign(&ctx->ecmult_gen_ctx, sig64[k], &key[k], &nonce[k], NULL, &test_schnorr_hash, msg32)) {
                break;
            }
        } while(1);

        secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pubkeyj[k], &key[k]);
        secp256k1_ge_set_gej_var(&pubkey[k], &pubkeyj[k]);
        CHECK(secp256k1_schnorr_sig_verify(&ctx->ecmult_ctx, sig64[k], &pubkey[k], &test_schnorr_hash, msg32));

        for (i = 0; i < 4; i++) {
            int pos = secp256k1_rand32() % 64;
            int mod = 1 + (secp256k1_rand32() % 255);
            sig64[k][pos] ^= mod;
            CHECK(secp256k1_schnorr_sig_verify(&ctx->ecmult_ctx, sig64[k], &pubkey[k], &test_schnorr_hash, msg32) == 0);
            sig64[k][pos] ^= mod;
        }
    }
}

void test_schnorr_threshold(void) {
    unsigned char msg[32];
    unsigned char sec[5][32];
    secp256k1_pubkey_t pub[5];
    unsigned char nonce[5][32];
    secp256k1_pubkey_t pubnonce[5];
    unsigned char sig[5][64];
    const unsigned char* sigs[5];
    unsigned char allsig[64];
    const secp256k1_pubkey_t* pubs[5];
    secp256k1_pubkey_t allpub;
    int n, i;
    int damage;
    int ret = 0;

    damage = (secp256k1_rand32() % 2) ? (1 + (secp256k1_rand32() % 4)) : 0;
    secp256k1_rand256_test(msg);
    n = 2 + (secp256k1_rand32() % 4);
    for (i = 0; i < n; i++) {
        do {
            secp256k1_rand256_test(sec[i]);
        } while (!secp256k1_ec_seckey_verify(ctx, sec[i]));
        CHECK(secp256k1_ec_pubkey_create(ctx, &pub[i], sec[i]));
        CHECK(secp256k1_schnorr_generate_nonce_pair(ctx, msg, sec[i], NULL, NULL, &pubnonce[i], nonce[i]));
        pubs[i] = &pub[i];
    }
    if (damage == 1) {
        nonce[secp256k1_rand32() % n][secp256k1_rand32() % 32] ^= 1 + (secp256k1_rand32() % 255);
    } else if (damage == 2) {
        sec[secp256k1_rand32() % n][secp256k1_rand32() % 32] ^= 1 + (secp256k1_rand32() % 255);
    }
    for (i = 0; i < n; i++) {
        secp256k1_pubkey_t allpubnonce;
        const secp256k1_pubkey_t *pubnonces[4];
        int j;
        for (j = 0; j < i; j++) {
            pubnonces[j] = &pubnonce[j];
        }
        for (j = i + 1; j < n; j++) {
            pubnonces[j - 1] = &pubnonce[j];
        }
        CHECK(secp256k1_ec_pubkey_combine(ctx, &allpubnonce, n - 1, pubnonces));
        ret |= (secp256k1_schnorr_partial_sign(ctx, msg, sig[i], sec[i], nonce[i], &allpubnonce) != 1) * 1;
        sigs[i] = sig[i];
    }
    if (damage == 3) {
        sig[secp256k1_rand32() % n][secp256k1_rand32() % 64] ^= 1 + (secp256k1_rand32() % 255);
    }
    ret |= (secp256k1_ec_pubkey_combine(ctx, &allpub, n, pubs) != 1) * 2;
    if ((ret & 1) == 0) {
        ret |= (secp256k1_schnorr_partial_combine(ctx, allsig, n, sigs) != 1) * 4;
    }
    if (damage == 4) {
        allsig[secp256k1_rand32() % 32] ^= 1 + (secp256k1_rand32() % 255);
    }
    if ((ret & 7) == 0) {
        ret |= (secp256k1_schnorr_verify(ctx, msg, allsig, &allpub) != 1) * 8;
    }
    CHECK((ret == 0) == (damage == 0));
}

void test_schnorr_recovery(void) {
    unsigned char msg32[32];
    unsigned char sig64[64];
    secp256k1_ge_t Q;

    secp256k1_rand256_test(msg32);
    secp256k1_rand256_test(sig64);
    secp256k1_rand256_test(sig64 + 32);
    if (secp256k1_schnorr_sig_recover(&ctx->ecmult_ctx, sig64, &Q, &test_schnorr_hash, msg32) == 1) {
        CHECK(secp256k1_schnorr_sig_verify(&ctx->ecmult_ctx, sig64, &Q, &test_schnorr_hash, msg32) == 1);
    }
}

void run_schnorr_tests(void) {
    int i;
    for (i = 0; i < 32*count; i++) {
        test_schnorr_end_to_end();
    }
    for (i = 0; i < 32 * count; i++) {
         test_schnorr_sign_verify();
    }
    for (i = 0; i < 16 * count; i++) {
         test_schnorr_recovery();
    }
    for (i = 0; i < 10 * count; i++) {
         test_schnorr_threshold();
    }
}

#endif