// Copyright (c) 2009-2013 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 "crypter.h"
#include "script/script.h"
#include "script/standard.h"
#include "streams.h"
#include "util.h"
#include <string>
#include <vector>
#include <boost/foreach.hpp>
// Enable wolfSSL timing-resistant ECC and TFM (fastmath) code paths, which
// harden against timing side-channels. wolfSSL gates these purely with #ifdef,
// so the defined value is immaterial (any value enables the feature); we do not
// rely on 420 meaning anything.
#undef ECC_TIMING_RESISTANT
#undef TFM_TIMING_RESISTANT
#define ECC_TIMING_RESISTANT 420
#define TFM_TIMING_RESISTANT 420
#undef  WC_RSA_BLINDING
#define WC_RSA_BLINDING
#include <wolfssl/openssl/aes.h>
#include <wolfssl/openssl/evp.h>

using namespace libzcash;

bool CCrypter::SetKeyFromPassphrase(const SecureString& strKeyData, const std::vector<unsigned char>& chSalt, const unsigned int nRounds, const unsigned int nDerivationMethod)
{
    if (nRounds < 1 || chSalt.size() != WALLET_CRYPTO_SALT_SIZE)
        return false;

    int i = 0;
    if (nDerivationMethod == 0)
        i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha512(), &chSalt[0],
                          (unsigned char *)&strKeyData[0], strKeyData.size(), nRounds, chKey, chIV);

    if (i != (int)WALLET_CRYPTO_KEY_SIZE)
    {
        memory_cleanse(chKey, sizeof(chKey));
        memory_cleanse(chIV, sizeof(chIV));
        return false;
    }

    fKeySet = true;
    return true;
}

bool CCrypter::SetKey(const CKeyingMaterial& chNewKey, const std::vector<unsigned char>& chNewIV)
{
    if (chNewKey.size() != WALLET_CRYPTO_KEY_SIZE || chNewIV.size() != WALLET_CRYPTO_KEY_SIZE)
        return false;

    memcpy(&chKey[0], &chNewKey[0], sizeof chKey);
    memcpy(&chIV[0], &chNewIV[0], sizeof chIV);

    fKeySet = true;
    return true;
}

bool CCrypter::Encrypt(const CKeyingMaterial& vchPlaintext, std::vector<unsigned char> &vchCiphertext)
{
    if (!fKeySet)
        return false;

    // max ciphertext len for a n bytes of plaintext is
    // n + AES_BLOCK_SIZE - 1 bytes
    int nLen = vchPlaintext.size();
    int nCLen = nLen + AES_BLOCK_SIZE, nFLen = 0;
    vchCiphertext = std::vector<unsigned char> (nCLen);

    bool fOk = true;

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    assert(ctx);
    if (fOk) fOk = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
    if (fOk) fOk = EVP_EncryptUpdate(ctx, &vchCiphertext[0], &nCLen, &vchPlaintext[0], nLen) != 0;
    if (fOk) fOk = EVP_EncryptFinal_ex(ctx, (&vchCiphertext[0]) + nCLen, &nFLen) != 0;
    EVP_CIPHER_CTX_free(ctx);

    if (!fOk) return false;

    vchCiphertext.resize(nCLen + nFLen);
    return true;
}

bool CCrypter::Decrypt(const std::vector<unsigned char>& vchCiphertext, CKeyingMaterial& vchPlaintext)
{
    if (!fKeySet)
        return false;

    // plaintext will always be equal to or lesser than length of ciphertext
    int nLen = vchCiphertext.size();
    int nPLen = nLen, nFLen = 0;

    vchPlaintext = CKeyingMaterial(nPLen);

    bool fOk = true;

    EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
    assert(ctx);
    if (fOk) fOk = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, chKey, chIV) != 0;
    if (fOk) fOk = EVP_DecryptUpdate(ctx, &vchPlaintext[0], &nPLen, &vchCiphertext[0], nLen) != 0;
    if (fOk) fOk = EVP_DecryptFinal_ex(ctx, (&vchPlaintext[0]) + nPLen, &nFLen) != 0;
    EVP_CIPHER_CTX_free(ctx);

    if (!fOk) return false;

    vchPlaintext.resize(nPLen + nFLen);
    return true;
}


static bool EncryptSecret(const CKeyingMaterial& vMasterKey, const CKeyingMaterial &vchPlaintext, const uint256& nIV, std::vector<unsigned char> &vchCiphertext)
{
    CCrypter cKeyCrypter;
    std::vector<unsigned char> chIV(WALLET_CRYPTO_KEY_SIZE);
    memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE);
    if (!cKeyCrypter.SetKey(vMasterKey, chIV))
        return false;
    return cKeyCrypter.Encrypt(*((const CKeyingMaterial*)&vchPlaintext), vchCiphertext);
}

static bool DecryptSecret(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCiphertext, const uint256& nIV, CKeyingMaterial& vchPlaintext)
{
    CCrypter cKeyCrypter;
    std::vector<unsigned char> chIV(WALLET_CRYPTO_KEY_SIZE);
    memcpy(&chIV[0], &nIV, WALLET_CRYPTO_KEY_SIZE);
    if (!cKeyCrypter.SetKey(vMasterKey, chIV))
        return false;
    return cKeyCrypter.Decrypt(vchCiphertext, *((CKeyingMaterial*)&vchPlaintext));
}

static bool DecryptHDSeed(
    const CKeyingMaterial& vMasterKey,
    const std::vector<unsigned char>& vchCryptedSecret,
    const uint256& seedFp,
    HDSeed& seed)
{
    CKeyingMaterial vchSecret;

    // Use seed's fingerprint as IV
    // TODO: Handle IV properly when we make encryption a supported feature
    if(!DecryptSecret(vMasterKey, vchCryptedSecret, seedFp, vchSecret))
        return false;

    seed = HDSeed(vchSecret);
    return seed.Fingerprint() == seedFp;
}

uint256 MnemonicEntropyFingerprint(const RawHDSeed& entropy)
{
    // The local copy is not gratuitous -- see the declaration in crypter.h.
    // It is secure_allocator-backed, so it is memory_cleanse()d on destruction
    // (support/allocators/secure.h:45-52).
    RawHDSeed tmp(entropy);
    return HDSeed(tmp).Fingerprint();
}

static bool DecryptMnemonicEntropy(
    const CKeyingMaterial& vMasterKey,
    const std::vector<unsigned char>& vchCryptedSecret,
    const uint256& entropyFp,
    RawHDSeed& entropyOut)
{
    CKeyingMaterial vchSecret;

    // Use the entropy's fingerprint as IV, mirroring DecryptHDSeed above.
    if (!DecryptSecret(vMasterKey, vchCryptedSecret, entropyFp, vchSecret))
        return false;

    // RawHDSeed and CKeyingMaterial are the SAME type (both are
    // std::vector<unsigned char, secure_allocator<unsigned char>>), so this is a
    // plain copy of the same bytes, not a reinterpretation.
    entropyOut = vchSecret;
    return MnemonicEntropyFingerprint(entropyOut) == entropyFp;
}

static bool DecryptKey(const CKeyingMaterial& vMasterKey, const std::vector<unsigned char>& vchCryptedSecret, const CPubKey& vchPubKey, CKey& key)
{
    CKeyingMaterial vchSecret;
    if (!DecryptSecret(vMasterKey, vchCryptedSecret, vchPubKey.GetHash(), vchSecret))
        return false;

    if (vchSecret.size() != 32)
        return false;

    key.Set(vchSecret.begin(), vchSecret.end(), vchPubKey.IsCompressed());
    return key.VerifyPubKey(vchPubKey);
}


static bool DecryptSaplingSpendingKey(const CKeyingMaterial& vMasterKey,
                               const std::vector<unsigned char>& vchCryptedSecret,
                               const libzcash::SaplingExtendedFullViewingKey& extfvk,
                               libzcash::SaplingExtendedSpendingKey& sk)
{
    CKeyingMaterial vchSecret;
    if (!DecryptSecret(vMasterKey, vchCryptedSecret, extfvk.fvk.GetFingerprint(), vchSecret))
        return false;

    if (vchSecret.size() != ZIP32_XSK_SIZE)
        return false;

    CSecureDataStream ss(vchSecret, SER_NETWORK, PROTOCOL_VERSION);
    ss >> sk;
    return sk.expsk.full_viewing_key() == extfvk.fvk;
}

bool CCryptoKeyStore::SetCrypted()
{
    LOCK2(cs_KeyStore, cs_SpendingKeyStore);
    if (fUseCrypto)
        return true;
    if (!(mapKeys.empty() && mapSaplingSpendingKeys.empty()))
        return false;
    fUseCrypto = true;
    return true;
}

bool CCryptoKeyStore::Lock()
{
    if (!SetCrypted())
        return false;

    {
        LOCK(cs_KeyStore);
        vMasterKey.clear();
    }

    NotifyStatusChanged(this);
    return true;
}

bool CCryptoKeyStore::Unlock(const CKeyingMaterial& vMasterKeyIn)
{
    {
        LOCK2(cs_KeyStore, cs_SpendingKeyStore);
        if (!SetCrypted())
            return false;

        bool keyPass = false;
        bool keyFail = false;
        if (!cryptedHDSeed.first.IsNull()) {
            HDSeed seed;
            if (!DecryptHDSeed(vMasterKeyIn, cryptedHDSeed.second, cryptedHDSeed.first, seed))
            {
                keyFail = true;
            } else {
                keyPass = true;
            }
        }
        // Deliberately NO arm here for cryptedMnemonicEntropy. This function is
        // the "some keys decrypt but not all" corruption detector and a keyFail
        // ends at the assert(false) below. The mnemonic entropy is an optional,
        // non-spending, display-only record: legacy wallets, hex-restored
        // wallets and every wallet predating this feature legitimately have a
        // seed and no entropy, and a wallet whose every key decrypts while its
        // entropy does not is not corrupt in any sense that should abort the
        // process -- it simply cannot print its seed phrase. It is decrypted
        // lazily in GetMnemonicEntropy() instead, so that case becomes a false
        // return from one RPC while derivation and spending (which read the
        // seed, not the entropy) carry on. Note the arm above caches nothing
        // either -- `seed` is discarded; it only votes keyPass/keyFail -- so
        // nothing is lost by omitting one here.
        CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
        for (; mi != mapCryptedKeys.end(); ++mi)
        {
            const CPubKey &vchPubKey = (*mi).second.first;
            const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
            CKey key;
            if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key))
            {
                keyFail = true;
                break;
            }
            keyPass = true;
            if (fDecryptionThoroughlyChecked)
                break;
        }
        CryptedSaplingSpendingKeyMap::const_iterator miSapling = mapCryptedSaplingSpendingKeys.begin();
        for (; miSapling != mapCryptedSaplingSpendingKeys.end(); ++miSapling)
        {
            const libzcash::SaplingExtendedFullViewingKey &extfvk = (*miSapling).first;
            const std::vector<unsigned char> &vchCryptedSecret = (*miSapling).second;
            libzcash::SaplingExtendedSpendingKey sk;
            if (!DecryptSaplingSpendingKey(vMasterKeyIn, vchCryptedSecret, extfvk, sk))
            {
                keyFail = true;
                break;
            }
            keyPass = true;
            if (fDecryptionThoroughlyChecked)
                break;
        }
        if (keyPass && keyFail)
        {
            LogPrintf("The wallet is probably corrupted: some keys decrypt but not all.\n");
            assert(false);
        }
        if (keyFail || !keyPass)
            return false;
        vMasterKey = vMasterKeyIn;
        fDecryptionThoroughlyChecked = true;
    }
    NotifyStatusChanged(this);
    return true;
}

bool CCryptoKeyStore::SetHDSeed(const HDSeed& seed)
{
    {
        LOCK(cs_SpendingKeyStore);
        if (!IsCrypted()) {
            return CBasicKeyStore::SetHDSeed(seed);
        }

        if (IsLocked())
            return false;

        std::vector<unsigned char> vchCryptedSecret;
        // Use seed's fingerprint as IV
        // TODO: Handle this properly when we make encryption a supported feature
        auto seedFp = seed.Fingerprint();
        if (!EncryptSecret(vMasterKey, seed.RawSeed(), seedFp, vchCryptedSecret))
            return false;

        // This will call into CWallet to store the crypted seed to disk
        if (!SetCryptedHDSeed(seedFp, vchCryptedSecret))
            return false;
    }
    return true;
}

bool CCryptoKeyStore::SetCryptedHDSeed(
    const uint256& seedFp,
    const std::vector<unsigned char>& vchCryptedSecret)
{
    {
        LOCK(cs_SpendingKeyStore);
        if (!IsCrypted()) {
            return false;
        }

        if (!cryptedHDSeed.first.IsNull()) {
            // Don't allow an existing seed to be changed. We can maybe relax this
            // restriction later once we have worked out the UX implications.
            return false;
        }

        cryptedHDSeed = std::make_pair(seedFp, vchCryptedSecret);
    }
    return true;
}

bool CCryptoKeyStore::HaveHDSeed() const
{
    LOCK(cs_SpendingKeyStore);
    if (!IsCrypted())
        return CBasicKeyStore::HaveHDSeed();

    return !cryptedHDSeed.second.empty();
}

bool CCryptoKeyStore::GetHDSeed(HDSeed& seedOut) const
{
    LOCK(cs_SpendingKeyStore);
    if (!IsCrypted())
        return CBasicKeyStore::GetHDSeed(seedOut);

    if (cryptedHDSeed.second.empty())
        return false;

    return DecryptHDSeed(vMasterKey, cryptedHDSeed.second, cryptedHDSeed.first, seedOut);
}

bool CCryptoKeyStore::SetMnemonicEntropy(const RawHDSeed& entropy)
{
    {
        LOCK(cs_SpendingKeyStore);
        if (!IsCrypted()) {
            return CBasicKeyStore::SetMnemonicEntropy(entropy);
        }

        if (IsLocked())
            return false;

        if (entropy.empty())
            return false;

        std::vector<unsigned char> vchCryptedSecret;
        // Use the entropy's fingerprint as IV
        // TODO: Handle this properly when we make encryption a supported feature
        auto entropyFp = MnemonicEntropyFingerprint(entropy);
        // RawHDSeed IS CKeyingMaterial, so `entropy` binds directly here.
        if (!EncryptSecret(vMasterKey, entropy, entropyFp, vchCryptedSecret))
            return false;

        // Virtual: this calls into CWallet to store the crypted entropy to disk.
        if (!SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret))
            return false;
    }
    return true;
}

bool CCryptoKeyStore::SetCryptedMnemonicEntropy(
    const uint256& entropyFp,
    const std::vector<unsigned char>& vchCryptedSecret)
{
    {
        LOCK(cs_SpendingKeyStore);
        if (!IsCrypted()) {
            return false;
        }

        if (!cryptedMnemonicEntropy.first.IsNull()) {
            // Don't allow existing entropy to be changed, mirroring
            // SetCryptedHDSeed: a phrase that no longer matches the installed
            // seed is worse than no phrase at all.
            return false;
        }

        cryptedMnemonicEntropy = std::make_pair(entropyFp, vchCryptedSecret);
    }
    return true;
}

bool CCryptoKeyStore::HaveMnemonicEntropy() const
{
    LOCK(cs_SpendingKeyStore);
    if (!IsCrypted())
        return CBasicKeyStore::HaveMnemonicEntropy();

    return !cryptedMnemonicEntropy.second.empty();
}

bool CCryptoKeyStore::GetMnemonicEntropy(RawHDSeed& entropyOut) const
{
    LOCK(cs_SpendingKeyStore);
    if (!IsCrypted())
        return CBasicKeyStore::GetMnemonicEntropy(entropyOut);

    if (cryptedMnemonicEntropy.second.empty())
        return false;

    // Decrypted lazily, on demand, and deliberately NOT in Unlock(): see the
    // comment there for why the entropy must not vote in the keyPass/keyFail
    // corruption detector.
    return DecryptMnemonicEntropy(vMasterKey, cryptedMnemonicEntropy.second,
                                  cryptedMnemonicEntropy.first, entropyOut);
}

bool CCryptoKeyStore::AddKeyPubKey(const CKey& key, const CPubKey &pubkey)
{
    {
        LOCK(cs_KeyStore);
        if (!IsCrypted())
            return CBasicKeyStore::AddKeyPubKey(key, pubkey);

        if (IsLocked())
            return false;

        std::vector<unsigned char> vchCryptedSecret;
        CKeyingMaterial vchSecret(key.begin(), key.end());
        if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret))
            return false;

        if (!AddCryptedKey(pubkey, vchCryptedSecret))
            return false;
    }
    return true;
}


bool CCryptoKeyStore::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret)
{
    {
        LOCK(cs_KeyStore);
        if (!SetCrypted())
            return false;

        mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
    }
    return true;
}

bool CCryptoKeyStore::GetKey(const CKeyID &address, CKey& keyOut) const
{
    {
        LOCK(cs_KeyStore);
        if (!IsCrypted())
            return CBasicKeyStore::GetKey(address, keyOut);

        CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
        if (mi != mapCryptedKeys.end())
        {
            const CPubKey &vchPubKey = (*mi).second.first;
            const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second;
            return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);
        }
    }
    return false;
}

bool CCryptoKeyStore::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const
{
    {
        LOCK(cs_KeyStore);
        if (!IsCrypted())
            return CKeyStore::GetPubKey(address, vchPubKeyOut);

        CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
        if (mi != mapCryptedKeys.end())
        {
            vchPubKeyOut = (*mi).second.first;
            return true;
        }
    }
    return false;
}

bool CCryptoKeyStore::AddSaplingSpendingKey(
    const libzcash::SaplingExtendedSpendingKey &sk,
    const libzcash::SaplingPaymentAddress &defaultAddr)
{
    {
        LOCK(cs_SpendingKeyStore);
        if (!IsCrypted()) {
            return CBasicKeyStore::AddSaplingSpendingKey(sk, defaultAddr);
        }

        if (IsLocked()) {
            return false;
        }

        std::vector<unsigned char> vchCryptedSecret;
        CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
        ss << sk;
        CKeyingMaterial vchSecret(ss.begin(), ss.end());
        auto extfvk = sk.ToXFVK();
        if (!EncryptSecret(vMasterKey, vchSecret, extfvk.fvk.GetFingerprint(), vchCryptedSecret)) {
            return false;
        }

        if (!AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, defaultAddr)) {
            return false;
        }
    }
    return true;
}

bool CCryptoKeyStore::AddCryptedSaplingSpendingKey(
    const libzcash::SaplingExtendedFullViewingKey &extfvk,
    const std::vector<unsigned char> &vchCryptedSecret,
    const libzcash::SaplingPaymentAddress &defaultAddr)
{
    {
        LOCK(cs_SpendingKeyStore);
        if (!SetCrypted()) {
            return false;
        }

        // if SaplingFullViewingKey is not in SaplingFullViewingKeyMap, add it
        if (!AddSaplingFullViewingKey(extfvk.fvk, defaultAddr)) {
            return false;
        }

        mapCryptedSaplingSpendingKeys[extfvk] = vchCryptedSecret;
    }
    return true;
}

bool CCryptoKeyStore::GetSaplingSpendingKey(const libzcash::SaplingFullViewingKey &fvk, libzcash::SaplingExtendedSpendingKey &skOut) const
{
    {
        LOCK(cs_SpendingKeyStore);
        if (!IsCrypted())
            return CBasicKeyStore::GetSaplingSpendingKey(fvk, skOut);

        for (auto entry : mapCryptedSaplingSpendingKeys) {
            if (entry.first.fvk == fvk) {
                const std::vector<unsigned char> &vchCryptedSecret = entry.second;
                return DecryptSaplingSpendingKey(vMasterKey, vchCryptedSecret, entry.first, skOut);
            }
        }
    }
    return false;
}

bool CCryptoKeyStore::EncryptKeys(CKeyingMaterial& vMasterKeyIn)
{
    {
        LOCK2(cs_KeyStore, cs_SpendingKeyStore);
        if (!mapCryptedKeys.empty() || IsCrypted())
            return false;

        fUseCrypto = true;
        if (!hdSeed.IsNull()) {
            {
                std::vector<unsigned char> vchCryptedSecret;
                // Use seed's fingerprint as IV
                // TODO: Handle this properly when we make encryption a supported feature
                auto seedFp = hdSeed.Fingerprint();
                if (!EncryptSecret(vMasterKeyIn, hdSeed.RawSeed(), seedFp, vchCryptedSecret)) {
                    return false;
                }
                // This will call into CWallet to store the crypted seed to disk
                if (!SetCryptedHDSeed(seedFp, vchCryptedSecret)) {
                    return false;
                }
            }
            hdSeed = HDSeed();
        }
        if (!mnemonicEntropy.empty()) {
            {
                std::vector<unsigned char> vchCryptedSecret;
                // Use the entropy's fingerprint as IV
                // TODO: Handle this properly when we make encryption a supported feature
                auto entropyFp = MnemonicEntropyFingerprint(mnemonicEntropy);
                if (!EncryptSecret(vMasterKeyIn, mnemonicEntropy, entropyFp, vchCryptedSecret)) {
                    return false;
                }
                // Virtual: calls into CWallet to store the crypted entropy to disk.
                if (!SetCryptedMnemonicEntropy(entropyFp, vchCryptedSecret)) {
                    return false;
                }
            }
            // Drop the plaintext. swap() rather than `= RawHDSeed()`: assigning a
            // shorter vector destroys the elements but KEEPS the capacity, so the
            // old bytes would linger in the locked buffer. swap() hands the buffer
            // to a temporary whose destructor deallocates it, and
            // secure_allocator::deallocate memory_cleanse()s
            // (support/allocators/secure.h:45-52). The `hdSeed = HDSeed();` above
            // has the same weakness but cannot be fixed here: HDSeed's raw vector
            // is private with no swap accessor (zip32.h:23-33).
            RawHDSeed().swap(mnemonicEntropy);
        }
        BOOST_FOREACH(KeyMap::value_type& mKey, mapKeys)
        {
            const CKey &key = mKey.second;
            CPubKey vchPubKey = key.GetPubKey();
            CKeyingMaterial vchSecret(key.begin(), key.end());
            std::vector<unsigned char> vchCryptedSecret;
            if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) {
                return false;
            }
            if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) {
                return false;
            }
        }
        mapKeys.clear();
        //! Sapling key support
        BOOST_FOREACH(SaplingSpendingKeyMap::value_type& mSaplingSpendingKey, mapSaplingSpendingKeys)
        {
            const auto &sk = mSaplingSpendingKey.second;
            CSecureDataStream ss(SER_NETWORK, PROTOCOL_VERSION);
            ss << sk;
            CKeyingMaterial vchSecret(ss.begin(), ss.end());
            auto extfvk = sk.ToXFVK();
            std::vector<unsigned char> vchCryptedSecret;
            if (!EncryptSecret(vMasterKeyIn, vchSecret, extfvk.fvk.GetFingerprint(), vchCryptedSecret)) {
                return false;
            }
            if (!AddCryptedSaplingSpendingKey(extfvk, vchCryptedSecret, sk.DefaultAddress())) {
                return false;
            }
        }
        mapSaplingSpendingKeys.clear();
    }
    return true;
}
