<commit_before>///////////////////////////////////////////////////////////////////////////////
//
// Vault.cpp
//
// Copyright (c) 2013-2014 <NAME>
//
// All Rights Reserved.
//

#include "Vault.h"
#include "Database.h"

#include <CoinQ/CoinQ_script.h>
#include <CoinQ/CoinQ_blocks.h>


#include "../odb/Schema-odb.hxx"

#include <odb/transaction.hxx>
#include <odb/session.hxx>

#include <CoinCore/hash.h>
#include <CoinCore/MerkleTree.h>
#include <CoinCore/secp256k1.h>
#include <CoinCore/BigInt.h>

#include <logger/logger.h>

#include <stdutils/stringutils.h>

#include <sstream>
#include <fstream>
#include <algorithm>

// support for boost serialization
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

using namespace CoinDB;

/*
 * class Vault implementation
*/
Vault::Vault(int argc, char** argv, bool create, uint32_t version)
    : db_(open_database(argc, argv, create))
{
    LOGGER(trace) << "Vault::Vault(..., " << (create ? "true" : "false") << ", " << version << ")" << std::endl;

    if (argc >= 2) name_ = argv[1];
    if (create) setSchemaVersion(version);
}

Vault::Vault(const std::string& dbname, bool create, uint32_t version)
    : db_(openDatabase("", "", dbname, create))
{
    LOGGER(trace) << "Vault::Vault(" << dbname << ", " << (create ? "true" : "false") << ", " << version << ")" << std::endl;

    name_ = dbname;
    if (create) setSchemaVersion(version);
}

Vault::Vault(const std::string& dbuser, const std::string& dbpasswd, const std::string& dbname, bool create, uint32_t version)
    : db_(openDatabase(dbuser, dbpasswd, dbname, create))
{
    LOGGER(trace) << "Vault::Vault(" << dbuser << ", ..., " << dbname << ", " << (create ? "true" : "false") << ", " << version << ")" << std::endl;

    name_ = dbname;
    if (create) setSchemaVersion(version);
}

///////////////////////
// GLOBAL OPERATIONS //
///////////////////////
uint32_t Vault::getSchemaVersion() const
{
    LOGGER(trace) << "Vault::getSchemaVersion()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getSchemaVersion_unwrapped();
}

uint32_t Vault::getSchemaVersion_unwrapped() const
{
    odb::result<Version> r(db_->query<Version>());
    return r.empty() ? 0 : r.begin()->version();
}

void Vault::setSchemaVersion(uint32_t version)
{
    LOGGER(trace) << "Vault::setSchemaVersion(" << version << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    setSchemaVersion_unwrapped(version);
    t.commit();
}

void Vault::setSchemaVersion_unwrapped(uint32_t version)
{
    odb::result<Version> r(db_->query<Version>());
    if (r.empty())
    {
        std::shared_ptr<Version> version_obj(new Version(version));
        db_->persist(version_obj);
    }
    else
    {
        std::shared_ptr<Version> version_obj(r.begin().load());
        version_obj->version(version);
        db_->update(version_obj);
    }
}

uint32_t Vault::getHorizonTimestamp() const
{
    LOGGER(trace) << "Vault::getHorizonTimestamp()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getHorizonTimestamp_unwrapped();
}

uint32_t Vault::getMaxFirstBlockTimestamp() const
{
    LOGGER(trace) << "Vault::getMaxFirstBlockTimestamp()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getMaxFirstBlockTimestamp_unwrapped();
}

uint32_t Vault::getHorizonTimestamp_unwrapped() const
{
    odb::result<HorizonTimestampView> r(db_->query<HorizonTimestampView>());
    return r.empty() ? 0 : r.begin()->timestamp;
}

uint32_t Vault::getMaxFirstBlockTimestamp_unwrapped() const
{
    uint32_t maxFirstBlockTimestamp = getHorizonTimestamp_unwrapped();
    if (maxFirstBlockTimestamp > MAX_HORIZON_TIMESTAMP_OFFSET)
        maxFirstBlockTimestamp -= MAX_HORIZON_TIMESTAMP_OFFSET;
    return maxFirstBlockTimestamp;
}

uint32_t Vault::getHorizonHeight() const
{
    LOGGER(trace) << "Vault::getHorizonHeight()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getHorizonHeight_unwrapped();
}

uint32_t Vault::getHorizonHeight_unwrapped() const
{
    odb::result<HorizonHeightView> r(db_->query<HorizonHeightView>());
    return r.empty() ? 0 : r.begin()->height;
}

std::vector<bytes_t> Vault::getLocatorHashes() const
{
    LOGGER(trace) << "Vault::getLocatorHashes()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getLocatorHashes_unwrapped();
}

std::vector<bytes_t> Vault::getLocatorHashes_unwrapped() const
{
    std::vector<bytes_t>  hashes;
    std::vector<uint32_t> heights;

    uint32_t i = getBestHeight_unwrapped();
    if (i == 0) return hashes;

    uint32_t n = 1;
    uint32_t step = 1;
    heights.push_back(i);
    while (step <= i)
    {
        i -= step;
        n++;
        if (n > 10) step *= 2;
        heights.push_back(i);
    }

    typedef odb::query<BlockHeader> query_t;
    odb::result<BlockHeader> r(db_->query<BlockHeader>(query_t::height.in_range(heights.begin(), heights.end()) + "ORDER BY" + query_t::height + "DESC"));
    for (auto& header: r) { hashes.push_back(header.hash()); }
    return hashes;
}

Coin::BloomFilter Vault::getBloomFilter(double falsePositiveRate, uint32_t nTweak, uint32_t nFlags) const
{
    LOGGER(trace) << "Vault::getBloomFilter(" << falsePositiveRate << ", " << nTweak << ", " << nFlags << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getBloomFilter_unwrapped(falsePositiveRate, nTweak, nFlags);
}

Coin::BloomFilter Vault::getBloomFilter_unwrapped(double falsePositiveRate, uint32_t nTweak, uint32_t nFlags) const
{
    using namespace CoinQ::Script;

    std::vector<bytes_t> elements;
    odb::result<SigningScriptView> r(db_->query<SigningScriptView>());
    for (auto& view: r)
    {
        Script script(view.txinscript);
        elements.push_back(script.txinscript(Script::SIGN));                // Add input script element
        elements.push_back(getScriptPubKeyPayee(view.txoutscript).second);  // Add output script element
    }
    if (elements.empty()) return Coin::BloomFilter();

    Coin::BloomFilter filter(elements.size(), falsePositiveRate, nTweak, nFlags);
    for (auto& element: elements) { filter.insert(element); }
    return filter;
}


///////////////////////////
// CHAIN CODE OPERATIONS //
///////////////////////////
bool Vault::areChainCodesLocked() const
{
    LOGGER(trace) << "Vault::areChainCodesLocked()" << std::endl;
    return chainCodeUnlockKey.empty();
}

void Vault::lockChainCodes() const
{
    LOGGER(trace) << "Vault::unlockChainCodes()" << std::endl;
    chainCodeUnlockKey.clear();
}

void Vault::unlockChainCodes(const secure_bytes_t& unlockKey) const
{
    LOGGER(trace) << "Vault::unlockChainCodes(...)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    verifyChainCodeUnlockKey_unwrapped(unlockKey);
    chainCodeUnlockKey = unlockKey;
}
    
void Vault::verifyChainCodeUnlockKey_unwrapped(const secure_bytes_t& unlockKey) const
{
    odb::result<Keychain> r(db_->query<Keychain>());
    for (auto& keychain: r)
        if (!keychain.unlockChainCode(unlockKey))
            throw KeychainChainCodeUnlockFailedException(keychain.name());
}

void Vault::setChainCodeUnlockKey(const secure_bytes_t& newUnlockKey)
{
    LOGGER(trace) << "Vault::setChainCodeUnlockKey(...)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    setChainCodeUnlockKey_unwrapped(newUnlockKey);
    chainCodeUnlockKey.clear();
}


void Vault::setChainCodeUnlockKey_unwrapped(const secure_bytes_t& newUnlockKey)
{
    odb::result<Keychain> r(db_->query<Keychain>());
    for (auto& keychain: r)
    {
        if (!keychain.unlockChainCode(chainCodeUnlockKey))
            throw KeychainChainCodeUnlockFailedException(keychain.name());

        keychain.setChainCodeUnlockKey(newUnlockKey);
    }
}

/////////////////////////
// KEYCHAIN OPERATIONS //
/////////////////////////
void Vault::exportKeychain(const std::string& keychain_name, const std::string& filepath, bool exportprivkeys, const secure_bytes_t& exportChainCodeUnlockKey) const
{
    LOGGER(trace) << "Vault::exportKeychain(" << keychain_name << ", " << filepath << ", " << (exportprivkeys ? "true" : "false") << ", ?)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    std::shared_ptr<Keychain> keychain = getKeychain_unwrapped(keychain_name);
    if (exportprivkeys && !keychain->isPrivate()) throw KeychainIsNotPrivateException(keychain_name);
    if (!exportprivkeys) { keychain->clearPrivateKey(); }
    exportKeychain_unwrapped(keychain, filepath, exportChainCodeUnlockKey);
}

void Vault::exportKeychain_unwrapped(std::shared_ptr<Keychain> keychain, const std::string& filepath, const secure_bytes_t& exportChainCodeUnlockKey) const
{
    if (!exportChainCodeUnlockKey.empty())
    {
        // Reencrypt the chain code using a different unlock key than our own.
        unlockKeychainChainCode_unwrapped(keychain);
        keychain->setChainCodeUnlockKey(exportChainCodeUnlockKey);
    }

    std::ofstream ofs(filepath);
    boost::archive::text_oarchive oa(ofs);
    oa << *keychain;
}

std::shared_ptr<Keychain> Vault::importKeychain(const std::string& filepath, bool& importprivkeys, const secure_bytes_t& importChainCodeUnlockKey)
{
    LOGGER(trace) << "Vault::importKeychain(" << filepath << ", " << (importprivkeys ? "true" : "false") << ", ?)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::session s;
    odb::core::transaction t(db_->begin());
    std::shared_ptr<Keychain> keychain = importKeychain_unwrapped(filepath, importprivkeys, importChainCodeUnlockKey);
    t.commit();
    return keychain;
}

std::shared_ptr<Keychain> Vault::importKeychain_unwrapped(const std::string& filepath, bool& importprivkeys, const secure_bytes_t& importChainCodeUnlockKey)
{
    std::shared_ptr<Keychain> keychain(new Keychain());
    {
        std::ifstream ifs(filepath);
        boost::archive::text_iarchive ia(ifs);
        ia >> *keychain;
    }

    if (importChainCodeUnlockKey.empty())
    {
        // Make sure we can decrypt the chain code using our own unlock key.
        unlockKeychainChainCode_unwrapped(keychain);
    }
    else
    {
        // Reencrypt the chain code using our own unlock key.
        unlockKeychainChainCode_unwrapped(keychain, importChainCodeUnlockKey);
        keychain->setChainCodeUnlockKey(chainCodeUnlockKey);
    }

    if (!keychain->isPrivate()) { importprivkeys = false; }
    if (!importprivkeys)        { keychain->clearPrivateKey(); }

    odb::result<Keychain> keychain_r(db_->query<Keychain>(odb::query<Keychain>::hash == keychain->hash()));
    if (!keychain_r.empty())
    {
        std::shared_ptr<Keychain> stored_keychain(keychain_r.begin().load());
        if (keychain->isPrivate() && !stored_keychain->isPrivate())
        {
            // We already have the public key. Import the private key and update signing keys.
            stored_keychain->importPrivateKey(*keychain);
            db_->update(stored_keychain);
            odb::result<Key> key_r(db_->query<Key>(odb::query<Key>::root_keychain == stored_keychain->id() && odb::query<Key>::is_private == 0));
            for (auto& key: key_r)
            {
                key.updatePrivate();
                db_->update(key);
            }
            return stored_keychain; 
        }
        throw KeychainAlreadyExistsException(stored_keychain->name());
    }

    std::string keychain_name = keychain->name();
    unsigned int append_num = 1; // in case of name conflict
    while (keychainExists_unwrapped(keychain->name()))
    {
        std::stringstream ss;
        ss << keychain_name << append_num++;
        keychain->name(ss.str());
    }

    db_->persist(keychain);
    return keychain;
}

bool Vault::keychainExists(const std::string& keychain_name) const
{
    LOGGER(trace) << "Vault::keychainExists(" << keychain_name << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return keychainExists_unwrapped(keychain_name);
}

bool Vault::keychainExists_unwrapped(const std::string& keychain_name) const
{
    odb::result<Keychain> r(db_->query<Keychain>(odb::query<Keychain>::name == keychain_name));
    return !r.empty();
}

bool Vault::keychainExists(const bytes_t& keychain_hash) const
{
    LOGGER(trace) << "Vault::keychainExists(@hash = " << uchar_vector(keychain_hash).getHex() << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return keychainExists_unwrapped(keychain_hash);
}

bool Vault::keychainExists_unwrapped(const bytes_t& keychain_hash) const
{
    odb::result<Keychain> r(db_->query<Keychain>(odb::query<Keychain>::hash == keychain_hash));
    return !r.empty();
}

std::shared_ptr<Keychain> Vault::newKeychain(const std::string& keychain_name, const secure_bytes_t& entropy, const secure_bytes_t& lockKey, const bytes_t& salt)
{
    LOGGER(trace) << "Vault::newKeychain(" << keychain_name << ", ...)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::session session;
    odb::core::transaction t(db_->begin());
    odb::result<Keychain> r(db_->query<Keychain>(odb::query<Keychain>::name == keychain_name));
    if (!r.empty()) throw KeychainAlreadyExistsException(keychain_name);

    std::shared_ptr<Keychain> keychain(new Keychain(keychain_name, entropy, lockKey, salt));
    persistKeychain_unwrapped(keychain);
    t.commit();

    return keychain;
}

void Vault::renameKeychain(const std::string& old_name, const std::string& new_name)
{
    LOGGER(trace) << "Vault::renameKeychain(" << old_name << ", " << new_name << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::session session;
    odb::core::transaction t(db_->begin());

    odb::result<Keychain> keychain_r(db_->query<Keychain>(odb::query<Keychain>::name == old_name));
    if (keychain_r.empty()) throw KeychainNotFoundException(old_name);

    if (old_name == new_name) return;

    odb::result<Keychain> new_keychain_r(db_->query<Keychain>(odb::query<Keychain>::name == new_name));
    if (!new_keychain_r.empty()) throw KeychainAlreadyExistsException(new_name);

    std::shared_ptr<Keychain> keychain(keychain_r.begin().load());
    keychain->name(new_name);

    db_->update(keychain);
    t.commit();
}

void Vault::persistKeychain_unwrapped(std::shared_ptr<Keychain> keychain)
{
    if (keychain->parent())
        db_->update(keychain->parent());

    db_->persist(keychain);
}

std::vector<KeychainView> Vault::getRootKeychainViews(const std::string& account_name, bool get_hidden) const
{
    LOGGER(trace) << "Vault::getRootKeychainViews(" << account_name << ", " << (get_hidden ? "true" : "false") << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getRootKeychainViews_unwrapped(account_name, get_hidden);
}

std::vector<KeychainView> Vault::getRootKeychainViews_unwrapped(const std::string& account_name, bool get_hidden) const
{
    typedef odb::query<KeychainView> query_t;
    query_t query(1 == 1);
    if (!account_name.empty())
        query = query && (query_t::Account::name == account_name);
    if (getSchemaVersion_unwrapped() >= 4 && !get_hidden)
        query = query && (!query_t::Keychain::hidden);
    odb::result<KeychainView> r(db_->query<KeychainView>(query));
    std::vector<KeychainView> views;
    for (auto& view: r)
    {
        view.is_locked = !mapPrivateKeyUnlock.count(view.name);
        views.push_back(view);
    }
    return views;
}

secure_bytes_t Vault::getKeychainExtendedKey(const std::string& keychain_name, bool get_private) const
{
    LOGGER(trace) << "Vault::getKeychainExtendedKey(" << keychain_name << ", " << (get_private ? "true" : "false") << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);<commit_msg>Temporary fix to Vault::getRootKeychainViews() returning duplicates.<commit_after>///////////////////////////////////////////////////////////////////////////////
//
// Vault.cpp
//
// Copyright (c) 2013-2014 <NAME>
//
// All Rights Reserved.
//

#include "Vault.h"
#include "Database.h"

#include <CoinQ/CoinQ_script.h>
#include <CoinQ/CoinQ_blocks.h>


#include "../odb/Schema-odb.hxx"

#include <odb/transaction.hxx>
#include <odb/session.hxx>

#include <CoinCore/hash.h>
#include <CoinCore/MerkleTree.h>
#include <CoinCore/secp256k1.h>
#include <CoinCore/BigInt.h>

#include <logger/logger.h>

#include <stdutils/stringutils.h>

#include <sstream>
#include <fstream>
#include <algorithm>

// support for boost serialization
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

using namespace CoinDB;

/*
 * class Vault implementation
*/
Vault::Vault(int argc, char** argv, bool create, uint32_t version)
    : db_(open_database(argc, argv, create))
{
    LOGGER(trace) << "Vault::Vault(..., " << (create ? "true" : "false") << ", " << version << ")" << std::endl;

    if (argc >= 2) name_ = argv[1];
    if (create) setSchemaVersion(version);
}

Vault::Vault(const std::string& dbname, bool create, uint32_t version)
    : db_(openDatabase("", "", dbname, create))
{
    LOGGER(trace) << "Vault::Vault(" << dbname << ", " << (create ? "true" : "false") << ", " << version << ")" << std::endl;

    name_ = dbname;
    if (create) setSchemaVersion(version);
}

Vault::Vault(const std::string& dbuser, const std::string& dbpasswd, const std::string& dbname, bool create, uint32_t version)
    : db_(openDatabase(dbuser, dbpasswd, dbname, create))
{
    LOGGER(trace) << "Vault::Vault(" << dbuser << ", ..., " << dbname << ", " << (create ? "true" : "false") << ", " << version << ")" << std::endl;

    name_ = dbname;
    if (create) setSchemaVersion(version);
}

///////////////////////
// GLOBAL OPERATIONS //
///////////////////////
uint32_t Vault::getSchemaVersion() const
{
    LOGGER(trace) << "Vault::getSchemaVersion()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getSchemaVersion_unwrapped();
}

uint32_t Vault::getSchemaVersion_unwrapped() const
{
    odb::result<Version> r(db_->query<Version>());
    return r.empty() ? 0 : r.begin()->version();
}

void Vault::setSchemaVersion(uint32_t version)
{
    LOGGER(trace) << "Vault::setSchemaVersion(" << version << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    setSchemaVersion_unwrapped(version);
    t.commit();
}

void Vault::setSchemaVersion_unwrapped(uint32_t version)
{
    odb::result<Version> r(db_->query<Version>());
    if (r.empty())
    {
        std::shared_ptr<Version> version_obj(new Version(version));
        db_->persist(version_obj);
    }
    else
    {
        std::shared_ptr<Version> version_obj(r.begin().load());
        version_obj->version(version);
        db_->update(version_obj);
    }
}

uint32_t Vault::getHorizonTimestamp() const
{
    LOGGER(trace) << "Vault::getHorizonTimestamp()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getHorizonTimestamp_unwrapped();
}

uint32_t Vault::getMaxFirstBlockTimestamp() const
{
    LOGGER(trace) << "Vault::getMaxFirstBlockTimestamp()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getMaxFirstBlockTimestamp_unwrapped();
}

uint32_t Vault::getHorizonTimestamp_unwrapped() const
{
    odb::result<HorizonTimestampView> r(db_->query<HorizonTimestampView>());
    return r.empty() ? 0 : r.begin()->timestamp;
}

uint32_t Vault::getMaxFirstBlockTimestamp_unwrapped() const
{
    uint32_t maxFirstBlockTimestamp = getHorizonTimestamp_unwrapped();
    if (maxFirstBlockTimestamp > MAX_HORIZON_TIMESTAMP_OFFSET)
        maxFirstBlockTimestamp -= MAX_HORIZON_TIMESTAMP_OFFSET;
    return maxFirstBlockTimestamp;
}

uint32_t Vault::getHorizonHeight() const
{
    LOGGER(trace) << "Vault::getHorizonHeight()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getHorizonHeight_unwrapped();
}

uint32_t Vault::getHorizonHeight_unwrapped() const
{
    odb::result<HorizonHeightView> r(db_->query<HorizonHeightView>());
    return r.empty() ? 0 : r.begin()->height;
}

std::vector<bytes_t> Vault::getLocatorHashes() const
{
    LOGGER(trace) << "Vault::getLocatorHashes()" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getLocatorHashes_unwrapped();
}

std::vector<bytes_t> Vault::getLocatorHashes_unwrapped() const
{
    std::vector<bytes_t>  hashes;
    std::vector<uint32_t> heights;

    uint32_t i = getBestHeight_unwrapped();
    if (i == 0) return hashes;

    uint32_t n = 1;
    uint32_t step = 1;
    heights.push_back(i);
    while (step <= i)
    {
        i -= step;
        n++;
        if (n > 10) step *= 2;
        heights.push_back(i);
    }

    typedef odb::query<BlockHeader> query_t;
    odb::result<BlockHeader> r(db_->query<BlockHeader>(query_t::height.in_range(heights.begin(), heights.end()) + "ORDER BY" + query_t::height + "DESC"));
    for (auto& header: r) { hashes.push_back(header.hash()); }
    return hashes;
}

Coin::BloomFilter Vault::getBloomFilter(double falsePositiveRate, uint32_t nTweak, uint32_t nFlags) const
{
    LOGGER(trace) << "Vault::getBloomFilter(" << falsePositiveRate << ", " << nTweak << ", " << nFlags << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getBloomFilter_unwrapped(falsePositiveRate, nTweak, nFlags);
}

Coin::BloomFilter Vault::getBloomFilter_unwrapped(double falsePositiveRate, uint32_t nTweak, uint32_t nFlags) const
{
    using namespace CoinQ::Script;

    std::vector<bytes_t> elements;
    odb::result<SigningScriptView> r(db_->query<SigningScriptView>());
    for (auto& view: r)
    {
        Script script(view.txinscript);
        elements.push_back(script.txinscript(Script::SIGN));                // Add input script element
        elements.push_back(getScriptPubKeyPayee(view.txoutscript).second);  // Add output script element
    }
    if (elements.empty()) return Coin::BloomFilter();

    Coin::BloomFilter filter(elements.size(), falsePositiveRate, nTweak, nFlags);
    for (auto& element: elements) { filter.insert(element); }
    return filter;
}


///////////////////////////
// CHAIN CODE OPERATIONS //
///////////////////////////
bool Vault::areChainCodesLocked() const
{
    LOGGER(trace) << "Vault::areChainCodesLocked()" << std::endl;
    return chainCodeUnlockKey.empty();
}

void Vault::lockChainCodes() const
{
    LOGGER(trace) << "Vault::unlockChainCodes()" << std::endl;
    chainCodeUnlockKey.clear();
}

void Vault::unlockChainCodes(const secure_bytes_t& unlockKey) const
{
    LOGGER(trace) << "Vault::unlockChainCodes(...)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    verifyChainCodeUnlockKey_unwrapped(unlockKey);
    chainCodeUnlockKey = unlockKey;
}
    
void Vault::verifyChainCodeUnlockKey_unwrapped(const secure_bytes_t& unlockKey) const
{
    odb::result<Keychain> r(db_->query<Keychain>());
    for (auto& keychain: r)
        if (!keychain.unlockChainCode(unlockKey))
            throw KeychainChainCodeUnlockFailedException(keychain.name());
}

void Vault::setChainCodeUnlockKey(const secure_bytes_t& newUnlockKey)
{
    LOGGER(trace) << "Vault::setChainCodeUnlockKey(...)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    setChainCodeUnlockKey_unwrapped(newUnlockKey);
    chainCodeUnlockKey.clear();
}


void Vault::setChainCodeUnlockKey_unwrapped(const secure_bytes_t& newUnlockKey)
{
    odb::result<Keychain> r(db_->query<Keychain>());
    for (auto& keychain: r)
    {
        if (!keychain.unlockChainCode(chainCodeUnlockKey))
            throw KeychainChainCodeUnlockFailedException(keychain.name());

        keychain.setChainCodeUnlockKey(newUnlockKey);
    }
}

/////////////////////////
// KEYCHAIN OPERATIONS //
/////////////////////////
void Vault::exportKeychain(const std::string& keychain_name, const std::string& filepath, bool exportprivkeys, const secure_bytes_t& exportChainCodeUnlockKey) const
{
    LOGGER(trace) << "Vault::exportKeychain(" << keychain_name << ", " << filepath << ", " << (exportprivkeys ? "true" : "false") << ", ?)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    std::shared_ptr<Keychain> keychain = getKeychain_unwrapped(keychain_name);
    if (exportprivkeys && !keychain->isPrivate()) throw KeychainIsNotPrivateException(keychain_name);
    if (!exportprivkeys) { keychain->clearPrivateKey(); }
    exportKeychain_unwrapped(keychain, filepath, exportChainCodeUnlockKey);
}

void Vault::exportKeychain_unwrapped(std::shared_ptr<Keychain> keychain, const std::string& filepath, const secure_bytes_t& exportChainCodeUnlockKey) const
{
    if (!exportChainCodeUnlockKey.empty())
    {
        // Reencrypt the chain code using a different unlock key than our own.
        unlockKeychainChainCode_unwrapped(keychain);
        keychain->setChainCodeUnlockKey(exportChainCodeUnlockKey);
    }

    std::ofstream ofs(filepath);
    boost::archive::text_oarchive oa(ofs);
    oa << *keychain;
}

std::shared_ptr<Keychain> Vault::importKeychain(const std::string& filepath, bool& importprivkeys, const secure_bytes_t& importChainCodeUnlockKey)
{
    LOGGER(trace) << "Vault::importKeychain(" << filepath << ", " << (importprivkeys ? "true" : "false") << ", ?)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::session s;
    odb::core::transaction t(db_->begin());
    std::shared_ptr<Keychain> keychain = importKeychain_unwrapped(filepath, importprivkeys, importChainCodeUnlockKey);
    t.commit();
    return keychain;
}

std::shared_ptr<Keychain> Vault::importKeychain_unwrapped(const std::string& filepath, bool& importprivkeys, const secure_bytes_t& importChainCodeUnlockKey)
{
    std::shared_ptr<Keychain> keychain(new Keychain());
    {
        std::ifstream ifs(filepath);
        boost::archive::text_iarchive ia(ifs);
        ia >> *keychain;
    }

    if (importChainCodeUnlockKey.empty())
    {
        // Make sure we can decrypt the chain code using our own unlock key.
        unlockKeychainChainCode_unwrapped(keychain);
    }
    else
    {
        // Reencrypt the chain code using our own unlock key.
        unlockKeychainChainCode_unwrapped(keychain, importChainCodeUnlockKey);
        keychain->setChainCodeUnlockKey(chainCodeUnlockKey);
    }

    if (!keychain->isPrivate()) { importprivkeys = false; }
    if (!importprivkeys)        { keychain->clearPrivateKey(); }

    odb::result<Keychain> keychain_r(db_->query<Keychain>(odb::query<Keychain>::hash == keychain->hash()));
    if (!keychain_r.empty())
    {
        std::shared_ptr<Keychain> stored_keychain(keychain_r.begin().load());
        if (keychain->isPrivate() && !stored_keychain->isPrivate())
        {
            // We already have the public key. Import the private key and update signing keys.
            stored_keychain->importPrivateKey(*keychain);
            db_->update(stored_keychain);
            odb::result<Key> key_r(db_->query<Key>(odb::query<Key>::root_keychain == stored_keychain->id() && odb::query<Key>::is_private == 0));
            for (auto& key: key_r)
            {
                key.updatePrivate();
                db_->update(key);
            }
            return stored_keychain; 
        }
        throw KeychainAlreadyExistsException(stored_keychain->name());
    }

    std::string keychain_name = keychain->name();
    unsigned int append_num = 1; // in case of name conflict
    while (keychainExists_unwrapped(keychain->name()))
    {
        std::stringstream ss;
        ss << keychain_name << append_num++;
        keychain->name(ss.str());
    }

    db_->persist(keychain);
    return keychain;
}

bool Vault::keychainExists(const std::string& keychain_name) const
{
    LOGGER(trace) << "Vault::keychainExists(" << keychain_name << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return keychainExists_unwrapped(keychain_name);
}

bool Vault::keychainExists_unwrapped(const std::string& keychain_name) const
{
    odb::result<Keychain> r(db_->query<Keychain>(odb::query<Keychain>::name == keychain_name));
    return !r.empty();
}

bool Vault::keychainExists(const bytes_t& keychain_hash) const
{
    LOGGER(trace) << "Vault::keychainExists(@hash = " << uchar_vector(keychain_hash).getHex() << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return keychainExists_unwrapped(keychain_hash);
}

bool Vault::keychainExists_unwrapped(const bytes_t& keychain_hash) const
{
    odb::result<Keychain> r(db_->query<Keychain>(odb::query<Keychain>::hash == keychain_hash));
    return !r.empty();
}

std::shared_ptr<Keychain> Vault::newKeychain(const std::string& keychain_name, const secure_bytes_t& entropy, const secure_bytes_t& lockKey, const bytes_t& salt)
{
    LOGGER(trace) << "Vault::newKeychain(" << keychain_name << ", ...)" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::session session;
    odb::core::transaction t(db_->begin());
    odb::result<Keychain> r(db_->query<Keychain>(odb::query<Keychain>::name == keychain_name));
    if (!r.empty()) throw KeychainAlreadyExistsException(keychain_name);

    std::shared_ptr<Keychain> keychain(new Keychain(keychain_name, entropy, lockKey, salt));
    persistKeychain_unwrapped(keychain);
    t.commit();

    return keychain;
}

void Vault::renameKeychain(const std::string& old_name, const std::string& new_name)
{
    LOGGER(trace) << "Vault::renameKeychain(" << old_name << ", " << new_name << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::session session;
    odb::core::transaction t(db_->begin());

    odb::result<Keychain> keychain_r(db_->query<Keychain>(odb::query<Keychain>::name == old_name));
    if (keychain_r.empty()) throw KeychainNotFoundException(old_name);

    if (old_name == new_name) return;

    odb::result<Keychain> new_keychain_r(db_->query<Keychain>(odb::query<Keychain>::name == new_name));
    if (!new_keychain_r.empty()) throw KeychainAlreadyExistsException(new_name);

    std::shared_ptr<Keychain> keychain(keychain_r.begin().load());
    keychain->name(new_name);

    db_->update(keychain);
    t.commit();
}

void Vault::persistKeychain_unwrapped(std::shared_ptr<Keychain> keychain)
{
    if (keychain->parent())
        db_->update(keychain->parent());

    db_->persist(keychain);
}

std::vector<KeychainView> Vault::getRootKeychainViews(const std::string& account_name, bool get_hidden) const
{
    LOGGER(trace) << "Vault::getRootKeychainViews(" << account_name << ", " << (get_hidden ? "true" : "false") << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
    odb::core::transaction t(db_->begin());
    return getRootKeychainViews_unwrapped(account_name, get_hidden);
}

std::vector<KeychainView> Vault::getRootKeychainViews_unwrapped(const std::string& account_name, bool get_hidden) const
{
    typedef odb::query<KeychainView> query_t;
    query_t query(1 == 1);
    if (!account_name.empty())
        query = query && (query_t::Account::name == account_name);
    if (getSchemaVersion_unwrapped() >= 4 && !get_hidden)
        query = query && (!query_t::Keychain::hidden);
    odb::result<KeychainView> r(db_->query<KeychainView>(query));
    std::vector<KeychainView> views;
    // TODO: figure out why query sometimes returns duplicates.
    std::set<unsigned long> view_ids;
    for (auto& view: r)
    {
        if (view_ids.count(view.id)) continue;
        view_ids.insert(view.id);
        view.is_locked = !mapPrivateKeyUnlock.count(view.name);
        views.push_back(view);
    }
    return views;
}

secure_bytes_t Vault::getKeychainExtendedKey(const std::string& keychain_name, bool get_private) const
{
    LOGGER(trace) << "Vault::getKeychainExtendedKey(" << keychain_name << ", " << (get_private ? "true" : "false") << ")" << std::endl;

    boost::lock_guard<boost::mutex> lock(mutex);
{'fraction_non_alphanumeric': 0.17049936290273648, 'fraction_numerical': 0.004368666949821006, 'mean_word_length': 4.2995176848874594, 'pattern_counts': {'":': 0, '<': 602, '<?xml version=': 0, '>': 332, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3187486', 'n_tokens_mistral': 10967, 'n_tokens_neox': 10708, 'n_words': 1813}