#ifndef SECP256K1_MODULE_SCHNORRSIG_HALFAGG_TESTS_H
#define SECP256K1_MODULE_SCHNORRSIG_HALFAGG_TESTS_H

#include "../../../include/secp256k1_schnorrsig_halfagg.h"
#include "../../unit_test.h"

#define N_MAX 50

/* We test that the hash initialized by secp256k1_schnorrsig_sha256_tagged_aggregate
 * has the expected state. */
void test_schnorrsig_sha256_tagged_aggregate(void) {
    static const unsigned char tag[] = {'H', 'a', 'l', 'f', 'A', 'g', 'g', '/', 'r', 'a', 'n', 'd', 'o', 'm', 'i', 'z', 'e', 'r'};
    secp256k1_sha256 sha_optimized;
    const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(CTX);

    secp256k1_schnorrsig_sha256_tagged_aggregation(&sha_optimized);
    test_sha256_tag_midstate(hash_ctx, &sha_optimized, tag, sizeof(tag));
}

/* Create n many x-only pubkeys and sigs for random messages */
void test_schnorrsig_aggregate_input_helper(secp256k1_xonly_pubkey *pubkeys, unsigned char *msgs32, unsigned char *sigs64, size_t n) {
    size_t i;
    for (i = 0; i < n; ++i) {
        unsigned char sk[32];
        secp256k1_keypair keypair;
        testrand256(sk);
        testrand256(&msgs32[i*32]);

        CHECK(secp256k1_keypair_create(CTX, &keypair, sk));
        CHECK(secp256k1_keypair_xonly_pub(CTX, &pubkeys[i], NULL, &keypair));
        CHECK(secp256k1_schnorrsig_sign(CTX, &sigs64[i*64], &msgs32[i*32], &keypair, NULL));
    }
}

/* In this test we create a bunch of Schnorr signatures,
 * aggregate some of them in one shot, and then
 * aggregate the others incrementally to the already aggregated ones.
 * The aggregate signature should verify after both steps. */
void test_schnorrsig_aggregate_internal(void) {
    secp256k1_xonly_pubkey pubkeys[N_MAX];
    unsigned char msgs32[N_MAX*32];
    unsigned char sigs64[N_MAX*64];
    unsigned char aggsig[32*(N_MAX + 1) + 17];
    size_t aggsig_len = sizeof(aggsig);

    size_t n = testrand_int(N_MAX + 1);
    size_t n_initial = testrand_int(n + 1);
    size_t n_new = n - n_initial;
    test_schnorrsig_aggregate_input_helper(pubkeys, msgs32, sigs64, n);

    /* Aggregate the first n_initial of them */
    CHECK(secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, sigs64, n_initial));
    /* Make sure that the aggregate signature verifies */
    CHECK(aggsig_len == 32*(n_initial + 1));
    CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n_initial, aggsig, aggsig_len));
    /* Aggregate the remaining n_new many signatures to the already existing ones */
    aggsig_len = sizeof(aggsig);
    secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, &sigs64[n_initial*64], n_initial, n_new);
    /* Make sure that the aggregate signature verifies */
    CHECK(aggsig_len == 32*(n + 1));
    CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len));

    /* Check that a direct aggregation of the n sigs yields an identical aggsig */
    {
        unsigned char aggsig2[sizeof(aggsig)];
        size_t aggsig_len2 = sizeof(aggsig2);
        CHECK(secp256k1_schnorrsig_aggregate(CTX, aggsig2, &aggsig_len2, pubkeys, msgs32, sigs64, n));
        CHECK(aggsig_len == aggsig_len2);
        CHECK(secp256k1_memcmp_var(aggsig, aggsig2, aggsig_len) == 0);
    }
}

/* This tests the verification test vectors from
 * https://github.com/BlockstreamResearch/cross-input-aggregation/blob/master/hacspec-halfagg/tests/tests.rs#L78 . */
void test_schnorrsig_aggverify_spec_vectors(void) {
    /* Test vector 0 */
    {
        size_t n = 0;
        const unsigned char aggsig[32] = {
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        };
        size_t aggsig_len = sizeof(aggsig);
        CHECK(secp256k1_schnorrsig_aggverify(CTX, NULL, NULL, n, aggsig, aggsig_len));
    }
    /* Test vector 1 */
    {
        size_t n = 1;
        const unsigned char pubkeys_ser[1*32] = {
            0x1b, 0x84, 0xc5, 0x56, 0x7b, 0x12, 0x64, 0x40,
            0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65,
            0xd7, 0x1e, 0x18, 0x34, 0x60, 0x48, 0x19, 0xff,
            0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f
        };
        secp256k1_xonly_pubkey pubkeys[1];
        const unsigned char msgs32[1*32] = {
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02
        };
        const unsigned char aggsig[1*32+32] = {
            0xb0, 0x70, 0xaa, 0xfc, 0xea, 0x43, 0x9a, 0x4f,
            0x6f, 0x1b, 0xbf, 0xc2, 0xeb, 0x66, 0xd2, 0x9d,
            0x24, 0xb0, 0xca, 0xb7, 0x4d, 0x6b, 0x74, 0x5c,
            0x3c, 0xfb, 0x00, 0x9c, 0xc8, 0xfe, 0x4a, 0xa8,
            0x0e, 0x06, 0x6c, 0x34, 0x81, 0x99, 0x36, 0x54,
            0x9f, 0xf4, 0x9b, 0x6f, 0xd4, 0xd4, 0x1e, 0xdf,
            0xc4, 0x01, 0xa3, 0x67, 0xb8, 0x7d, 0xdd, 0x59,
            0xfe, 0xe3, 0x81, 0x77, 0x96, 0x1c, 0x22, 0x5f,
        };
        size_t aggsig_len = sizeof(aggsig);
        size_t i;
        for (i = 0; i < n; ++i) {
            CHECK(secp256k1_xonly_pubkey_parse(CTX, &pubkeys[i], &pubkeys_ser[i*32]));
        }
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len));
    }
    /* Test vector 2 */
    {
        size_t n = 2;
        const unsigned char pubkeys_ser[2*32] = {
            0x1b, 0x84, 0xc5, 0x56, 0x7b, 0x12, 0x64, 0x40,
            0x99, 0x5d, 0x3e, 0xd5, 0xaa, 0xba, 0x05, 0x65,
            0xd7, 0x1e, 0x18, 0x34, 0x60, 0x48, 0x19, 0xff,
            0x9c, 0x17, 0xf5, 0xe9, 0xd5, 0xdd, 0x07, 0x8f,

            0x46, 0x27, 0x79, 0xad, 0x4a, 0xad, 0x39, 0x51,
            0x46, 0x14, 0x75, 0x1a, 0x71, 0x08, 0x5f, 0x2f,
            0x10, 0xe1, 0xc7, 0xa5, 0x93, 0xe4, 0xe0, 0x30,
            0xef, 0xb5, 0xb8, 0x72, 0x1c, 0xe5, 0x5b, 0x0b,
        };
        secp256k1_xonly_pubkey pubkeys[2];
        const unsigned char msgs32[2*32] = {
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
            0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,

            0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
            0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
            0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
            0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
        };
        const unsigned char aggsig[2*32+32] = {
            0xb0, 0x70, 0xaa, 0xfc, 0xea, 0x43, 0x9a, 0x4f,
            0x6f, 0x1b, 0xbf, 0xc2, 0xeb, 0x66, 0xd2, 0x9d,
            0x24, 0xb0, 0xca, 0xb7, 0x4d, 0x6b, 0x74, 0x5c,
            0x3c, 0xfb, 0x00, 0x9c, 0xc8, 0xfe, 0x4a, 0xa8,
            0xa3, 0xaf, 0xbd, 0xb4, 0x5a, 0x6a, 0x34, 0xbf,
            0x7c, 0x8c, 0x00, 0xf1, 0xb6, 0xd7, 0xe7, 0xd3,
            0x75, 0xb5, 0x45, 0x40, 0xf1, 0x37, 0x16, 0xc8,
            0x7b, 0x62, 0xe5, 0x1e, 0x2f, 0x4f, 0x22, 0xff,
            0xbf, 0x89, 0x13, 0xec, 0x53, 0x22, 0x6a, 0x34,
            0x89, 0x2d, 0x60, 0x25, 0x2a, 0x70, 0x52, 0x61,
            0x4c, 0xa7, 0x9a, 0xe9, 0x39, 0x98, 0x68, 0x28,
            0xd8, 0x1d, 0x23, 0x11, 0x95, 0x73, 0x71, 0xad,
        };
        size_t aggsig_len = sizeof(aggsig);
        size_t i;
        for (i = 0; i < n; ++i) {
            CHECK(secp256k1_xonly_pubkey_parse(CTX, &pubkeys[i], &pubkeys_ser[i*32]));
        }
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len));
    }
}

static void test_schnorrsig_aggregate_api_internal(void) {
    size_t n = testrand_int(N_MAX + 1);
    size_t n_initial = testrand_int(n + 1);
    size_t n_new = n - n_initial;

    /* Test preparation. */
    secp256k1_xonly_pubkey pubkeys[N_MAX];
    unsigned char msgs32[N_MAX*32];
    unsigned char sigs64[N_MAX*64];
    unsigned char aggsig[32*(N_MAX + 1)];
    test_schnorrsig_aggregate_input_helper(pubkeys, msgs32, sigs64, n);

    /* Test body 1: Check API of function aggregate. */
    {
        /* Should not accept NULL for aggsig or aggsig length */
        size_t aggsig_len = sizeof(aggsig);
        CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggregate(CTX, NULL, &aggsig_len, pubkeys, msgs32, sigs64, n_initial));
        CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggregate(CTX, aggsig, NULL, pubkeys, msgs32, sigs64, n_initial));
        /* Should not accept NULL for keys, messages, or signatures if n_initial is not 0 */
        if (n_initial != 0) {
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, NULL, msgs32, sigs64, n_initial));
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, pubkeys, NULL, sigs64, n_initial));
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, NULL, n_initial));
        }
    }

    /* Test body 2: Check API of function inc_aggregate. */
    {
        size_t aggsig_len = sizeof(aggsig);
        CHECK(secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, sigs64, n_initial));
        aggsig_len = 32*(n+1);
        /* Should not accept NULL for aggsig or aggsig length */
        CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_inc_aggregate(CTX, NULL, &aggsig_len, pubkeys, msgs32, &sigs64[n_initial*64], n_initial, n_new));
        CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, NULL, pubkeys, msgs32, &sigs64[n_initial*64], n_initial, n_new));
        /* Should not accept NULL for keys or messages if n is not 0 */
        if (n != 0) {
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, NULL, msgs32, &sigs64[n_initial*64], n_initial, n_new));
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, NULL, &sigs64[n_initial*64], n_initial, n_new));
        }
        /* Should not accept NULL for new_sigs64 if n_new is not 0 */
        if (n_new != 0) {
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, NULL, n_initial, n_new));
        }
        /* Should not accept overflowing number of sigs. */
        CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, &sigs64[n_initial*64], SIZE_MAX, SIZE_MAX));
        if (n_initial > 0) {
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, &sigs64[n_initial*64], n_initial, SIZE_MAX));
        }
        /* Should reject if aggsig_len is too small. */
        aggsig_len = 32*n;
        CHECK(secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, &sigs64[n_initial*64], n_initial, n_new) == 0);
        aggsig_len = 32*(n+1) - 1;
        CHECK(secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, &sigs64[n_initial*64], n_initial, n_new) == 0);
    }

    /* Test body 3: Check API of function aggverify. */
    {
        size_t aggsig_len = sizeof(aggsig);
        CHECK(secp256k1_schnorrsig_inc_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, &sigs64[n_initial*64], n_initial, n_new));
        /* Should not accept NULL for keys or messages if n is not 0 */
        if (n != 0) {
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggverify(CTX, NULL, msgs32, n, aggsig, aggsig_len));
            CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggverify(CTX, pubkeys, NULL, n, aggsig, aggsig_len));
        }
        /* Should never accept NULL the aggsig */
        CHECK_ILLEGAL(CTX, secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, NULL, aggsig_len));
        /* Should reject for invalid aggsig_len. */
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len + 1) == 0);
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len - 1) == 0);
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len + 32) == 0);
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len - 32) == 0);
    }
}

/* In this test, we make sure that trivial attempts to break
 * the security of verification do not work. */
static void test_schnorrsig_aggregate_unforge_internal(void) {
    secp256k1_xonly_pubkey pubkeys[N_MAX];
    unsigned char msgs32[N_MAX*32];
    unsigned char sigs64[N_MAX*64];
    unsigned char aggsig[32*(N_MAX + 1)];

    size_t n = testrand_int(N_MAX + 1);

    /* Test 1: We fix a set of n messages and compute
     * a random aggsig for them. This should not verify. */
    test_schnorrsig_aggregate_input_helper(pubkeys, msgs32, sigs64, n);
    {
        size_t aggsig_len = sizeof(aggsig);
        size_t i;
        /* Sample aggsig randomly */
        for (i = 0; i < n + 1; ++i) {
            testrand256(&aggsig[i*32]);
        }
        /* Make sure that it does not verify */
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len) == 0);
    }

    /* Test 2: We fix a set of n messages and compute valid
     * signatures for all but one. The resulting aggregate signature
     * should not verify. */
    test_schnorrsig_aggregate_input_helper(pubkeys, msgs32, sigs64, n);
    if (n > 0) {
        size_t aggsig_len = sizeof(aggsig);
        /* Replace a randomly chosen real sig with a random one. */
        size_t k = testrand_int(n);
        testrand256(&sigs64[k*64]);
        testrand256(&sigs64[k*64+32]);
        /* Aggregate the n signatures */
        CHECK(secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, sigs64, n));
        /* Make sure the result does not verify */
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len) == 0);
    }

    /* Test 3: We generate a valid aggregate signature and then
     * change one of the messages. This should not verify. */
    test_schnorrsig_aggregate_input_helper(pubkeys, msgs32, sigs64, n);
    if (n > 0) {
        size_t aggsig_len = sizeof(aggsig);
        size_t k;
        /* Aggregate the n signatures */
        CHECK(secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, sigs64, n));
        /* Change one of the messages */
        k = testrand_int(32*n);
        msgs32[k] = msgs32[k]^0xff;
        /* Make sure the result does not verify */
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len) == 0);
    }
}

/* In this test, we make sure that the algorithms properly reject
 * for overflowing and non parseable values. */
static void test_schnorrsig_aggregate_overflow_internal(void) {
    secp256k1_xonly_pubkey pubkeys[N_MAX];
    unsigned char msgs32[N_MAX*32];
    unsigned char sigs64[N_MAX*64];
    unsigned char aggsig[32*(N_MAX + 1)];
    size_t n = testrand_int(N_MAX + 1);

    /* We check that verification returns 0 if the s in aggsig overflows. */
    test_schnorrsig_aggregate_input_helper(pubkeys, msgs32, sigs64, n);
    {
        size_t aggsig_len = sizeof(aggsig);
        /* Aggregate */
        CHECK(secp256k1_schnorrsig_aggregate(CTX, aggsig, &aggsig_len, pubkeys, msgs32, sigs64, n));
        /* Make s in the aggsig overflow */
        memset(&aggsig[n*32], 0xFF, 32);
        /* Should not verify */
        CHECK(secp256k1_schnorrsig_aggverify(CTX, pubkeys, msgs32, n, aggsig, aggsig_len) == 0);
    }
}

DEFINE_SHA256_TRANSFORM_PROBE(sha256_schnorrsig_halfagg)
static void test_schnorrsig_halfagg_ctx_sha256(void) {
    /* Check ctx-provided SHA256 compression override takes effect */
    secp256k1_context *ctx = secp256k1_context_clone(CTX);
    unsigned char aggsig_default[96], aggsig_custom[96];
    unsigned char sk[2][32] = {{1}, {2}};
    unsigned char msgs[2*32];
    secp256k1_keypair keypairs[2];
    secp256k1_xonly_pubkey pks[2];
    unsigned char sigs[2*64];
    size_t aggsig_len;
    size_t i;

    memset(msgs, 1, sizeof(msgs));
    for (i = 0; i < 2; i++) {
        CHECK(secp256k1_keypair_create(CTX, &keypairs[i], sk[i]));
        CHECK(secp256k1_keypair_xonly_pub(CTX, &pks[i], NULL, &keypairs[i]));
        CHECK(secp256k1_schnorrsig_sign32(CTX, &sigs[i*64], &msgs[i*32], &keypairs[i], NULL));
    }

    /* Default behavior. No ctx-provided SHA256 compression */
    aggsig_len = sizeof(aggsig_default);
    CHECK(secp256k1_schnorrsig_aggregate(ctx, aggsig_default, &aggsig_len, pks, msgs, sigs, 2));
    CHECK(!sha256_schnorrsig_halfagg_called);

    /* Override SHA256 compression directly, bypassing the ctx setter sanity checks */
    ctx->hash_ctx.fn_sha256_compression = sha256_schnorrsig_halfagg;
    aggsig_len = sizeof(aggsig_custom);
    CHECK(secp256k1_schnorrsig_aggregate(ctx, aggsig_custom, &aggsig_len, pks, msgs, sigs, 2));
    CHECK(sha256_schnorrsig_halfagg_called);
    /* Outputs must differ if custom compression was used */
    CHECK(secp256k1_memcmp_var(aggsig_default, aggsig_custom, 96) != 0);

    secp256k1_context_destroy(ctx);
}

/* --- Test registry --- */
REPEAT_TEST(test_schnorrsig_aggregate)
REPEAT_TEST(test_schnorrsig_aggregate_api)
REPEAT_TEST(test_schnorrsig_aggregate_unforge)
REPEAT_TEST(test_schnorrsig_aggregate_overflow)

static const struct tf_test_entry tests_schnorrsig_halfagg[] = {
    CASE1(test_schnorrsig_sha256_tagged_aggregate),
    CASE1(test_schnorrsig_aggverify_spec_vectors),
    CASE1(test_schnorrsig_aggregate),
    CASE1(test_schnorrsig_aggregate_api),
    CASE1(test_schnorrsig_aggregate_unforge),
    CASE1(test_schnorrsig_aggregate_overflow),
    CASE1(test_schnorrsig_halfagg_ctx_sha256),
};

#undef N_MAX

#endif
