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


#ifndef _SECP256K1_BORROMEAN_IMPL_H_
#define _SECP256K1_BORROMEAN_IMPL_H_

#include "scalar.h"
#include "field.h"
#include "group.h"
#include "ecmult.h"
#include "ecmult_gen.h"
#include "borromean.h"

#include <limits.h>

#ifdef WORDS_BIGENDIAN
#define BE32(x) (x)
#else
#define BE32(p) ((((p) & 0xFF) << 24) | (((p) & 0xFF00) << 8) | (((p) & 0xFF0000) >> 8) | (((p) & 0xFF000000) >> 24))
#endif

SECP256K1_INLINE static void secp256k1_borromean_hash(unsigned char *hash, const unsigned char *m, int mlen, const unsigned char *e, int elen,
 int ridx, int eidx) {
    uint32_t ring;
    uint32_t epos;
    secp256k1_sha256_t sha256_en;
    secp256k1_sha256_initialize(&sha256_en);
    ring = BE32((uint32_t)ridx);
    epos = BE32((uint32_t)eidx);
    secp256k1_sha256_write(&sha256_en, e, elen);
    secp256k1_sha256_write(&sha256_en, m, mlen);
    secp256k1_sha256_write(&sha256_en, (unsigned char*)&ring, 4);
    secp256k1_sha256_write(&sha256_en, (unsigned char*)&epos, 4);
    secp256k1_sha256_finalize(&sha256_en, hash);
}

/**  "Borromean" ring signature.
 *   Verifies nrings concurrent ring signatures all sharing a challenge value.
 *   Signature is one s value per pubkey and a hash.
 *   Verification equation:
 *   | m = H(P_{0..}||message) (Message must contain pubkeys or a pubkey commitment)
 *   | For each ring i:
 *   | | en = to_scalar(H(e0||m||i||0))
 *   | | For each pubkey j:
 *   | | | r = s_i_j G + en * P_i_j
 *   | | | e = H(r||m||i||j)
 *   | | | en = to_scalar(e)
 *   | | r_i = r
 *   | return e_0 ==== H(r_{0..i}||m)
 */
int secp256k1_borromean_verify(const secp256k1_ecmult_context_t* ecmult_ctx, secp256k1_scalar_t *evalues, const unsigned char *e0,
 const secp256k1_scalar_t *s, const secp256k1_gej_t *pubs, const int *rsizes, int nrings, const unsigned char *m, int mlen) {
    secp256k1_gej_t rgej;
    secp256k1_ge_t rge;
    secp256k1_scalar_t ens;
    secp256k1_sha256_t sha256_e0;
    unsigned char tmp[33];
    int i;
    int j;
    int count;
    int size;
    int overflow;
    VERIFY_CHECK(ecmult_ctx != NULL);
    VERIFY_CHECK(e0 != NULL);
    VERIFY_CHECK(s != NULL);
    VERIFY_CHECK(pubs != NULL);
    VERIFY_CHECK(rsizes != NULL);
    VERIFY_CHECK(nrings > 0);
    VERIFY_CHECK(m != NULL);
    count = 0;
    secp256k1_sha256_initialize(&sha256_e0);
    for (i = 0; i < nrings; i++) {
        VERIFY_CHECK(INT_MAX - count > rsizes[i]);
        secp256k1_borromean_hash(tmp, m, mlen, e0, 32, i, 0);
        secp256k1_scalar_set_b32(&ens, tmp, &overflow);
        for (j = 0; j < rsizes[i]; j++) {
            if (overflow || secp256k1_scalar_is_zero(&s[count]) || secp256k1_scalar_is_zero(&ens) || secp256k1_gej_is_infinity(&pubs[count])) {
                return 0;
            }
            if (evalues) {
                /*If requested, save the challenges for proof rewind.*/
                evalues[count] = ens;
            }
            secp256k1_ecmult(ecmult_ctx, &rgej, &pubs[count], &ens, &s[count]);
            if (secp256k1_gej_is_infinity(&rgej)) {
                return 0;
            }
            /* OPT: loop can be hoisted and split to use batch inversion across all the rings; this would make it much faster. */
            secp256k1_ge_set_gej_var(&rge, &rgej);
            secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1);
            if (j != rsizes[i] - 1) {
                secp256k1_borromean_hash(tmp, m, mlen, tmp, 33, i, j + 1);
                secp256k1_scalar_set_b32(&ens, tmp, &overflow);
            } else {
                secp256k1_sha256_write(&sha256_e0, tmp, size);
            }
            count++;
        }
    }
    secp256k1_sha256_write(&sha256_e0, m, mlen);
    secp256k1_sha256_finalize(&sha256_e0, tmp);
    return memcmp(e0, tmp, 32) == 0;
}

int secp256k1_borromean_sign(const secp256k1_ecmult_context_t* ecmult_ctx, const secp256k1_ecmult_gen_context_t *ecmult_gen_ctx,
 unsigned char *e0, secp256k1_scalar_t *s, const secp256k1_gej_t *pubs, const secp256k1_scalar_t *k, const secp256k1_scalar_t *sec,
 const int *rsizes, const int *secidx, int nrings, const unsigned char *m, int mlen) {
    secp256k1_gej_t rgej;
    secp256k1_ge_t rge;
    secp256k1_scalar_t ens;
    secp256k1_sha256_t sha256_e0;
    unsigned char tmp[33];
    int i;
    int j;
    int count;
    int size;
    int overflow;
    VERIFY_CHECK(ecmult_ctx != NULL);
    VERIFY_CHECK(ecmult_gen_ctx != NULL);
    VERIFY_CHECK(e0 != NULL);
    VERIFY_CHECK(s != NULL);
    VERIFY_CHECK(pubs != NULL);
    VERIFY_CHECK(k != NULL);
    VERIFY_CHECK(sec != NULL);
    VERIFY_CHECK(rsizes != NULL);
    VERIFY_CHECK(secidx != NULL);
    VERIFY_CHECK(nrings > 0);
    VERIFY_CHECK(m != NULL);
    secp256k1_sha256_initialize(&sha256_e0);
    count = 0;
    for (i = 0; i < nrings; i++) {
        VERIFY_CHECK(INT_MAX - count > rsizes[i]);
        secp256k1_ecmult_gen(ecmult_gen_ctx, &rgej, &k[i]);
        secp256k1_ge_set_gej(&rge, &rgej);
        if (secp256k1_gej_is_infinity(&rgej)) {
            return 0;
        }
        secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1);
        for (j = secidx[i] + 1; j < rsizes[i]; j++) {
            secp256k1_borromean_hash(tmp, m, mlen, tmp, 33, i, j);
            secp256k1_scalar_set_b32(&ens, tmp, &overflow);
            if (overflow || secp256k1_scalar_is_zero(&ens)) {
                return 0;
            }
            /** The signing algorithm as a whole is not memory uniform so there is likely a cache sidechannel that
             *  leaks which members are non-forgeries. That the forgeries themselves are variable time may leave
             *  an additional privacy impacting timing side-channel, but not a key loss one.
             */
            secp256k1_ecmult(ecmult_ctx, &rgej, &pubs[count + j], &ens, &s[count + j]);
            if (secp256k1_gej_is_infinity(&rgej)) {
                return 0;
            }
            secp256k1_ge_set_gej_var(&rge, &rgej);
            secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1);
        }
        secp256k1_sha256_write(&sha256_e0, tmp, size);
        count += rsizes[i];
    }
    secp256k1_sha256_write(&sha256_e0, m, mlen);
    secp256k1_sha256_finalize(&sha256_e0, e0);
    count = 0;
    for (i = 0; i < nrings; i++) {
        VERIFY_CHECK(INT_MAX - count > rsizes[i]);
        secp256k1_borromean_hash(tmp, m, mlen, e0, 32, i, 0);
        secp256k1_scalar_set_b32(&ens, tmp, &overflow);
        if (overflow || secp256k1_scalar_is_zero(&ens)) {
            return 0;
        }
        for (j = 0; j < secidx[i]; j++) {
            secp256k1_ecmult(ecmult_ctx, &rgej, &pubs[count + j], &ens, &s[count + j]);
            if (secp256k1_gej_is_infinity(&rgej)) {
                return 0;
            }
            secp256k1_ge_set_gej_var(&rge, &rgej);
            secp256k1_eckey_pubkey_serialize(&rge, tmp, &size, 1);
            secp256k1_borromean_hash(tmp, m, mlen, tmp, 33, i, j + 1);
            secp256k1_scalar_set_b32(&ens, tmp, &overflow);
            if (overflow || secp256k1_scalar_is_zero(&ens)) {
                return 0;
            }
        }
        secp256k1_scalar_mul(&s[count + j], &ens, &sec[i]);
        secp256k1_scalar_negate(&s[count + j], &s[count + j]);
        secp256k1_scalar_add(&s[count + j], &s[count + j], &k[i]);
        if (secp256k1_scalar_is_zero(&s[count + j])) {
            return 0;
        }
        count += rsizes[i];
    }
    secp256k1_scalar_clear(&ens);
    secp256k1_ge_clear(&rge);
    secp256k1_gej_clear(&rgej);
    memset(tmp, 0, 33);
    return 1;
}

#endif