// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2014 The Bitcoin Core developers
// Copyright (c) 2016-2024 The Hush developers
// Distributed under the GPLv3 software license, see the accompanying
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html

/******************************************************************************
 * Copyright © 2014-2019 The SuperNET Developers.                             *
 *                                                                            *
 * See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at                  *
 * the top-level directory of this distribution for the individual copyright  *
 * holder information and the developer policies on copyright and licensing.  *
 *                                                                            *
 * Unless otherwise agreed in a custom licensing agreement, no part of the    *
 * SuperNET software, including this file may be copied, modified, propagated *
 * or distributed except according to the terms contained in the LICENSE file *
 *                                                                            *
 * Removal or modification of this copyright notice is prohibited.            *
 *                                                                            *
 ******************************************************************************/

#include "script/sign.h"

#include "primitives/transaction.h"
#include "key.h"
#include "keystore.h"
#include "script/standard.h"
#include "uint256.h"
#include "cc/CCinclude.h"
#include "cc/eval.h"
#include "key_io.h"

#include <boost/foreach.hpp>

using namespace std;

typedef vector<unsigned char> valtype;
extern uint8_t ASSETCHAINS_TXPOW;
extern char NSPV_wifstr[],NSPV_pubkeystr[];
extern int32_t HUSH_NSPV;
#ifndef HUSH_NSPV_FULLNODE
#define HUSH_NSPV_FULLNODE (HUSH_NSPV <= 0)
#endif // !HUSH_NSPV_FULLNODE
#ifndef HUSH_NSPV_SUPERLITE
#define HUSH_NSPV_SUPERLITE (HUSH_NSPV > 0)
#endif // !HUSH_NSPV_SUPERLITE

uint256 SIG_TXHASH;

TransactionSignatureCreator::TransactionSignatureCreator(const CKeyStore* keystoreIn, const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : BaseSignatureCreator(keystoreIn), txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}

bool TransactionSignatureCreator::CreateSig(std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, uint32_t consensusBranchId, CKey *pprivKey, void *extraData) const
{
    CKey key; uint256 hash;
    try {
        hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, consensusBranchId);
    } catch (logic_error ex) {
        {
            fprintf(stderr,"logic error\n");
        return false;
        }
    }
    SIG_TXHASH = hash;
    if ( HUSH_NSPV_SUPERLITE )
        key = DecodeSecret(NSPV_wifstr);
    else if (pprivKey)
        key = *pprivKey;
    else if (!keystore || !keystore->GetKey(address, key))
    {
        fprintf(stderr,"keystore.%p error\n",keystore);
        return false;
    }
    //fprintf(stderr,"privkey (%s) for %s\n",NSPV_wifstr,EncodeDestination(key.GetPubKey().GetID()).c_str());

    if (0) // scriptCode.IsPayToCryptoCondition())
    {
    }
    else
    {
        if ( ASSETCHAINS_TXPOW == 0 )
        {
            if (!key.Sign(hash, vchSig))
            {
                fprintf(stderr,"key.Sign error\n");
                return false;
            }
            else
            {
                //fprintf(stderr,"signed success %s\n",(char *)HexStr(vchSig).c_str());
            }
        }
        else
        {
            if (!key.Sign(hash, vchSig, rand()))
                return false;
        }
    }
    
    vchSig.push_back((unsigned char)nHashType);
    if ( HUSH_NSPV_SUPERLITE )
        memset((uint8_t *)key.begin(),0,32);
    return true;
}

static bool Sign1(const CKeyID& address, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector<valtype>& ret, uint32_t consensusBranchId)
{
    vector<unsigned char> vchSig;
    if (!creator.CreateSig(vchSig, address, scriptCode, consensusBranchId))
    {
        fprintf(stderr,"Sign1 creatsig error\n");
        return false;
    }
    ret.push_back(vchSig);
    return true;
}

static bool SignN(const vector<valtype>& multisigdata, const BaseSignatureCreator& creator, const CScript& scriptCode, std::vector<valtype>& ret, uint32_t consensusBranchId)
{
    int nSigned = 0;
    int nRequired = multisigdata.front()[0];
    for (unsigned int i = 1; i < multisigdata.size()-1 && nSigned < nRequired; i++)
    {
        const valtype& pubkey = multisigdata[i];
        CKeyID keyID = CPubKey(pubkey).GetID();
        if (Sign1(keyID, creator, scriptCode, ret, consensusBranchId))
            ++nSigned;
    }
    return nSigned==nRequired;
}

bool _Getscriptaddress(char *destaddr, const CScript &scriptPubKey)
{
    CTxDestination address;
    txnouttype whichType;
    std::vector<std::vector<unsigned char>> vvch = std::vector<std::vector<unsigned char>>();
    if (Solver(scriptPubKey, whichType, vvch) && vvch[0].size() == 20)
    {
        address = CKeyID(uint160(vvch[0]));
        strcpy(destaddr,(char *)CBitcoinAddress(address).ToString().c_str());
        return(true);
    }
    fprintf(stderr,"Solver for scriptPubKey failed\n%s\n", scriptPubKey.ToString().c_str());
    return(false);
}

static bool SignStepCC(const BaseSignatureCreator& creator, const CScript& scriptPubKey, vector<valtype> &vSolutions,
                       vector<valtype>& ret, uint32_t consensusBranchId)
{
    return false;
}

/**
 * Sign scriptPubKey using signature made with creator.
 * Signatures are returned in scriptSigRet (or returns false if scriptPubKey can't be signed),
 * unless whichTypeRet is TX_SCRIPTHASH, in which case scriptSigRet is the redemption script.
 * Returns false if scriptPubKey could not be completely satisfied.
 */
static bool SignStep(const BaseSignatureCreator& creator, const CScript& scriptPubKey,
                     std::vector<valtype>& ret, txnouttype& whichTypeRet, uint32_t consensusBranchId)
{
    CScript scriptRet;
    uint160 h160;
    ret.clear();
    
    vector<valtype> vSolutions;
    
    if (!Solver(scriptPubKey, whichTypeRet, vSolutions))
    {
        // if this is a CLTV script, solve for the destination after CLTV
        if (scriptPubKey.IsCheckLockTimeVerify())
        {
            uint8_t pushOp = scriptPubKey[0];
            uint32_t scriptStart = pushOp + 3;
            
            // check post CLTV script
            CScript postfix = CScript(scriptPubKey.size() > scriptStart ? scriptPubKey.begin() + scriptStart : scriptPubKey.end(), scriptPubKey.end());
            
            // check again with only postfix subscript
            if (!Solver(postfix, whichTypeRet, vSolutions))
                return false;
        }
        else
            return false;
    }
    
    CKeyID keyID;
    switch (whichTypeRet)
    {
        case TX_NONSTANDARD:
        case TX_NULL_DATA:
            return false;
        case TX_PUBKEY:
            keyID = CPubKey(vSolutions[0]).GetID();
            return Sign1(keyID, creator, scriptPubKey, ret, consensusBranchId);
        case TX_PUBKEYHASH:
            keyID = CKeyID(uint160(vSolutions[0]));
            if (!Sign1(keyID, creator, scriptPubKey, ret, consensusBranchId))
            {
                fprintf(stderr,"sign1 error\n");
                return false;
            }
            else
            {
                if ( HUSH_NSPV_FULLNODE )
                {
                    CPubKey vch;
                    creator.KeyStore().GetPubKey(keyID, vch);
                    ret.push_back(ToByteVector(vch));
                } else ret.push_back(ParseHex(NSPV_pubkeystr));
            }
            return true;
        case TX_SCRIPTHASH:
            if (creator.KeyStore().GetCScript(uint160(vSolutions[0]), scriptRet)) {
                ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
                return true;
            }
            return false;
            
        case TX_MULTISIG:
            ret.push_back(valtype()); // workaround CHECKMULTISIG bug
            return (SignN(vSolutions, creator, scriptPubKey, ret, consensusBranchId));
            
        default:
            return false;
    }
}

static CScript PushAll(const vector<valtype>& values)
{
    CScript result;
    BOOST_FOREACH(const valtype& v, values) {
        if (v.size() == 0) {
            result << OP_0;
        } else if (v.size() == 1 && v[0] >= 1 && v[0] <= 16) {
            result << CScript::EncodeOP_N(v[0]);
        } else {
            result << v;
        }
    }
    return result;
}

bool ProduceSignature(const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata, uint32_t consensusBranchId)
{
    CScript script = fromPubKey;
    bool solved = true;
    std::vector<valtype> result;
    txnouttype whichType;
    solved = SignStep(creator, script, result, whichType, consensusBranchId);
    CScript subscript;
    
    if (solved && whichType == TX_SCRIPTHASH)
    {
        // Solver returns the subscript that needs to be evaluated;
        // the final scriptSig is the signatures from that
        // and then the serialized subscript:
        script = subscript = CScript(result[0].begin(), result[0].end());
        solved = solved && SignStep(creator, script, result, whichType, consensusBranchId) && whichType != TX_SCRIPTHASH;
        result.push_back(std::vector<unsigned char>(subscript.begin(), subscript.end()));
    }
    
    sigdata.scriptSig = PushAll(result);
    // Test solution
    return solved && VerifyScript(sigdata.scriptSig, fromPubKey, STANDARD_SCRIPT_VERIFY_FLAGS, creator.Checker(), consensusBranchId);
}

SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn)
{
    SignatureData data;
    assert(tx.vin.size() > nIn);
    data.scriptSig = tx.vin[nIn].scriptSig;
    return data;
}

void UpdateTransaction(CMutableTransaction& tx, unsigned int nIn, const SignatureData& data)
{
    assert(tx.vin.size() > nIn);
    tx.vin[nIn].scriptSig = data.scriptSig;
}

bool SignSignature(
                   const CKeyStore &keystore,
                   const CScript& fromPubKey,
                   CMutableTransaction& txTo,
                   unsigned int nIn,
                   const CAmount& amount,
                   int nHashType,
                   uint32_t consensusBranchId)
{
    assert(nIn < txTo.vin.size());
    
    CTransaction txToConst(txTo);
    TransactionSignatureCreator creator(&keystore, &txToConst, nIn, amount, nHashType);
    
    SignatureData sigdata;
    bool ret = ProduceSignature(creator, fromPubKey, sigdata, consensusBranchId);
    UpdateTransaction(txTo, nIn, sigdata);
    return ret;
}

bool SignSignature(
                   const CKeyStore &keystore,
                   const CTransaction& txFrom,
                   CMutableTransaction& txTo,
                   unsigned int nIn,
                   int nHashType,
                   uint32_t consensusBranchId)
{
    assert(nIn < txTo.vin.size());
    CTxIn& txin = txTo.vin[nIn];
    assert(txin.prevout.n < txFrom.vout.size());
    const CTxOut& txout = txFrom.vout[txin.prevout.n];
    
    return SignSignature(keystore, txout.scriptPubKey, txTo, nIn, txout.nValue, nHashType, consensusBranchId);
}

static vector<valtype> CombineMultisig(const CScript& scriptPubKey, const BaseSignatureChecker& checker,
                                       const vector<valtype>& vSolutions,
                                       const vector<valtype>& sigs1, const vector<valtype>& sigs2, uint32_t consensusBranchId)
{
    // Combine all the signatures we've got:
    set<valtype> allsigs;
    BOOST_FOREACH(const valtype& v, sigs1)
    {
        if (!v.empty())
            allsigs.insert(v);
    }
    BOOST_FOREACH(const valtype& v, sigs2)
    {
        if (!v.empty())
            allsigs.insert(v);
    }
    
    // Build a map of pubkey -> signature by matching sigs to pubkeys:
    assert(vSolutions.size() > 1);
    unsigned int nSigsRequired = vSolutions.front()[0];
    unsigned int nPubKeys = vSolutions.size()-2;
    map<valtype, valtype> sigs;
    BOOST_FOREACH(const valtype& sig, allsigs)
    {
        for (unsigned int i = 0; i < nPubKeys; i++)
        {
            const valtype& pubkey = vSolutions[i+1];
            if (sigs.count(pubkey))
                continue; // Already got a sig for this pubkey
            
            if (checker.CheckSig(sig, pubkey, scriptPubKey, consensusBranchId))
            {
                sigs[pubkey] = sig;
                break;
            }
        }
    }
    // Now build a merged CScript:
    unsigned int nSigsHave = 0;
    std::vector<valtype> result; result.push_back(valtype()); // pop-one-too-many workaround
    for (unsigned int i = 0; i < nPubKeys && nSigsHave < nSigsRequired; i++)
    {
        if (sigs.count(vSolutions[i+1]))
        {
            result.push_back(sigs[vSolutions[i+1]]);
            ++nSigsHave;
        }
    }
    // Fill any missing with OP_0:
    for (unsigned int i = nSigsHave; i < nSigsRequired; i++)
        result.push_back(valtype());
    
    return result;
}

namespace
{
    struct Stacks
    {
        std::vector<valtype> script;
        
        Stacks() {}
        explicit Stacks(const std::vector<valtype>& scriptSigStack_) : script(scriptSigStack_) {}
        explicit Stacks(const SignatureData& data, uint32_t consensusBranchId) {
            EvalScript(script, data.scriptSig, SCRIPT_VERIFY_STRICTENC, BaseSignatureChecker(), consensusBranchId);
        }
        
        SignatureData Output() const {
            SignatureData result;
            result.scriptSig = PushAll(script);
            return result;
        }
    };
}

static Stacks CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker,
                                const txnouttype txType, const vector<valtype>& vSolutions,
                                Stacks sigs1, Stacks sigs2, uint32_t consensusBranchId)
{
    switch (txType)
    {
        case TX_NONSTANDARD:
        case TX_NULL_DATA:
            // Don't know anything about this, assume bigger one is correct:
            if (sigs1.script.size() >= sigs2.script.size())
                return sigs1;
            return sigs2;
        case TX_PUBKEY:
        case TX_PUBKEYHASH:
        case TX_SCRIPTHASH:
            if (sigs1.script.empty() || sigs1.script.back().empty())
                return sigs2;
            else if (sigs2.script.empty() || sigs2.script.back().empty())
                return sigs1;
            else
            {
                // Recur to combine:
                valtype spk = sigs1.script.back();
                CScript pubKey2(spk.begin(), spk.end());
                
                txnouttype txType2;
                vector<vector<unsigned char> > vSolutions2;
                Solver(pubKey2, txType2, vSolutions2);
                sigs1.script.pop_back();
                sigs2.script.pop_back();
                Stacks result = CombineSignatures(pubKey2, checker, txType2, vSolutions2, sigs1, sigs2, consensusBranchId);
                result.script.push_back(spk);
                return result;
            }
        case TX_MULTISIG:
            return Stacks(CombineMultisig(scriptPubKey, checker, vSolutions, sigs1.script, sigs2.script, consensusBranchId));
        default:
            return Stacks();
    }
}

SignatureData CombineSignatures(const CScript& scriptPubKey, const BaseSignatureChecker& checker,
                                const SignatureData& scriptSig1, const SignatureData& scriptSig2,
                                uint32_t consensusBranchId)
{
    txnouttype txType;
    vector<vector<unsigned char> > vSolutions;
    Solver(scriptPubKey, txType, vSolutions);
    
    return CombineSignatures(
                             scriptPubKey, checker, txType, vSolutions,
                             Stacks(scriptSig1, consensusBranchId),
                             Stacks(scriptSig2, consensusBranchId),
                             consensusBranchId).Output();
}

namespace {
    /** Dummy signature checker which accepts all signatures. */
    class DummySignatureChecker : public BaseSignatureChecker
    {
    public:
        DummySignatureChecker() {}
        
        bool CheckSig(
                      const std::vector<unsigned char>& scriptSig,
                      const std::vector<unsigned char>& vchPubKey,
                      const CScript& scriptCode,
                      uint32_t consensusBranchId) const
        {
            return true;
        }
    };
    const DummySignatureChecker dummyChecker;
}

const BaseSignatureChecker& DummySignatureCreator::Checker() const
{
    return dummyChecker;
}

bool DummySignatureCreator::CreateSig(
                                      std::vector<unsigned char>& vchSig,
                                      const CKeyID& keyid,
                                      const CScript& scriptCode,
                                      uint32_t consensusBranchId, 
                                      CKey *key,
                                      void *extraData) const
{
    // Create a dummy signature that is a valid DER-encoding
    vchSig.assign(72, '\000');
    vchSig[0] = 0x30;
    vchSig[1] = 69;
    vchSig[2] = 0x02;
    vchSig[3] = 33;
    vchSig[4] = 0x01;
    vchSig[4 + 33] = 0x02;
    vchSig[5 + 33] = 32;
    vchSig[6 + 33] = 0x01;
    vchSig[6 + 33 + 32] = SIGHASH_ALL;
    return true;
}

