// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) DUSK NETWORK. All rights reserved.

//! In general computing science, a circuit is a computational model through
//! which input values proceed through a sequence of gates, each of which
//! computes a specific function. In our case, the circuits perform the logical
//! checks with public and private inputs to make sure that the generated Blind
//! Bid proofs are generated by the rules of the game. For explanatory reasons,
//! we define two circuits although in practice, these two are a collection of
//! gadgets added up together in order to compose the [`BlindBidCircuit`]:
//!
//! 1. Blind Bid circuit;
//! 2. Score Generation circuit.
//!
//! Below we describe the Blind Bid circuit and the score generation circuit,
//! who together form the pillars of the Proof-of-Blind Bid procedure.
//!
//! ### Blind Bid Circuit
//! ![Fig1](https://lh4.googleusercontent.com/oPR_7LLAAj6K8qjxiqMMavfucdslgv3DAGcElrn6hwDLMk0mYucfcwPyqLoT0HIyqvqmUDof4PhnXFV6-3tbImYtdG4WNHJcq72GaLsHHKE4EMiIv8pMJqLplGVlzuK6nIYsUp_r)
//! Some noteworthy proofs are:
//!
//! Opening Proof: this is generated to check where the Bid has been stored on
//! the merkle-tree (you could see this as a ledger where values are stored)
//! that contains all of the bids. This proof checks that the Bid has indeed
//! been made, and can be trusted.
//!
//! Pre-image check of the Bid: this is a consistency check that aims to make it
//! impossible to cheat during the computation of the bid. If a bad actor
//! attempts to cheat, the opening proof will not be the same and therefore not
//! consistent.
//!
//! It goes both ways. If you try to cheat on the pre-image check, the Opening
//! Proof will fail as a result. And if you try to cheat on the Opening Proof,
//! the pre-image would be impossible to compute because there are 2^256
//! different possibilities. To put that in perspective, even with all the time
//! in the universe, you would not be able to check all of them (note that a
//! consensus round also only takes ~10 seconds).
//!
//! In Fig 1. you can see that in step 3. & 4 we perform range checks to make
//! sure that the Bid is valid and eligible during the current consensus round
//! and steps. Finally, in proofs 7. & 8. we check the hash of the secret (H(k))
//! and the prover ID (i), asking for proof that the block generator - who we
//! assume has posted the bid -, indeed is the owner of the bid.
//!
//! Once the process above has been completed we move to Score Generation.
//!
//! ### Score Generation Circuit
//! ![Fig2](https://lh5.googleusercontent.com/I6b88LUtOM5MIcbkJu3p0CZdoz34kBl7PXFovQbt4uiuAExOyW0yfLsI-1BbnzOh0u_kDv5LOB0ztuJgrh7h3y2Qh94qNN4FnP6P2Yi0cxHjJhWC3sSA49jEZtYLSutXIDkwC4Dz)
//! Score generation needs to be understood as a continuation of the next
//! circuit instead of a different entity.
//!
//! The final step is to check if the Score in the Blind Bid is correct. This
//! step is important, as the Score determines the winner of an election round.
//!
//! The prover ID (y) is directly connected to the secret (k) and pre-image hash
//! of the Bid (H(bidi)), meaning that any changes to the score will
//! automatically result in a different prover ID, and thus a failed constraint
//! on line 1. of the Score Generation Circuit.

use crate::bid::score::Score;
use crate::bid::{encoding::preimage_gadget, Bid};
use anyhow::Result;
use dusk_bls12_381::BlsScalar;
use dusk_jubjub::{JubJubAffine, GENERATOR_EXTENDED, GENERATOR_NUMS_EXTENDED};
use dusk_pki::Ownable;
use dusk_plonk::constraint_system::ecc::{
    scalar_mul::fixed_base::scalar_mul, Point,
};
use dusk_plonk::prelude::*;
use dusk_poseidon::{
    sponge,
    tree::{merkle_opening as merkle_opening_gadget, PoseidonBranch},
};
use plonk_gadgets::{AllocatedScalar, RangeGadgets::max_bound};
#[cfg(test)]
mod bid_tests;
#[cfg(test)]
mod tree_assets;

/// The BlindBidCircuit is used to prove or verify a proof of blindbid.
/// As the [Circuit](dusk_plonk::circuit_builder::Circuit) shows. The circuit
/// needs to be previously compiled.
///
/// The [Public Inputs](dusk_plonk::circuit_builder::PublicInput) **in order**
/// used for the proof are:
/// 1. Merkle Tree Root.
/// 2. Bid hashed with Poseidon sponge hash.
/// 3. Bid commitment field.
/// 4. Prover ID.
/// 5. Score produced by the Bid.
///
/// # Example
/// ```ignore
/// // Initialize your `PublicInput` Vector.
/// let pi = vec![
///     PublicInput::BlsScalar(*branch.root(), 0),
///     PublicInput::BlsScalar(storage_bid, 0),
///     PublicInput::AffinePoint(bid.commitment(), 0, 0),
///     PublicInput::BlsScalar(bid.hashed_secret(), 0),
///     PublicInput::BlsScalar(prover_id, 0),
///     PublicInput::BlsScalar(score.value(), 0)];
///
/// // Create a mutable instance of the BlindBidCircuit and
/// //
/// let mut circuit = BlindBidCircuit {
///     bid,
///     score: Score::default(),
///     secret_k: BlsScalar::one(),
///     secret: JubJubAffine::default(),
///     seed: BlsScalar::from(consensus_round_seed),
///     latest_consensus_round: BlsScalar::from(latest_consensus_round),
///     latest_consensus_step: BlsScalar::from(latest_consensus_step),
///     branch: &branch,
///     trim_size: 1 << 15,
///     pi_positions: vec![],
/// };
/// circuit.verify_proof(&pub_params, &vk, b"CorrectBid", &proof, &pi)
/// ```
#[cfg_attr(docsrs, doc(cfg(all(feature = "std", feature = "canon"))))]
#[derive(Debug, Clone)]
pub struct BlindBidCircuit<'a> {
    /// Bid used to generate the score
    pub bid: Bid,
    /// Score generated from the previous Bid.
    pub score: Score,
    /// Pre-image of the hashed_secret of the Bid.
    pub secret_k: BlsScalar,
    /// Consensus round seed at which Score was generated.
    pub seed: BlsScalar,
    /// Latest consensus round value at which Score was generated.
    pub latest_consensus_round: BlsScalar,
    /// Latest consensus step value at which Score was generated.
    pub latest_consensus_step: BlsScalar,
    /// Merkle Opening of the leaf that contains the Bid.
    pub branch: &'a PoseidonBranch<17>,
    /// Secret that derypts the Cipher.
    pub secret: JubJubAffine,
    /// Trim size of the Public Parameters used by the PLONK mechanism.
    pub trim_size: usize,
    /// Positions of the Public Inputs used with the proof.
    pub pi_positions: Vec<PublicInput>,
}

impl<'a> Circuit<'a> for BlindBidCircuit<'a> {
    fn gadget(&mut self, composer: &mut StandardComposer) -> Result<()> {
        // Check if the inputs were indeed pre-loaded inside of the circuit
        // structure.
        let bid = self.bid;
        let branch = self.branch;
        let secret_k = self.secret_k;
        let seed = self.seed;
        let latest_consensus_round = self.latest_consensus_round;
        let latest_consensus_step = self.latest_consensus_step;
        let score = self.score;
        let secret = self.secret;
        // Instantiate PI vector.
        let pi = self.get_mut_pi_positions();
        // Get the corresponding `StorageBid` value that for the `Bid`
        // which is effectively the value of the proven leaf (hash of the Bid)
        // and allocate it.
        let storage_bid: BlsScalar = bid.into();
        let bid_hash = AllocatedScalar::allocate(composer, storage_bid);
        // Allocate Bid-internal fields
        let bid_hashed_secret =
            AllocatedScalar::allocate(composer, bid.hashed_secret());
        let bid_cipher = (
            composer.add_input(bid.encrypted_data().cipher()[0]),
            composer.add_input(bid.encrypted_data().cipher()[1]),
        );
        let bid_commitment =
            Point::from_private_affine(composer, bid.commitment());
        let bid_stealth_addr = (
            Point::from_private_affine(
                composer,
                bid.stealth_address().pk_r().as_ref().into(),
            ),
            Point::from_private_affine(
                composer,
                bid.stealth_address().R().into(),
            ),
        );
        let bid_eligibility_ts = AllocatedScalar::allocate(
            composer,
            BlsScalar::from(bid.eligibility()),
        );
        let bid_expiration = AllocatedScalar::allocate(
            composer,
            BlsScalar::from(bid.expiration()),
        );
        let pos =
            AllocatedScalar::allocate(composer, BlsScalar::from(bid.pos()));
        // Allocate bid-needed inputs
        let secret_k = AllocatedScalar::allocate(composer, secret_k);
        let seed = AllocatedScalar::allocate(composer, seed);
        let latest_consensus_step =
            AllocatedScalar::allocate(composer, latest_consensus_step);
        let latest_consensus_round =
            AllocatedScalar::allocate(composer, latest_consensus_round);
        // Decrypt the cypher using the secret and allocate value & blinder.
        // If the decryption fails, we just set the result to an
        // impossible-to-obtain value.
        // On that way, verifiers do not get stuck on the process (they don't
        // care) about the real values here (just about filling the
        // composer). And provers won't get any info about if this
        // secret can or not decrypt the cipher.
        let (value, blinder) = bid
            .decrypt_data(&secret)
            .unwrap_or((JubJubScalar::one(), JubJubScalar::one()));
        let bid_value = AllocatedScalar::allocate(composer, value.into());
        let bid_blinder = AllocatedScalar::allocate(composer, blinder.into());
        // Allocate the bid tree root to be used later by the score_generation
        // gadget.
        let bid_tree_root =
            AllocatedScalar::allocate(composer, branch.root().clone());

        // ------------------------------------------------------- //
        //                                                         //
        //                     BlindBid Circuit                    //
        //                                                         //
        // ------------------------------------------------------- //

        // 1. Merkle Opening
        let root = merkle_opening_gadget(composer, branch, bid_hash.var);
        // Add PI constraint for the root to the PI constructor
        pi.push(PublicInput::BlsScalar(
            -branch.root(),
            composer.circuit_size(),
        ));

        // Constraint the bid_tree_root against a PI that represents
        // the root of the Bid tree that lives inside of the `Bid` contract.
        composer.constrain_to_constant(root, BlsScalar::zero(), -branch.root());

        // 2. Bid pre_image check
        let computed_bid_hash = preimage_gadget(
            composer,
            bid_cipher,
            bid_commitment,
            bid_stealth_addr,
            bid_hashed_secret.var,
            bid_eligibility_ts.var,
            bid_expiration.var,
            pos.var,
        );

        // Add PI constraint for bid preimage check.
        pi.push(PublicInput::BlsScalar(
            -bid_hash.scalar,
            composer.circuit_size(),
        ));
        // Constraint the hash to be equal to the real one
        composer.constrain_to_constant(
            computed_bid_hash,
            BlsScalar::zero(),
            -bid_hash.scalar,
        );

        // 3. t_a >= k_t
        // k_t - t_a should be > 2^64 which is the max size of the round.
        let kt_min_ta = composer.add(
            (BlsScalar::one(), latest_consensus_round.var),
            (-BlsScalar::one(), bid_eligibility_ts.var),
            BlsScalar::zero(),
            BlsScalar::zero(),
        );
        let kt_min_ta_scalar =
            latest_consensus_round.scalar - bid_eligibility_ts.scalar;
        let kt_min_ta = AllocatedScalar {
            scalar: kt_min_ta_scalar,
            var: kt_min_ta,
        };
        // Third cond should be one since the range should fail since the op
        // should be < 0 and therefore become really big.
        let third_cond = max_bound(
            composer,
            BlsScalar::from(2u64).pow(&[64, 0, 0, 0]),
            kt_min_ta,
        )
        .0;
        // Constraint third condition to be one.
        // So basically, that the rangeproof does not hold.
        composer.constrain_to_constant(
            third_cond,
            BlsScalar::one(),
            BlsScalar::zero(),
        );

        // 4. t_e >= k_t
        // k_t - t_e should be > 2^64 which is the max size of the round.
        let kt_min_te = composer.add(
            (BlsScalar::one(), latest_consensus_round.var),
            (-BlsScalar::one(), bid_expiration.var),
            BlsScalar::zero(),
            BlsScalar::zero(),
        );
        let kt_min_te_scalar =
            latest_consensus_round.scalar - bid_expiration.scalar;
        let kt_min_te = AllocatedScalar {
            scalar: kt_min_te_scalar,
            var: kt_min_te,
        };
        // Third cond should be one since the range should fail since the op
        // should be < 0 and therefore become really big.
        let fourth_cond = max_bound(
            composer,
            BlsScalar::from(2u64).pow(&[64, 0, 0, 0]),
            kt_min_te,
        )
        .0;
        // Constraint third condition to be one.
        // So basically, that the rangeproof does not hold.
        composer.constrain_to_constant(
            fourth_cond,
            BlsScalar::one(),
            BlsScalar::zero(),
        );

        // 5. c = C(v, b) Pedersen Commitment check
        let p1 = scalar_mul(composer, bid_value.var, GENERATOR_EXTENDED);
        let p2 = scalar_mul(composer, bid_blinder.var, GENERATOR_NUMS_EXTENDED);
        let computed_c = p1.point().fast_add(composer, *p2.point());
        // Add PI constraint for the commitment computation check.
        pi.push(PublicInput::AffinePoint(
            bid.commitment(),
            composer.circuit_size(),
            composer.circuit_size() + 1,
        ));

        // Assert computed_commitment == announced commitment.
        composer.assert_equal_public_point(computed_c, bid.commitment());

        // 6. 0 < value <= 2^64 range check
        // v < 2^64
        composer.range_gate(bid_value.var, 64usize);

        // 7. `m = H(k)` Secret key pre-image check.
        let secret_k_hash = sponge::gadget(composer, &[secret_k.var]);
        // Add PI constraint for the secret_k_hash.
        pi.push(PublicInput::BlsScalar(
            -bid.hashed_secret(),
            composer.circuit_size(),
        ));

        // Constraint the secret_k_hash to be equal to the publicly avaliable
        // one.
        composer.constrain_to_constant(
            secret_k_hash,
            BlsScalar::zero(),
            -bid.hashed_secret(),
        );

        // We generate the prover_id and constrain it to a public input
        // On that way we bind the Score to the correct id.
        // 8. `prover_id = H(secret_k, sigma^s, k^t, k^s)`. Preimage check
        let prover_id = sponge::gadget(
            composer,
            &[
                secret_k.var,
                seed.var,
                latest_consensus_round.var,
                latest_consensus_step.var,
            ],
        );

        // Constraint the prover_id to be the public one and set it in the PI
        // constructor.
        pi.push(PublicInput::BlsScalar(
            -bid.generate_prover_id(
                secret_k.scalar,
                seed.scalar,
                latest_consensus_round.scalar,
                latest_consensus_step.scalar,
            ),
            composer.circuit_size(),
        ));
        composer.constrain_to_constant(
            prover_id,
            BlsScalar::zero(),
            -bid.generate_prover_id(
                secret_k.scalar,
                seed.scalar,
                latest_consensus_round.scalar,
                latest_consensus_step.scalar,
            ),
        );

        // 9. Score generation circuit check with the corresponding gadget.
        let computed_score = score.prove_correct_score_gadget(
            composer,
            bid_value,
            secret_k,
            bid_tree_root,
            seed,
            latest_consensus_round,
            latest_consensus_step,
        );

        // Constraint the score to be the public one and set it in the PI
        // constructor.
        pi.push(PublicInput::BlsScalar(
            -score.value(),
            composer.circuit_size(),
        ));
        composer.constrain_to_constant(
            computed_score,
            BlsScalar::zero(),
            -score.value(),
        );
        Ok(())
    }

    fn get_pi_positions(&self) -> &Vec<PublicInput> {
        &self.pi_positions
    }

    fn get_mut_pi_positions(&mut self) -> &mut Vec<PublicInput> {
        &mut self.pi_positions
    }

    fn get_trim_size(&self) -> usize {
        self.trim_size
    }

    fn set_trim_size(&mut self, size: usize) {
        self.trim_size = size;
    }
}
