// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2017-2019 The WaykiChain Developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef WALLET_CRYPTER_H
#define WALLET_CRYPTER_H

#include "commons/allocators.h"
#include "commons/serialize.h"
#include "entities/keystore.h"

class uint256;

const uint32_t WALLET_CRYPTO_KEY_SIZE  = 32;
const uint32_t WALLET_CRYPTO_SALT_SIZE = 8;

/*
Private key encryption is done based on a CMasterKey,
which holds a salt and random encryption key.

CMasterKeys are encrypted using AES-256-CBC using a key
derived using derivation method nDerivationMethod
(0 == EVP_sha512()) and derivation iterations nDeriveIterations.
vchOtherDerivationParameters is provided for alternative algorithms
which may require more parameters (such as scrypt).

Wallet Private Keys are then encrypted using AES-256-CBC
with the double-sha256 of the public key as the IV, and the
master key's key as the encryption key (see keystore.[ch]).
*/

/** Master key for wallet encryption */
class CMasterKey {
public:
    vector<unsigned char> vchCryptedKey;
    vector<unsigned char> vchSalt;
    // 0 = EVP_sha512()
    // 1 = scrypt()
    uint32_t nDerivationMethod;
    uint32_t nDeriveIterations;
    // Use this for more parameters to key derivation,
    // such as the various parameters to scrypt
    vector<unsigned char> vchOtherDerivationParameters;

    IMPLEMENT_SERIALIZE(READWRITE(vchCryptedKey); READWRITE(vchSalt); READWRITE(nDerivationMethod);
                        READWRITE(nDeriveIterations); READWRITE(vchOtherDerivationParameters);)
    CMasterKey() {
        // 25000 rounds is just under 0.1 seconds on a 1.86 GHz Pentium M
        // ie slightly lower than the lowest hardware we need bother supporting
        nDeriveIterations            = 25000;
        nDerivationMethod            = 0;
        vchOtherDerivationParameters = vector<unsigned char>(0);
    }
    bool SetNull() {
        vchCryptedKey.clear();
        vchSalt.clear();
        vchOtherDerivationParameters.clear();
        nDeriveIterations = 25000;
        nDerivationMethod = 0;

        return true;
    }
};

typedef vector<unsigned char, secure_allocator<unsigned char> > CKeyingMaterial;

/** Encryption/decryption context with key information */
class CCrypter {
private:
    unsigned char chKey[WALLET_CRYPTO_KEY_SIZE];
    unsigned char chIV[WALLET_CRYPTO_KEY_SIZE];
    bool fKeySet;

public:
    bool SetKeyFromPassphrase(const SecureString& strKeyData, const vector<unsigned char>& chSalt,
                              const uint32_t nRounds, const uint32_t nDerivationMethod);
    bool Encrypt(const CKeyingMaterial& vchPlaintext, vector<unsigned char>& vchCiphertext);
    bool Decrypt(const vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext);
    bool SetKey(const CKeyingMaterial& chNewKey, const vector<unsigned char>& chNewIV);

    void CleanKey() {
        OPENSSL_cleanse(chKey, sizeof(chKey));
        OPENSSL_cleanse(chIV, sizeof(chIV));
        fKeySet = false;
    }

    CCrypter() {
        fKeySet = false;

        // Try to keep the key data out of swap (and be a bit over-careful to keep the IV that we don't even use out of
        // swap) Note that this does nothing about suspend-to-disk (which will put all our key data on disk) Note as
        // well that at no point in this program is any attempt made to prevent stealing of keys by reading the memory
        // of the running process.
        LockedPageManager::Instance().LockRange(&chKey[0], sizeof chKey);
        LockedPageManager::Instance().LockRange(&chIV[0], sizeof chIV);
    }

    ~CCrypter() {
        CleanKey();

        LockedPageManager::Instance().UnlockRange(&chKey[0], sizeof chKey);
        LockedPageManager::Instance().UnlockRange(&chIV[0], sizeof chIV);
    }
};

bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial& vchPlaintext, const uint256& nIV,
                   vector<unsigned char>& vchCiphertext);
bool DecryptSecret(const CKeyingMaterial& vMasterKey, const vector<unsigned char>& vchCiphertext, const uint256& nIV,
                   CKeyingMaterial& vchPlaintext);

/** Keystore which keeps the private keys encrypted.
 * It derives from the basic key store, which is used if no encryption is active.
 */
class CCryptoKeyStore : public CBasicKeyStore {
private:
    CryptedKeyMap mapCryptedKeys;

    CKeyingMaterial vMasterKey;

    // if fUseCrypto is true, mainKey in mapKeys must be empty
    // if fUseCrypto is false, vMasterKey must be empty
    bool fUseCrypto;

protected:
    bool SetCrypted();

    // will encrypt previously unencrypted keys
    bool EncryptKeys(CKeyingMaterial& vMasterKeyIn);

    bool Unlock(const CKeyingMaterial& vMasterKeyIn);

public:
    CCryptoKeyStore() : fUseCrypto(false) {}

    bool IsEncrypted() const { return fUseCrypto; }

    bool IsLocked() const {
        if (!IsEncrypted())
            return false;
        bool result;
        {
            LOCK(cs_KeyStore);
            result = vMasterKey.empty();
        }
        return result;
    }

    bool Lock();

    bool IsEmpty() { return mapCryptedKeys.empty() && mapKeys.empty(); }
    virtual bool AddCryptedKey(const CPubKey& vchPubKey, const vector<unsigned char>& vchCryptedSecret);
    bool AddKeyCombi(const CKeyID& keyId, const CKeyCombi& keyCombi);
    bool HaveKey(const CKeyID& address) const {
        {
            LOCK(cs_KeyStore);
            if (!IsEncrypted())
                return CBasicKeyStore::HaveKey(address);
            return mapCryptedKeys.count(address) > 0;
        }
        return false;
    }
    bool GetKey(const CKeyID& address, CKey& keyOut, bool IsMiner = false) const;

    bool GetPubKey(const CKeyID& address, CPubKey& vchPubKeyOut, bool IsMiner = false) const;

    void GetKeys(set<CKeyID>& setAddress, bool bFlag = false) const {
        if (!IsEncrypted()) {
            CBasicKeyStore::GetKeys(setAddress, bFlag);
            return;
        }

        setAddress.clear();
        CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
        while (mi != mapCryptedKeys.end()) {
            if (!bFlag)
                setAddress.insert((*mi).first);
            else {
                CKeyCombi keyCombi;
                if (GetKeyCombi((*mi).first, keyCombi)) {
                    if (keyCombi.HaveMinerKey() || keyCombi.HaveMainKey())  // only return satisfied mining address
                        setAddress.insert((*mi).first);
                }
            }
            mi++;
        }
    }

    bool GetKeyCombi(const CKeyID& address, CKeyCombi& keyCombiOut) const;
};

#endif  // WALLET_CRYPTER_H