/** @file
 *****************************************************************************

 Declaration of interfaces for a ppzkSNARK for R1CS.

 This includes:
 - class for proving key
 - class for verification key
 - class for processed verification key
 - class for key pair (proving key & verification key)
 - class for proof
 - generator algorithm
 - prover algorithm
 - verifier algorithm (with strong or weak input consistency)
 - online verifier algorithm (with strong or weak input consistency)

 The implementation instantiates (a modification of) the protocol of \[PGHR13],
 by following extending, and optimizing the approach described in \[BCTV14].


 Acronyms:

 - R1CS = "Rank-1 Constraint Systems"
 - ppzkSNARK = "PreProcessing Zero-Knowledge Succinct Non-interactive ARgument of Knowledge"

 References:

 \[BCTV14]:
 "Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture",
 Eli Ben-Sasson, Alessandro Chiesa, Eran Tromer, Madars Virza,
 USENIX Security 2014,
 <http://eprint.iacr.org/2013/879>

 \[PGHR13]:
 "Pinocchio: Nearly practical verifiable computation",
 Bryan Parno, Craig Gentry, Jon Howell, Mariana Raykova,
 IEEE S&P 2013,
 <https://eprint.iacr.org/2013/279>

 *****************************************************************************
 * @author     This file is part of libsnark, developed by SCIPR Lab
 *             and contributors (see AUTHORS).
 * @copyright  MIT license (see LICENSE file)
 *****************************************************************************/

#ifndef R1CS_PPZKSNARK_HPP_
#define R1CS_PPZKSNARK_HPP_

#include <memory>

#include <libff/algebra/curves/public_params.hpp>

#include <libsnark/common/data_structures/accumulation_vector.hpp>
#include <libsnark/knowledge_commitment/knowledge_commitment.hpp>
#include <libsnark/relations/constraint_satisfaction_problems/r1cs/r1cs.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark_params.hpp>

namespace libsnark {

/******************************** Proving key ********************************/

template<typename ppT>
class r1cs_ppzksnark_proving_key;

template<typename ppT>
std::ostream& operator<<(std::ostream &out, const r1cs_ppzksnark_proving_key<ppT> &pk);

template<typename ppT>
std::istream& operator>>(std::istream &in, r1cs_ppzksnark_proving_key<ppT> &pk);

/**
 * A proving key for the R1CS ppzkSNARK.
 */
template<typename ppT>
class r1cs_ppzksnark_proving_key {
public:
    knowledge_commitment_vector<libff::G1<ppT>, libff::G1<ppT> > A_query;
    knowledge_commitment_vector<libff::G2<ppT>, libff::G1<ppT> > B_query;
    knowledge_commitment_vector<libff::G1<ppT>, libff::G1<ppT> > C_query;
    libff::G1_vector<ppT> H_query;
    libff::G1_vector<ppT> K_query;

    r1cs_ppzksnark_constraint_system<ppT> constraint_system;

    r1cs_ppzksnark_proving_key() {};
    r1cs_ppzksnark_proving_key<ppT>& operator=(const r1cs_ppzksnark_proving_key<ppT> &other) = default;
    r1cs_ppzksnark_proving_key(const r1cs_ppzksnark_proving_key<ppT> &other) = default;
    r1cs_ppzksnark_proving_key(r1cs_ppzksnark_proving_key<ppT> &&other) = default;
    r1cs_ppzksnark_proving_key(knowledge_commitment_vector<libff::G1<ppT>, libff::G1<ppT> > &&A_query,
                               knowledge_commitment_vector<libff::G2<ppT>, libff::G1<ppT> > &&B_query,
                               knowledge_commitment_vector<libff::G1<ppT>, libff::G1<ppT> > &&C_query,
                               libff::G1_vector<ppT> &&H_query,
                               libff::G1_vector<ppT> &&K_query,
                               r1cs_ppzksnark_constraint_system<ppT> &&constraint_system) :
        A_query(std::move(A_query)),
        B_query(std::move(B_query)),
        C_query(std::move(C_query)),
        H_query(std::move(H_query)),
        K_query(std::move(K_query)),
        constraint_system(std::move(constraint_system))
    {};

    size_t G1_size() const
    {
        return 2*(A_query.domain_size() + C_query.domain_size()) + B_query.domain_size() + H_query.size() + K_query.size();
    }

    size_t G2_size() const
    {
        return B_query.domain_size();
    }

    size_t G1_sparse_size() const
    {
        return 2*(A_query.size() + C_query.size()) + B_query.size() + H_query.size() + K_query.size();
    }

    size_t G2_sparse_size() const
    {
        return B_query.size();
    }

    size_t size_in_bits() const
    {
        return A_query.size_in_bits() + B_query.size_in_bits() + C_query.size_in_bits() + libff::size_in_bits(H_query) + libff::size_in_bits(K_query);
    }

    void print_size() const
    {
        libff::print_indent(); printf("* G1 elements in PK: %zu\n", this->G1_size());
        libff::print_indent(); printf("* Non-zero G1 elements in PK: %zu\n", this->G1_sparse_size());
        libff::print_indent(); printf("* G2 elements in PK: %zu\n", this->G2_size());
        libff::print_indent(); printf("* Non-zero G2 elements in PK: %zu\n", this->G2_sparse_size());
        libff::print_indent(); printf("* PK size in bits: %zu\n", this->size_in_bits());
    }

    bool operator==(const r1cs_ppzksnark_proving_key<ppT> &other) const;
    friend std::ostream& operator<< <ppT>(std::ostream &out, const r1cs_ppzksnark_proving_key<ppT> &pk);
    friend std::istream& operator>> <ppT>(std::istream &in, r1cs_ppzksnark_proving_key<ppT> &pk);
};


/******************************* Verification key ****************************/

template<typename ppT>
class r1cs_ppzksnark_verification_key;

template<typename ppT>
std::ostream& operator<<(std::ostream &out, const r1cs_ppzksnark_verification_key<ppT> &vk);

template<typename ppT>
std::istream& operator>>(std::istream &in, r1cs_ppzksnark_verification_key<ppT> &vk);

/**
 * A verification key for the R1CS ppzkSNARK.
 */
template<typename ppT>
class r1cs_ppzksnark_verification_key {
public:
    libff::G2<ppT> alphaA_g2;
    libff::G1<ppT> alphaB_g1;
    libff::G2<ppT> alphaC_g2;
    libff::G2<ppT> gamma_g2;
    libff::G1<ppT> gamma_beta_g1;
    libff::G2<ppT> gamma_beta_g2;
    libff::G2<ppT> rC_Z_g2;

    accumulation_vector<libff::G1<ppT> > encoded_IC_query;

    r1cs_ppzksnark_verification_key() = default;
    r1cs_ppzksnark_verification_key(const libff::G2<ppT> &alphaA_g2,
                                    const libff::G1<ppT> &alphaB_g1,
                                    const libff::G2<ppT> &alphaC_g2,
                                    const libff::G2<ppT> &gamma_g2,
                                    const libff::G1<ppT> &gamma_beta_g1,
                                    const libff::G2<ppT> &gamma_beta_g2,
                                    const libff::G2<ppT> &rC_Z_g2,
                                    const accumulation_vector<libff::G1<ppT> > &eIC) :
        alphaA_g2(alphaA_g2),
        alphaB_g1(alphaB_g1),
        alphaC_g2(alphaC_g2),
        gamma_g2(gamma_g2),
        gamma_beta_g1(gamma_beta_g1),
        gamma_beta_g2(gamma_beta_g2),
        rC_Z_g2(rC_Z_g2),
        encoded_IC_query(eIC)
    {};

    size_t G1_size() const
    {
        return 2 + encoded_IC_query.size();
    }

    size_t G2_size() const
    {
        return 5;
    }

    size_t size_in_bits() const
    {
        return (2 * libff::G1<ppT>::size_in_bits() + encoded_IC_query.size_in_bits() + 5 * libff::G2<ppT>::size_in_bits());
    }

    void print_size() const
    {
        libff::print_indent(); printf("* G1 elements in VK: %zu\n", this->G1_size());
        libff::print_indent(); printf("* G2 elements in VK: %zu\n", this->G2_size());
        libff::print_indent(); printf("* VK size in bits: %zu\n", this->size_in_bits());
    }

    bool operator==(const r1cs_ppzksnark_verification_key<ppT> &other) const;
    friend std::ostream& operator<< <ppT>(std::ostream &out, const r1cs_ppzksnark_verification_key<ppT> &vk);
    friend std::istream& operator>> <ppT>(std::istream &in, r1cs_ppzksnark_verification_key<ppT> &vk);

    static r1cs_ppzksnark_verification_key<ppT> dummy_verification_key(const size_t input_size);
};


/************************ Processed verification key *************************/

template<typename ppT>
class r1cs_ppzksnark_processed_verification_key;

template<typename ppT>
std::ostream& operator<<(std::ostream &out, const r1cs_ppzksnark_processed_verification_key<ppT> &pvk);

template<typename ppT>
std::istream& operator>>(std::istream &in, r1cs_ppzksnark_processed_verification_key<ppT> &pvk);

/**
 * A processed verification key for the R1CS ppzkSNARK.
 *
 * Compared to a (non-processed) verification key, a processed verification key
 * contains a small constant amount of additional pre-computed information that
 * enables a faster verification time.
 */
template<typename ppT>
class r1cs_ppzksnark_processed_verification_key {
public:
    libff::G2_precomp<ppT> pp_G2_one_precomp;
    libff::G2_precomp<ppT> vk_alphaA_g2_precomp;
    libff::G1_precomp<ppT> vk_alphaB_g1_precomp;
    libff::G2_precomp<ppT> vk_alphaC_g2_precomp;
    libff::G2_precomp<ppT> vk_rC_Z_g2_precomp;
    libff::G2_precomp<ppT> vk_gamma_g2_precomp;
    libff::G1_precomp<ppT> vk_gamma_beta_g1_precomp;
    libff::G2_precomp<ppT> vk_gamma_beta_g2_precomp;

    accumulation_vector<libff::G1<ppT> > encoded_IC_query;

    bool operator==(const r1cs_ppzksnark_processed_verification_key &other) const;
    friend std::ostream& operator<< <ppT>(std::ostream &out, const r1cs_ppzksnark_processed_verification_key<ppT> &pvk);
    friend std::istream& operator>> <ppT>(std::istream &in, r1cs_ppzksnark_processed_verification_key<ppT> &pvk);
};


/********************************** Key pair *********************************/

/**
 * A key pair for the R1CS ppzkSNARK, which consists of a proving key and a verification key.
 */
template<typename ppT>
class r1cs_ppzksnark_keypair {
public:
    r1cs_ppzksnark_proving_key<ppT> pk;
    r1cs_ppzksnark_verification_key<ppT> vk;

    r1cs_ppzksnark_keypair() = default;
    r1cs_ppzksnark_keypair(const r1cs_ppzksnark_keypair<ppT> &other) = default;
    r1cs_ppzksnark_keypair(r1cs_ppzksnark_proving_key<ppT> &&pk,
                           r1cs_ppzksnark_verification_key<ppT> &&vk) :
        pk(std::move(pk)),
        vk(std::move(vk))
    {}

    r1cs_ppzksnark_keypair(r1cs_ppzksnark_keypair<ppT> &&other) = default;
};


/*********************************** Proof ***********************************/

template<typename ppT>
class r1cs_ppzksnark_proof;

template<typename ppT>
std::ostream& operator<<(std::ostream &out, const r1cs_ppzksnark_proof<ppT> &proof);

template<typename ppT>
std::istream& operator>>(std::istream &in, r1cs_ppzksnark_proof<ppT> &proof);

/**
 * A proof for the R1CS ppzkSNARK.
 *
 * While the proof has a structure, externally one merely opaquely produces,
 * serializes/deserializes, and verifies proofs. We only expose some information
 * about the structure for statistics purposes.
 */
template<typename ppT>
class r1cs_ppzksnark_proof {
public:
    knowledge_commitment<libff::G1<ppT>, libff::G1<ppT> > g_A;
    knowledge_commitment<libff::G2<ppT>, libff::G1<ppT> > g_B;
    knowledge_commitment<libff::G1<ppT>, libff::G1<ppT> > g_C;
    libff::G1<ppT> g_H;
    libff::G1<ppT> g_K;

    r1cs_ppzksnark_proof()
    {
        // invalid proof with valid curve points
        this->g_A.g = libff::G1<ppT> ::one();
        this->g_A.h = libff::G1<ppT>::one();
        this->g_B.g = libff::G2<ppT> ::one();
        this->g_B.h = libff::G1<ppT>::one();
        this->g_C.g = libff::G1<ppT> ::one();
        this->g_C.h = libff::G1<ppT>::one();
        this->g_H = libff::G1<ppT>::one();
        this->g_K = libff::G1<ppT>::one();
    }
    r1cs_ppzksnark_proof(knowledge_commitment<libff::G1<ppT>, libff::G1<ppT> > &&g_A,
                         knowledge_commitment<libff::G2<ppT>, libff::G1<ppT> > &&g_B,
                         knowledge_commitment<libff::G1<ppT>, libff::G1<ppT> > &&g_C,
                         libff::G1<ppT> &&g_H,
                         libff::G1<ppT> &&g_K) :
        g_A(std::move(g_A)),
        g_B(std::move(g_B)),
        g_C(std::move(g_C)),
        g_H(std::move(g_H)),
        g_K(std::move(g_K))
    {};

    size_t G1_size() const
    {
        return 7;
    }

    size_t G2_size() const
    {
        return 1;
    }

    size_t size_in_bits() const
    {
        return G1_size() * libff::G1<ppT>::size_in_bits() + G2_size() * libff::G2<ppT>::size_in_bits();
    }

    void print_size() const
    {
        libff::print_indent(); printf("* G1 elements in proof: %zu\n", this->G1_size());
        libff::print_indent(); printf("* G2 elements in proof: %zu\n", this->G2_size());
        libff::print_indent(); printf("* Proof size in bits: %zu\n", this->size_in_bits());
    }

    bool is_well_formed() const
    {
        return (g_A.g.is_well_formed() && g_A.h.is_well_formed() &&
                g_B.g.is_well_formed() && g_B.h.is_well_formed() &&
                g_C.g.is_well_formed() && g_C.h.is_well_formed() &&
                g_H.is_well_formed() &&
                g_K.is_well_formed());
    }

    bool operator==(const r1cs_ppzksnark_proof<ppT> &other) const;
    friend std::ostream& operator<< <ppT>(std::ostream &out, const r1cs_ppzksnark_proof<ppT> &proof);
    friend std::istream& operator>> <ppT>(std::istream &in, r1cs_ppzksnark_proof<ppT> &proof);
};


/***************************** Main algorithms *******************************/

/**
 * A generator algorithm for the R1CS ppzkSNARK.
 *
 * Given a R1CS constraint system CS, this algorithm produces proving and verification keys for CS.
 */
template<typename ppT>
r1cs_ppzksnark_keypair<ppT> r1cs_ppzksnark_generator(const r1cs_ppzksnark_constraint_system<ppT> &cs);

/**
 * A prover algorithm for the R1CS ppzkSNARK.
 *
 * Given a R1CS primary input X and a R1CS auxiliary input Y, this algorithm
 * produces a proof (of knowledge) that attests to the following statement:
 *               ``there exists Y such that CS(X,Y)=0''.
 * Above, CS is the R1CS constraint system that was given as input to the generator algorithm.
 */
template<typename ppT>
r1cs_ppzksnark_proof<ppT> r1cs_ppzksnark_prover(const r1cs_ppzksnark_proving_key<ppT> &pk,
                                                const r1cs_ppzksnark_primary_input<ppT> &primary_input,
                                                const r1cs_ppzksnark_auxiliary_input<ppT> &auxiliary_input);

/*
 Below are four variants of verifier algorithm for the R1CS ppzkSNARK.

 These are the four cases that arise from the following two choices:

 (1) The verifier accepts a (non-processed) verification key or, instead, a processed verification key.
     In the latter case, we call the algorithm an "online verifier".

 (2) The verifier checks for "weak" input consistency or, instead, "strong" input consistency.
     Strong input consistency requires that |primary_input| = CS.num_inputs, whereas
     weak input consistency requires that |primary_input| <= CS.num_inputs (and
     the primary input is implicitly padded with zeros up to length CS.num_inputs).
 */

/**
 * A verifier algorithm for the R1CS ppzkSNARK that:
 * (1) accepts a non-processed verification key, and
 * (2) has weak input consistency.
 */
template<typename ppT>
bool r1cs_ppzksnark_verifier_weak_IC(const r1cs_ppzksnark_verification_key<ppT> &vk,
                                     const r1cs_ppzksnark_primary_input<ppT> &primary_input,
                                     const r1cs_ppzksnark_proof<ppT> &proof);

/**
 * A verifier algorithm for the R1CS ppzkSNARK that:
 * (1) accepts a non-processed verification key, and
 * (2) has strong input consistency.
 */
template<typename ppT>
bool r1cs_ppzksnark_verifier_strong_IC(const r1cs_ppzksnark_verification_key<ppT> &vk,
                                       const r1cs_ppzksnark_primary_input<ppT> &primary_input,
                                       const r1cs_ppzksnark_proof<ppT> &proof);

/**
 * Convert a (non-processed) verification key into a processed verification key.
 */
template<typename ppT>
r1cs_ppzksnark_processed_verification_key<ppT> r1cs_ppzksnark_verifier_process_vk(const r1cs_ppzksnark_verification_key<ppT> &vk);

/**
 * A verifier algorithm for the R1CS ppzkSNARK that:
 * (1) accepts a processed verification key, and
 * (2) has weak input consistency.
 */
template<typename ppT>
bool r1cs_ppzksnark_online_verifier_weak_IC(const r1cs_ppzksnark_processed_verification_key<ppT> &pvk,
                                            const r1cs_ppzksnark_primary_input<ppT> &input,
                                            const r1cs_ppzksnark_proof<ppT> &proof);

/**
 * A verifier algorithm for the R1CS ppzkSNARK that:
 * (1) accepts a processed verification key, and
 * (2) has strong input consistency.
 */
template<typename ppT>
bool r1cs_ppzksnark_online_verifier_strong_IC(const r1cs_ppzksnark_processed_verification_key<ppT> &pvk,
                                              const r1cs_ppzksnark_primary_input<ppT> &primary_input,
                                              const r1cs_ppzksnark_proof<ppT> &proof);

/****************************** Miscellaneous ********************************/

/**
 * For debugging purposes (of r1cs_ppzksnark_r1cs_ppzksnark_verifier_gadget):
 *
 * A verifier algorithm for the R1CS ppzkSNARK that:
 * (1) accepts a non-processed verification key,
 * (2) has weak input consistency, and
 * (3) uses affine coordinates for elliptic-curve computations.
 */
template<typename ppT>
bool r1cs_ppzksnark_affine_verifier_weak_IC(const r1cs_ppzksnark_verification_key<ppT> &vk,
                                            const r1cs_ppzksnark_primary_input<ppT> &primary_input,
                                            const r1cs_ppzksnark_proof<ppT> &proof);


} // libsnark

#include <libsnark/zk_proof_systems/ppzksnark/r1cs_ppzksnark/r1cs_ppzksnark.tcc>

#endif // R1CS_PPZKSNARK_HPP_
