/**
* @file       tutorial.cpp
*
* @brief      Simple tutorial program to illustrate Zerocoin usage.
*
* @author     Ian Miers, Christina Garman and Matthew Green
* @date       June 2013
*
* @copyright  Copyright 2013 Ian Miers, Christina Garman and Matthew Green
* @license    This project is released under the MIT license.
**/
// Copyright (c) 2017-2018 The PIVX developers
// Copyright (c) 2019 The CryptoDev developers
// Copyright (c) 2019 The Flits developers

#include <boost/test/unit_test.hpp>
#include <string>
#include <iostream>
#include <fstream>
// #include <curses.h>
#include <exception>
#include "streams.h"
#include "libzerocoin/bignum.h"
#include "libzerocoin/ParamGeneration.h"
#include "libzerocoin/Denominations.h"
#include "libzerocoin/Coin.h"
#include "libzerocoin/CoinSpend.h"
#include "libzerocoin/Accumulator.h"
#include "test_fls.h"


#define COINS_TO_ACCUMULATE     5
#define DUMMY_TRANSACTION_HASH  0 // in real life these would be uint256 hashes
#define DUMMY_ACCUMULATOR_ID    0 // in real life these would be uint256 hashes

//
// We generated this for testing only. Don't use it in production!
//
#define TUTORIAL_TEST_MODULUS   "a8852ebf7c49f01cd196e35394f3b74dd86283a07f57e0a262928e7493d4a3961d93d93c90ea3369719641d626d28b9cddc6d9307b9aabdbffc40b6d6da2e329d079b4187ff784b2893d9f53e9ab913a04ff02668114695b07d8ce877c4c8cac1b12b9beff3c51294ebe349eca41c24cd32a6d09dd1579d3947e5c4dcc30b2090b0454edb98c6336e7571db09e0fdafbd68d8f0470223836e90666a5b143b73b9cd71547c917bf24c0efc86af2eba046ed781d9acb05c80f007ef5a0a5dfca23236f37e698e8728def12554bc80f294f71c040a88eff144d130b24211016a97ce0f5fe520f477e555c9997683d762aff8bd1402ae6938dd5c994780b1bf6aa7239e9d8101630ecfeaa730d2bbc97d39beb057f016db2e28bf12fab4989c0170c2593383fd04660b5229adcd8486ba78f6cc1b558bcd92f344100dff239a8c00dbc4c2825277f24bdd04475bcc9a8c39fd895eff97c1967e434effcb9bd394e0577f4cf98c30d9e6b54cd47d6e447dcf34d67e48e4421691dbe4a7d9bd503abb9"

//
// The following routine exercises most of the core functions of
// the library. We've commented it as well as possible to give
// you the flavor of how libzerocoin works.
//
// For details on Zerocoin integration, see the libzerocoin wiki
// at: https://github.com/Zerocoin/libzerocoin/wiki
//

bool
ZerocoinTutorial()
{
    // The following simple code illustrates the call flow for Zerocoin
    // applications. In a real currency network these operations would
    // be split between individual payers/payees, network nodes and miners.
    //
    // For each call we specify the participant who would use it.

    // Zerocoin uses exceptions (based on the std::runtime_error class)
    // to indicate all sorts of problems. Always remember to catch them!

    try {

        /********************************************************************/
        // What is it:      Parameter loading
        // Who does it:     ALL ZEROCOIN PARTICIPANTS
        // What it does:    Loads a trusted Zerocoin modulus "N" and
        //                  generates all associated parameters from it.
        //                  We use a hardcoded "N" that we generated using
        //                  the included 'paramgen' utility.
        /********************************************************************/

        // Load a test modulus from our hardcoded string (above)
        CBigNum testModulus;
        testModulus.SetHex(std::string(TUTORIAL_TEST_MODULUS));

        // Set up the Zerocoin Params object
        libzerocoin::ZerocoinParams* params = new libzerocoin::ZerocoinParams(testModulus);

        std::cout << "Successfully loaded parameters." << std::endl;

        /********************************************************************/
        // What is it:      Coin generation
        // Who does it:     ZEROCOIN CLIENTS
        // What it does:    Generates a new 'zerocoin' coin using the
        //                  public parameters. Once generated, the client
        //                  will transmit the public portion of this coin
        //                  in a ZEROCOIN_MINT transaction. The inputs
        //                  to this transaction must add up to the zerocoin
        //                  denomination plus any transaction fees.
        /********************************************************************/

        // The following constructor does all the work of minting a brand
        // new zerocoin. It stores all the private values inside the
        // PrivateCoin object. This includes the coin secrets, which must be
        // stored in a secure location (wallet) at the client.
        libzerocoin::PrivateCoin newCoin(params, libzerocoin::CoinDenomination::ZQ_ONE);

        // Get a copy of the 'public' portion of the coin. You should
        // embed this into a Zerocoin 'MINT' transaction along with a series
        // of currency inputs totaling the assigned value of one zerocoin.
        libzerocoin::PublicCoin pubCoin = newCoin.getPublicCoin();

        std::cout << "Successfully minted a zerocoin." << std::endl;

        // Serialize the public coin to a CDataStream object.
        CDataStream serializedCoin(SER_NETWORK, PROTOCOL_VERSION);
        serializedCoin << pubCoin;

        /********************************************************************/
        // What is it:      Coin verification
        // Who does it:     TRANSACTION VERIFIERS
        // What it does:    Verifies the structure of a zerocoin obtained from
        //                  a ZEROCOIN_MINT transaction. All coins must be
        //                  verified before you operate on them.
        //                  Note that this is only part of the transaction
        //                  verification process! The client must also check
        //                  that (1) the inputs to the transaction are valid
        //                  and add up to the value of one zerocoin, (2) that
        //                  this particular zerocoin has not been minted before.
        /********************************************************************/

        // Deserialize the public coin into a fresh object.
        libzerocoin::PublicCoin pubCoinNew(params, serializedCoin);

        // Now make sure the coin is valid.
        if (!pubCoinNew.validate()) {
            // If this returns false, don't accept the coin for any purpose!
            // Any ZEROCOIN_MINT with an invalid coin should NOT be
            // accepted as a valid transaction in the block chain.
            std::cout << "Error: coin is not valid!";
        }

        std::cout << "Deserialized and verified the coin." << std::endl;

        /********************************************************************/
        // What is it:      libzerocoin::Accumulator computation
        // Who does it:     ZEROCOIN CLIENTS & TRANSACTION VERIFIERS
        // What it does:    Collects a number of PublicCoin values drawn from
        //                  the block chain and calculates an accumulator.
        //                  This accumulator is incrementally computable;
        //                  you can stop and serialize it at any point
        //                  then continue accumulating new transactions.
        //                  The accumulator is also order-independent, so
        //                  the same coins can be accumulated in any order
        //                  to give the same result.
        //                  WARNING: do not accumulate the same coin twice!
        /********************************************************************/

        // Create an empty accumulator object
        libzerocoin::Accumulator accumulator(params,libzerocoin::CoinDenomination::ZQ_ONE);

        // Add several coins to it (we'll generate them here on the fly).
        for (uint32_t i = 0; i < COINS_TO_ACCUMULATE; i++) {
            libzerocoin::PrivateCoin testCoin(params, libzerocoin::CoinDenomination::ZQ_ONE);
            accumulator += testCoin.getPublicCoin();
        }

        // Serialize the accumulator object.
        //
        // If you're using Accumulator Checkpoints, each miner would
        // start by deserializing the accumulator checkpoint from the
        // previous block (or creating a new Accumulator if no previous
        // block exists). It will then add all the coins in the new block,
        // then serialize the resulting Accumulator object to obtain the
        // new checkpoint. All block verifiers should do the same thing
        // to check their work.
        CDataStream serializedAccumulator(SER_NETWORK, PROTOCOL_VERSION);
        serializedAccumulator << accumulator;

        // Deserialize the accumulator object
        libzerocoin::Accumulator newAccumulator(params, serializedAccumulator);

        // We can now continue accumulating things into the accumulator
        // we just deserialized. For example, let's put in the coin
        // we generated up above.
        newAccumulator += pubCoinNew;

        std::cout << "Successfully accumulated coins." << std::endl;

        /********************************************************************/
        // What is it:      Coin spend
        // Who does it:     ZEROCOIN CLIENTS
        // What it does:    Create a new transaction that spends a Zerocoin.
        //                  The user first authors a transaction specifying
        //                  a set of destination addresses (outputs). They
        //                  next compute an Accumulator over all coins in the
        //                  block chain (see above) and a Witness based on the
        //                  coin to be spent. Finally they instantiate a CoinSpend
        //                  object that 'signs' the transaction with a special
        //                  zero knowledge signature of knowledge over the coin
        //                  data, Witness and Accumulator.
        /********************************************************************/

        // We are going to spend the coin "newCoin" that we generated at the
        // top of this function.
        //
        // We'll use the accumulator we constructed above. This contains
        // a set of coins, but does NOT include the coin "newCoin".
        //
        // To generate the witness, we start with this accumulator and
        // add the public half of the coin we want to spend.
        libzerocoin::AccumulatorWitness witness(params, accumulator, newCoin.getPublicCoin());

        // Add the public half of "newCoin" to the Accumulator itself.
        accumulator += newCoin.getPublicCoin();

        // At this point we should generate a ZEROCOIN_SPEND transaction to
        // send to the network. This network should include a set of outputs
        // totalling to the value of one zerocoin (minus transaction fees).

        // Construct the CoinSpend object. This acts like a signature on the
        // transaction.
        libzerocoin::CoinSpend spend(params, params, newCoin, accumulator, 0, witness, 0, libzerocoin::SpendType::SPEND);//(0) - Presstab

        // This is a sanity check. The CoinSpend object should always verify,
        // but why not check before we put it onto the wire?
        if (!spend.Verify(accumulator)) {
            std::cout << "ERROR: Our new CoinSpend transaction did not verify!" << std::endl;
            return false;
        }

        // Serialize the CoinSpend object into a buffer.
        CDataStream serializedCoinSpend(SER_NETWORK, PROTOCOL_VERSION);
        serializedCoinSpend << spend;

        std::cout << "Successfully generated a coin spend transaction." << std::endl;

        /********************************************************************/
        // What is it:      Coin spend verification
        // Who does it:     ALL PARTIES
        // What it does:    Verifies that a CoinSpend signature is correct
        //                  with respect to a ZEROCOIN_SPEND transaction hash.
        //                  The client must also extract the serial number from
        //                  the CoinSpend and verify that this serial number has
        //                  not previously appeared in another ZEROCOIN_SPEND
        //                  transaction.
        /********************************************************************/

        // Deserialize the CoinSpend intro a fresh object
        libzerocoin::CoinSpend newSpend(params, params, serializedCoinSpend);

        // Create a new metadata object to contain the hash of the received
        // ZEROCOIN_SPEND transaction. If we were a real client we'd actually
        // compute the hash of the received transaction here.

        // If we were a real client we would now re-compute the Accumulator
        // from the information given in the ZEROCOIN_SPEND transaction.
        // For our purposes we'll just use the one we calculated above.
        //
        // Verify that the spend is valid with respect to the Accumulator
        // and the Metadata
        if (!newSpend.Verify(accumulator)) {
            std::cout << "ERROR: The CoinSpend transaction did not verify!" << std::endl;
            return false;
        }

        // Pull the serial number out of the CoinSpend object. If we
        // were a real Zerocoin client we would now check that the serial number
        // has not been spent before (in another ZEROCOIN_SPEND) transaction.
        // The serial number is stored as a CBigNum.
        CBigNum serialNumber = newSpend.getCoinSerialNumber();

        std::cout << "Successfully verified a coin spend transaction." << std::endl;
        std::cout << std::endl << "Coin serial number is:" << std::endl << serialNumber << std::endl;

        // We're done
        return true;

    } catch (std::runtime_error &e) {
        std::cout << e.what() << std::endl;
        return false;
    }

    return false;
}

BOOST_FIXTURE_TEST_SUITE(tutorial_libzerocoin, TestingSetup)
BOOST_AUTO_TEST_CASE(tutorial_libzerocoin_tests)
{
    std::cout << "libzerocoin v" << ZEROCOIN_VERSION_STRING << " tutorial." << std::endl << std::endl;

    ZerocoinTutorial();
}
BOOST_AUTO_TEST_SUITE_END()
