Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to create wallet from seed or audit key #164

Merged
merged 13 commits into from
Sep 13, 2024
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ BLSCT_H = \
blsct/wallet/address.h \
blsct/wallet/hdchain.h \
blsct/wallet/helpers.h \
blsct/wallet/import_wallet_type.h \
blsct/wallet/keyman.h \
blsct/wallet/keyring.h \
blsct/wallet/txfactory.h \
Expand Down
2 changes: 1 addition & 1 deletion src/bench/wallet_create.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ static void WalletCreate(benchmark::Bench& bench, bool encrypted)

fs::path wallet_path = test_setup->m_path_root / strprintf("test_wallet_%d", random.rand32()).c_str();
bench.run([&] {
auto wallet = CreateWallet(context, wallet_path.utf8string(), /*load_on_start=*/std::nullopt, options, status, error_string, warnings);
auto wallet = CreateWallet(context, wallet_path.utf8string(), {}, blsct::IMPORT_MASTER_KEY, /*load_on_start=*/std::nullopt, options, status, error_string, warnings);
assert(status == DatabaseStatus::SUCCESS);
assert(wallet != nullptr);

Expand Down
1 change: 1 addition & 0 deletions src/bitcoin-wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ static void SetupWalletToolArgs(ArgsManager& argsman)
argsman.AddArg("-debug=<category>", "Output debugging information (default: 0).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
argsman.AddArg("-descriptors", "Create descriptors wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-blsct", "Create blsct wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-seed", "Seed used for the wallet creation. Only for 'create'. Can be a master seed or an audit key.", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-legacy", "Create legacy wallet. Only for 'create'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-format=<format>", "The format of the wallet file to create. Either \"bdb\" or \"sqlite\". Only used with 'createfromdump'", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
argsman.AddArg("-printtoconsole", "Send trace/debug info to console (default: 1 when no -debug is true, 0 otherwise).", ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
Expand Down
16 changes: 16 additions & 0 deletions src/blsct/wallet/import_wallet_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2023 The Navio developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef NAVIO_BLSCT_IMPORT_WALLET_TYPE_H
#define NAVIO_BLSCT_IMPORT_WALLET_TYPE_H

namespace blsct {
enum SeedType {
IMPORT_MASTER_KEY,
IMPORT_VIEW_KEY
};

} // namespace blsct

#endif // NAVIO_BLSCT_KEYMAN_H
48 changes: 36 additions & 12 deletions src/blsct/wallet/keyman.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ void KeyMan::SetHDSeed(const PrivateKey& key)
throw std::runtime_error(std::string(__func__) + ": AddSpendKey failed");

if (!AddViewKey(viewKey, viewKey.GetPublicKey()))
throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed");
throw std::runtime_error(std::string(__func__) + ": AddViewKey failed");

if (!AddKeyPubKey(tokenKey, tokenKey.GetPublicKey()))
throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed");
Expand All @@ -261,13 +261,39 @@ void KeyMan::SetHDSeed(const PrivateKey& key)
wallet::WalletBatch batch(m_storage.GetDatabase());
}

bool KeyMan::SetupGeneration(bool force)
bool KeyMan::SetupGeneration(const std::vector<unsigned char>& seed, const SeedType& type, bool force)
{
if ((CanGenerateKeys() && !force) || m_storage.IsLocked()) {
return false;
}

SetHDSeed(GenerateNewSeed());
if (seed.size() == 32) {
if (type == IMPORT_MASTER_KEY) {
MclScalar scalarSeed;
scalarSeed.SetVch(seed);
SetHDSeed(scalarSeed);
}
} else if (seed.size() == 80) {
if (type == IMPORT_VIEW_KEY) {
std::vector<unsigned char> viewVch(seed.begin(), seed.begin() + 32);
std::vector<unsigned char> spendingVch(seed.begin() + 32, seed.end());

MclScalar scalarView;
scalarView.SetVch(viewVch);

MclG1Point pointSpending;
pointSpending.SetVch(spendingVch);

if (!AddViewKey(scalarView, PrivateKey(scalarView).GetPublicKey()))
throw std::runtime_error(std::string(__func__) + ": AddViewKey failed");

if (!AddSpendKey(pointSpending))
throw std::runtime_error(std::string(__func__) + ": AddSpendKey failed");
}
} else {
SetHDSeed(GenerateNewSeed());
}

if (!NewSubAddressPool() || !NewSubAddressPool(-1) || !NewSubAddressPool(-2)) {
return false;
}
Expand Down Expand Up @@ -460,17 +486,15 @@ blsct::PrivateKey KeyMan::GetMasterSeedKey() const

blsct::PrivateKey KeyMan::GetPrivateViewKey() const
{
if (!IsHDEnabled())
throw std::runtime_error(strprintf("%s: the wallet has no HD enabled"));
if (!fViewKeyDefined)
throw std::runtime_error(strprintf("%s: the wallet has no view key available"));

auto viewId = m_hd_chain.view_id;

PrivateKey ret;

if (!GetKey(viewId, ret))
throw std::runtime_error(strprintf("%s: could not access the private view key", __func__));
return viewKey;
}

return ret;
blsct::PublicKey KeyMan::GetPublicSpendingKey() const
{
return spendPublicKey;
}

blsct::PrivateKey KeyMan::GetSpendingKey() const
Expand Down
6 changes: 4 additions & 2 deletions src/blsct/wallet/keyman.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <blsct/wallet/address.h>
#include <blsct/wallet/hdchain.h>
#include <blsct/wallet/helpers.h>
#include <blsct/wallet/import_wallet_type.h>
#include <blsct/wallet/keyring.h>
#include <logging.h>
#include <wallet/crypter.h>
Expand All @@ -34,7 +35,7 @@ class Manager
explicit Manager(wallet::WalletStorage& storage) : m_storage(storage) {}
virtual ~Manager(){};

virtual bool SetupGeneration(bool force = false) { return false; }
virtual bool SetupGeneration(const std::vector<unsigned char>& seed, const SeedType& type, bool force = false) { return false; }

/* Returns true if HD is enabled */
virtual bool IsHDEnabled() const { return false; }
Expand Down Expand Up @@ -72,7 +73,7 @@ class KeyMan : public Manager, public KeyRing
KeyMan(wallet::WalletStorage& storage, int64_t keypool_size)
: Manager(storage), KeyRing(), m_keypool_size(keypool_size) {}

bool SetupGeneration(bool force = false) override;
bool SetupGeneration(const std::vector<unsigned char>& seed, const SeedType& type = IMPORT_MASTER_KEY, bool force = false) override;
bool IsHDEnabled() const override;

/* Returns true if the wallet can generate new keys */
Expand Down Expand Up @@ -136,6 +137,7 @@ class KeyMan : public Manager, public KeyRing
CTxDestination GetDestination(const CTxOut& txout) const;
blsct::PrivateKey GetMasterSeedKey() const;
blsct::PrivateKey GetPrivateViewKey() const;
blsct::PublicKey GetPublicSpendingKey() const;
blsct::PrivateKey GetSpendingKey() const;
blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out) const;
blsct::PrivateKey GetSpendingKeyForOutput(const CTxOut& out, const CKeyID& id) const;
Expand Down
15 changes: 10 additions & 5 deletions src/blsct/wallet/txfactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,22 @@ class TxFactoryBase
{
protected:
CMutableTransaction tx;
std::map<TokenId, std::vector<UnsignedOutput>> vOutputs;
std::map<TokenId, std::vector<UnsignedInput>> vInputs;
std::map<TokenId, Amounts> nAmounts;
std::map<TokenId, std::vector<UnsignedOutput>>
vOutputs;
std::map<TokenId, std::vector<UnsignedInput>>
vInputs;
std::map<TokenId, Amounts>
nAmounts;

public:
TxFactoryBase(){};

void AddOutput(const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0, const bool& fSubtractFeeFromAmount = false);
bool AddInput(const CAmount& amount, const MclScalar& gamma, const blsct::PrivateKey& spendingKey, const TokenId& token_id, const COutPoint& outpoint, const bool& rbf = false);
std::optional<CMutableTransaction> BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = NORMAL, const bool& fSubtractedFee = false);
static std::optional<CMutableTransaction> CreateTransaction(const std::vector<InputCandidates>& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0);
std::optional<CMutableTransaction>
BuildTx(const blsct::DoublePublicKey& changeDestination, const CAmount& minStake = 0, const CreateTransactionType& type = NORMAL, const bool& fSubtractedFee = false);
static std::optional<CMutableTransaction>
CreateTransaction(const std::vector<InputCandidates>& inputCandidates, const blsct::DoublePublicKey& changeDestination, const SubAddress& destination, const CAmount& nAmount, std::string sMemo, const TokenId& token_id = TokenId(), const CreateTransactionType& type = NORMAL, const CAmount& minStake = 0);
static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const wallet::CoinFilterParams& coins_params, std::vector<InputCandidates>& inputCandidates) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
static void AddAvailableCoins(wallet::CWallet* wallet, blsct::KeyMan* blsct_km, const TokenId& token_id, const CreateTransactionType& type, std::vector<InputCandidates>& inputCandidates);
};
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#define BITCOIN_INTERFACES_WALLET_H

#include <addresstype.h>
#include <blsct/wallet/import_wallet_type.h>
#include <consensus/amount.h>
#include <interfaces/chain.h>
#include <pubkey.h>
Expand Down Expand Up @@ -323,7 +324,7 @@ class WalletLoader : public ChainClient
{
public:
//! Create new wallet.
virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;
virtual util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, const std::vector<unsigned char>& seed, const blsct::SeedType& type, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) = 0;

//! Load existing wallet.
virtual util::Result<std::unique_ptr<Wallet>> loadWallet(const std::string& name, std::vector<bilingual_str>& warnings) = 0;
Expand Down
2 changes: 1 addition & 1 deletion src/test/blsct/pos/pos_chain_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ BOOST_FIXTURE_TEST_CASE(StakedCommitment, TestBLSCTChain100Setup)

LOCK(wallet.cs_wallet);
auto blsct_km = wallet.GetOrCreateBLSCTKeyMan();
BOOST_CHECK(blsct_km->SetupGeneration(true));
BOOST_CHECK(blsct_km->SetupGeneration({}, blsct::IMPORT_MASTER_KEY, true));

auto recvAddress = std::get<blsct::DoublePublicKey>(blsct_km->GetNewDestination(0).value());

Expand Down
6 changes: 3 additions & 3 deletions src/test/blsct/wallet/txfactory_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ BOOST_FIXTURE_TEST_CASE(ismine_test, TestingSetup)

LOCK(wallet->cs_wallet);
auto blsct_km = wallet->GetOrCreateBLSCTKeyMan();
BOOST_CHECK(blsct_km->SetupGeneration(true));
BOOST_CHECK(blsct_km->SetupGeneration({}, blsct::IMPORT_MASTER_KEY, true));

auto recvAddress = std::get<blsct::DoublePublicKey>(blsct_km->GetNewDestination(0).value());

Expand Down Expand Up @@ -53,7 +53,7 @@ BOOST_FIXTURE_TEST_CASE(createtransaction_test, TestingSetup)

LOCK(wallet->cs_wallet);
auto blsct_km = wallet->GetOrCreateBLSCTKeyMan();
BOOST_CHECK(blsct_km->SetupGeneration(true));
BOOST_CHECK(blsct_km->SetupGeneration({}, blsct::IMPORT_MASTER_KEY, true));

auto recvAddress = std::get<blsct::DoublePublicKey>(blsct_km->GetNewDestination(0).value());

Expand Down Expand Up @@ -114,7 +114,7 @@ BOOST_FIXTURE_TEST_CASE(addinput_test, TestingSetup)

LOCK(wallet->cs_wallet);
auto blsct_km = wallet->GetOrCreateBLSCTKeyMan();
BOOST_CHECK(blsct_km->SetupGeneration(true));
BOOST_CHECK(blsct_km->SetupGeneration({}, blsct::IMPORT_MASTER_KEY, true));

auto recvAddress = std::get<blsct::DoublePublicKey>(blsct_km->GetNewDestination(0).value());

Expand Down
2 changes: 1 addition & 1 deletion src/test/blsct/wallet/validation_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ BOOST_FIXTURE_TEST_CASE(validation_test, TestingSetup)

LOCK(wallet->cs_wallet);
auto blsct_km = wallet->GetOrCreateBLSCTKeyMan();
BOOST_CHECK(blsct_km->SetupGeneration(true));
BOOST_CHECK(blsct_km->SetupGeneration({}, blsct::IMPORT_MASTER_KEY, true));

auto recvAddress = std::get<blsct::DoublePublicKey>(blsct_km->GetNewDestination(0).value());

Expand Down
4 changes: 2 additions & 2 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,7 +597,7 @@ class WalletLoaderImpl : public WalletLoader
void schedulerMockForward(std::chrono::seconds delta) override { Assert(m_context.scheduler)->MockForward(delta); }

//! WalletLoader methods
util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) override
util::Result<std::unique_ptr<Wallet>> createWallet(const std::string& name, const SecureString& passphrase, const std::vector<unsigned char>& seed, const blsct::SeedType& type, uint64_t wallet_creation_flags, std::vector<bilingual_str>& warnings) override
{
DatabaseOptions options;
DatabaseStatus status;
Expand All @@ -606,7 +606,7 @@ class WalletLoaderImpl : public WalletLoader
options.create_flags = wallet_creation_flags;
options.create_passphrase = passphrase;
bilingual_str error;
std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, /*load_on_start=*/true, options, status, error, warnings))};
std::unique_ptr<Wallet> wallet{MakeWallet(m_context, CreateWallet(m_context, name, seed, type, /*load_on_start=*/true, options, status, error, warnings))};
if (wallet) {
return wallet;
} else {
Expand Down
15 changes: 7 additions & 8 deletions src/wallet/rpc/backup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,26 +713,25 @@ RPCHelpMan getblsctseed()
}


RPCHelpMan getblsctviewkey()
RPCHelpMan getblsctauditkey()
{
return RPCHelpMan{
"getblsctviewkey",
"\nDumps the BLSCT wallet private view key, which can be used to observe the wallet history without being able to spend the transactions.\n"
"getblsctauditkey",
"\nDumps the BLSCT wallet audit key, which can be used to observe the wallet history without being able to spend the transactions.\n"
"Note: This command is only compatible with BLSCT wallets.\n",
{},
RPCResult{
RPCResult::Type::STR, "viewkey", "The BLSCT wallet private view key"},
RPCExamples{HelpExampleCli("getblsctviewkey", "") + HelpExampleRpc("getblsctseed", "")},
RPCResult::Type::STR, "auditkey", "The BLSCT wallet audit key"},
RPCExamples{HelpExampleCli("getblsctauditkey", "") + HelpExampleRpc("getblsctauditkey", "")},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue {
const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
if (!pwallet) return UniValue::VNULL;

const CWallet& wallet = *pwallet;
const blsct::KeyMan& blsct_km = EnsureConstBlsctKeyMan(wallet);

auto seed = blsct_km.GetMasterSeedKey();

return seed.GetScalar().GetString();
return strprintf("%s%s", blsct_km.GetPrivateViewKey().GetScalar().GetString(), HexStr(blsct_km.GetPublicSpendingKey().GetVch()));
;
},
};
}
Expand Down
22 changes: 19 additions & 3 deletions src/wallet/rpc/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,7 @@ static RPCHelpMan createwallet()
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
{"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
{"blsct", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a wallet with BLSCT keys."},
{"seed", RPCArg::Type::STR_HEX, RPCArg::Default{""}, "Create the wallet from the specified seed (can be a master seed or an audit key)."},
},
RPCResult{
RPCResult::Type::OBJ, "", "", {
Expand Down Expand Up @@ -413,15 +414,30 @@ static RPCHelpMan createwallet()
flags &= ~WALLET_FLAG_DESCRIPTORS;
}

std::vector<unsigned char> seed;
blsct::SeedType type = blsct::IMPORT_MASTER_KEY;
if (!request.params[9].isNull() && request.params[9].isStr()) {
seed = ParseHex(request.params[9].get_str());
}

if (seed.size() == 160) {
seed = ParseHex(request.params[10].get_str());
type = blsct::IMPORT_VIEW_KEY;
flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
} else if (seed.size() != 64) {
throw JSONRPCError(RPC_WALLET_ERROR, "Seed must be 64 (master) or 160 (view) characters long");
}

DatabaseOptions options;
DatabaseStatus status;
ReadDatabaseArgs(*context.args, options);
options.require_create = true;
options.create_flags = flags;
options.create_passphrase = passphrase;
bilingual_str error;

std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), seed, type, load_on_start, options, status, error, warnings);
if (!wallet) {
RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
throw JSONRPCError(code, error.original);
Expand Down Expand Up @@ -828,7 +844,7 @@ RPCHelpMan walletdisplayaddress();

// backup
RPCHelpMan getblsctseed();
RPCHelpMan getblsctviewkey();
RPCHelpMan getblsctauditkey();
RPCHelpMan dumpprivkey();
RPCHelpMan importprivkey();
RPCHelpMan importaddress();
Expand Down Expand Up @@ -908,7 +924,7 @@ Span<const CRPCCommand> GetWalletRPCCommands()
{"wallet", &getaddressinfo},
{"wallet", &getbalance},
{"wallet", &getblsctseed},
{"wallet", &getblsctviewkey},
{"wallet", &getblsctauditkey},
{"wallet", &getnewaddress},
{"wallet", &getrawchangeaddress},
{"wallet", &getreceivedbyaddress},
Expand Down
2 changes: 1 addition & 1 deletion src/wallet/test/walletload_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_load_verif_crypted_blsct, TestingSetup)
wallet->InitWalletFlags(wallet::WALLET_FLAG_BLSCT);
LOCK(wallet->cs_wallet);
auto blsct_km = wallet->GetOrCreateBLSCTKeyMan();
BOOST_CHECK(blsct_km->SetupGeneration(true));
BOOST_CHECK(blsct_km->SetupGeneration({}, blsct::IMPORT_MASTER_KEY, true));

// Get the keys in the wallet before encryption
auto masterKeysMetadata = blsct_km->GetHDChain();
Expand Down
4 changes: 2 additions & 2 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ std::shared_ptr<CWallet> LoadWallet(WalletContext& context, const std::string& n
return wallet;
}

std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string& name, const std::vector<unsigned char>& seed, const blsct::SeedType& type, std::optional<bool> load_on_start, DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error, std::vector<bilingual_str>& warnings)
{
uint64_t wallet_creation_flags = options.create_flags;
const SecureString& passphrase = options.create_passphrase;
Expand Down Expand Up @@ -449,7 +449,7 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
auto blsct_man = wallet->GetBLSCTKeyMan();

if (blsct_man) {
if (!blsct_man->SetupGeneration()) {
if (!blsct_man->SetupGeneration(seed, type)) {
error = Untranslated("Unable to generate initial blsct keys");
status = DatabaseStatus::FAILED_CREATE;
return nullptr;
Expand Down
Loading
Loading