Skip to content

Commit 979ed95

Browse files
committed
feat: implement mnemonic for descriptor wallets
1 parent 267693f commit 979ed95

File tree

16 files changed

+258
-60
lines changed

16 files changed

+258
-60
lines changed

src/bench/wallet_balance.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
2828
{
2929
LOCK(wallet.cs_wallet);
3030
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
31-
wallet.SetupDescriptorScriptPubKeyMans();
31+
wallet.SetupDescriptorScriptPubKeyMans("", "");
3232
if (wallet.LoadWallet() != DBErrors::LOAD_OK) assert(false);
3333
}
3434
auto handler = test_setup->m_node.chain->handleNotifications({&wallet, [](CWallet*) {}});

src/qt/test/addressbooktests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void TestAddAddressesToSendBook(interfaces::Node& node)
7676
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
7777
{
7878
LOCK(wallet->cs_wallet);
79-
wallet->SetupDescriptorScriptPubKeyMans();
79+
wallet->SetupDescriptorScriptPubKeyMans("", "");
8080
}
8181

8282
auto build_address = [wallet]() {

src/qt/test/wallettests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ void TestGUI(interfaces::Node& node)
126126
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
127127
{
128128
LOCK(wallet->cs_wallet);
129-
wallet->SetupDescriptorScriptPubKeyMans();
129+
wallet->SetupDescriptorScriptPubKeyMans("", "");
130130

131131
// Add the coinbase key
132132
FlatSigningProvider provider;

src/serialize.h

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <utility>
2727
#include <vector>
2828

29+
#include <support/allocators/secure.h>
2930
#include <prevector.h>
3031
#include <span.h>
3132

@@ -820,9 +821,16 @@ struct VectorFormatter
820821
/**
821822
* string
822823
*/
823-
template<typename Stream, typename C> void Serialize(Stream& os, const std::basic_string<C>& str);
824-
template<typename Stream, typename C> void Unserialize(Stream& is, std::basic_string<C>& str);
824+
template<typename Stream, typename A, typename B, typename C> void Serialize(Stream& os, const std::basic_string<A, B, C>& str);
825+
template<typename Stream, typename A, typename B, typename C> void Unserialize(Stream& is, std::basic_string<A, B, C>& str);
825826

827+
/**
828+
* SecureString
829+
*/
830+
/*
831+
template<typename Stream, typename C> void Serialize(Stream& os, const SecureString& str);
832+
template<typename Stream, typename C> void Unserialize(Stream& is, SecureString& str);
833+
*/
826834
/**
827835
* prevector
828836
* prevectors of unsigned char are a special case and are intended to be serialized as a single opaque blob.
@@ -951,16 +959,16 @@ struct DefaultFormatter
951959
/**
952960
* string
953961
*/
954-
template<typename Stream, typename C>
955-
void Serialize(Stream& os, const std::basic_string<C>& str)
962+
template<typename Stream, typename A, typename B, typename C>
963+
void Serialize(Stream& os, const std::basic_string<A, B, C>& str)
956964
{
957965
WriteCompactSize(os, str.size());
958966
if (!str.empty())
959967
os.write(MakeByteSpan(str));
960968
}
961969

962-
template<typename Stream, typename C>
963-
void Unserialize(Stream& is, std::basic_string<C>& str)
970+
template<typename Stream, typename A, typename B, typename C>
971+
void Unserialize(Stream& is, std::basic_string<A, B, C>& str)
964972
{
965973
unsigned int nSize = ReadCompactSize(is);
966974
str.resize(nSize);

src/wallet/rpc/backup.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,8 @@ RPCHelpMan listdescriptors()
19691969
{
19701970
{RPCResult::Type::OBJ, "", "", {
19711971
{RPCResult::Type::STR, "desc", "Descriptor string representation"},
1972+
{RPCResult::Type::STR, "mnemonic", "The mnemonic for this Descriptor wallet (bip39, english words). Presented only if private=true and created with mnemonic"},
1973+
{RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this Descriptor wallet (bip39). Presented only if private=true and created with mnemonic"},
19721974
{RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
19731975
{RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
19741976
{RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"},
@@ -2015,6 +2017,14 @@ RPCHelpMan listdescriptors()
20152017
if (!desc_spk_man->GetDescriptorString(descriptor, priv)) {
20162018
throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string.");
20172019
}
2020+
if (priv) {
2021+
SecureString mnemonic;
2022+
SecureString mnemonic_passphrase;
2023+
if (desc_spk_man->GetMnemonicString(mnemonic, mnemonic_passphrase) && !mnemonic.empty()) {
2024+
spk.pushKV("mnemonic", mnemonic.c_str());
2025+
spk.pushKV("mnemonicpassphrase", mnemonic_passphrase.c_str());
2026+
}
2027+
}
20182028
spk.pushKV("desc", descriptor);
20192029
spk.pushKV("timestamp", wallet_descriptor.creation_time);
20202030
const bool active = active_spk_mans.count(desc_spk_man) != 0;

src/wallet/scriptpubkeyman.cpp

Lines changed: 128 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <util/strencodings.h>
1414
#include <util/system.h>
1515
#include <util/translation.h>
16+
#include <wallet/bip39.h>
1617
#include <wallet/scriptpubkeyman.h>
1718

1819
namespace wallet {
@@ -1849,6 +1850,7 @@ bool DescriptorScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master
18491850
keyFail = true;
18501851
break;
18511852
}
1853+
// TODO: test for mnemonics
18521854
keyPass = true;
18531855
if (m_decryption_thoroughly_checked)
18541856
break;
@@ -1875,15 +1877,34 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle
18751877
{
18761878
const CKey &key = key_in.second;
18771879
CPubKey pubkey = key.GetPubKey();
1880+
assert(pubkey.GetID() == key_in.first);
1881+
const auto mnemonic_in = m_mnemonics.find(key_in.first);
18781882
CKeyingMaterial secret(key.begin(), key.end());
18791883
std::vector<unsigned char> crypted_secret;
18801884
if (!EncryptSecret(master_key, secret, pubkey.GetHash(), crypted_secret)) {
18811885
return false;
18821886
}
1887+
std::vector<unsigned char> crypted_mnemonic;
1888+
std::vector<unsigned char> crypted_mnemonic_passphrase;
1889+
if (mnemonic_in != m_mnemonics.end()) {
1890+
const Mnemonic mnemonic = mnemonic_in->second;
1891+
1892+
CKeyingMaterial mnemonic_secret(mnemonic.first.begin(), mnemonic.first.end());
1893+
CKeyingMaterial mnemonic_passphrase_secret(mnemonic.second.begin(), mnemonic.second.end());
1894+
if (!EncryptSecret(master_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) {
1895+
return false;
1896+
}
1897+
if (!EncryptSecret(master_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) {
1898+
return false;
1899+
}
1900+
}
1901+
18831902
m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
1884-
batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret);
1903+
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase);
1904+
batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase);
18851905
}
18861906
m_map_keys.clear();
1907+
m_mnemonics.clear();
18871908
return true;
18881909
}
18891910

@@ -2008,12 +2029,12 @@ void DescriptorScriptPubKeyMan::AddDescriptorKey(const CKey& key, const CPubKey
20082029
{
20092030
LOCK(cs_desc_man);
20102031
WalletBatch batch(m_storage.GetDatabase());
2011-
if (!AddDescriptorKeyWithDB(batch, key, pubkey)) {
2032+
if (!AddDescriptorKeyWithDB(batch, key, pubkey, "", "")) {
20122033
throw std::runtime_error(std::string(__func__) + ": writing descriptor private key failed");
20132034
}
20142035
}
20152036

2016-
bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey)
2037+
bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase)
20172038
{
20182039
AssertLockHeld(cs_desc_man);
20192040
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
@@ -2030,22 +2051,37 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
20302051
}
20312052

20322053
std::vector<unsigned char> crypted_secret;
2054+
std::vector<unsigned char> crypted_mnemonic;
2055+
std::vector<unsigned char> crypted_mnemonic_passphrase;
20332056
CKeyingMaterial secret(key.begin(), key.end());
2057+
CKeyingMaterial mnemonic_secret(mnemonic.begin(), mnemonic.end());
2058+
CKeyingMaterial mnemonic_passphrase_secret(mnemonic_passphrase.begin(), mnemonic_passphrase.end());
20342059
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
2035-
return EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret);
2060+
if (!EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret)) return false;
2061+
if (!mnemonic.empty()) {
2062+
if (!EncryptSecret(encryption_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) {
2063+
return false;
2064+
}
2065+
if (!EncryptSecret(encryption_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) {
2066+
return false;
2067+
}
2068+
}
2069+
return true;
20362070
})) {
20372071
return false;
20382072
}
20392073

20402074
m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
2041-
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret);
2075+
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase);
2076+
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase);
20422077
} else {
20432078
m_map_keys[pubkey.GetID()] = key;
2044-
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey());
2079+
m_mnemonics[pubkey.GetID()] = make_pair(mnemonic, mnemonic_passphrase);
2080+
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey(), mnemonic, mnemonic_passphrase);
20452081
}
20462082
}
20472083

2048-
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, bool internal)
2084+
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, bool internal)
20492085
{
20502086
LOCK(cs_desc_man);
20512087
assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
@@ -2055,6 +2091,16 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
20552091
return false;
20562092
}
20572093

2094+
if (!secure_mnemonic.empty()) {
2095+
// TODO: remove duplicated code with AddKey()
2096+
SecureVector seed_key_tmp;
2097+
CMnemonic::ToSeed(secure_mnemonic, secure_mnemonic_passphrase, seed_key_tmp);
2098+
2099+
CExtKey master_key_tmp;
2100+
master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp));
2101+
assert(master_key == master_key_tmp);
2102+
}
2103+
20582104
int64_t creation_time = GetTime();
20592105

20602106
std::string xpub = EncodeExtPubKey(master_key.Neuter());
@@ -2075,7 +2121,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
20752121

20762122
// Store the master private key, and descriptor
20772123
WalletBatch batch(m_storage.GetDatabase());
2078-
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())) {
2124+
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey(), secure_mnemonic, secure_mnemonic_passphrase)) {
20792125
throw std::runtime_error(std::string(__func__) + ": writing descriptor master private key failed");
20802126
}
20812127
if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) {
@@ -2356,21 +2402,34 @@ void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache)
23562402
}
23572403
}
23582404

2359-
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key)
2405+
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase)
23602406
{
23612407
LOCK(cs_desc_man);
2408+
if (!mnemonic.empty()) {
2409+
// TODO: remove duplicated code with AddKey()
2410+
SecureVector seed_key_tmp;
2411+
CMnemonic::ToSeed(mnemonic, mnemonic_passphrase, seed_key_tmp);
2412+
2413+
CExtKey master_key_tmp;
2414+
master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp));
2415+
assert(key == master_key_tmp.key);
2416+
}
2417+
23622418
m_map_keys[key_id] = key;
2419+
m_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase);
2420+
23632421
return true;
23642422
}
23652423

2366-
bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key)
2424+
bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key, const std::vector<unsigned char>& mnemonic,const std::vector<unsigned char>& mnemonic_passphrase)
23672425
{
23682426
LOCK(cs_desc_man);
23692427
if (!m_map_keys.empty()) {
23702428
return false;
23712429
}
23722430

23732431
m_map_crypted_keys[key_id] = make_pair(pubkey, crypted_key);
2432+
m_crypted_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase);
23742433
return true;
23752434
}
23762435

@@ -2412,7 +2471,6 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool
24122471

24132472
FlatSigningProvider provider;
24142473
provider.keys = GetKeys();
2415-
24162474
if (priv) {
24172475
// For the private version, always return the master key to avoid
24182476
// exposing child private keys. The risk implications of exposing child
@@ -2423,6 +2481,65 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool
24232481
return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache);
24242482
}
24252483

2484+
bool DescriptorScriptPubKeyMan::GetMnemonicString(SecureString& mnemonic_out, SecureString& mnemonic_passphrase_out) const
2485+
{
2486+
LOCK(cs_desc_man);
2487+
2488+
mnemonic_out.clear();
2489+
mnemonic_passphrase_out.clear();
2490+
2491+
if (m_mnemonics.empty() && m_crypted_mnemonics.empty()) {
2492+
WalletLogPrintf("%s: Descriptor wallet has no mnemonic defined\n", __func__);
2493+
return false;
2494+
}
2495+
if (m_storage.IsLocked(false)) return false;
2496+
2497+
if (m_mnemonics.size() + m_crypted_mnemonics.size() > 1) {
2498+
WalletLogPrintf("%s: ERROR: One descriptor has multiple mnemonics. Can't match it\n", __func__);
2499+
return false;
2500+
}
2501+
if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked(true)) {
2502+
if (!m_crypted_mnemonics.empty() && m_map_crypted_keys.size() != 1) {
2503+
WalletLogPrintf("%s: ERROR: can't choose encryption key for mnemonic out of %lld\n", __func__, m_map_crypted_keys.size());
2504+
return false;
2505+
}
2506+
const CPubKey& pubkey = m_map_crypted_keys.begin()->second.first;
2507+
const auto mnemonic = m_crypted_mnemonics.begin()->second;
2508+
const std::vector<unsigned char>& crypted_mnemonic = mnemonic.first;
2509+
const std::vector<unsigned char>& crypted_mnemonic_passphrase = mnemonic.second;
2510+
2511+
SecureVector mnemonic_v;
2512+
SecureVector mnemonic_passphrase_v;
2513+
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
2514+
return DecryptSecret(encryption_key, crypted_mnemonic, pubkey.GetHash(), mnemonic_v);
2515+
})) {
2516+
LogPrintf("can't decrypt mnemonic pubkey %s crypted: %s\n", pubkey.GetHash().ToString(), HexStr(crypted_mnemonic));
2517+
return false;
2518+
}
2519+
if (!crypted_mnemonic_passphrase.empty()) {
2520+
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
2521+
return DecryptSecret(encryption_key, crypted_mnemonic_passphrase, pubkey.GetHash(), mnemonic_passphrase_v);
2522+
})) {
2523+
LogPrintf("can't decrypt mnemonic-passphrase\n");
2524+
return false;
2525+
}
2526+
}
2527+
2528+
std::copy(mnemonic_v.begin(), mnemonic_v.end(), std::back_inserter(mnemonic_out));
2529+
std::copy(mnemonic_passphrase_v.begin(), mnemonic_passphrase_v.end(), std::back_inserter(mnemonic_passphrase_out));
2530+
2531+
return true;
2532+
}
2533+
if (m_mnemonics.empty()) return false;
2534+
2535+
const auto mnemonic_it = m_mnemonics.begin();
2536+
2537+
mnemonic_out = mnemonic_it->second.first;
2538+
mnemonic_passphrase_out = mnemonic_it->second.second;
2539+
2540+
return true;
2541+
}
2542+
24262543
void DescriptorScriptPubKeyMan::UpgradeDescriptorCache()
24272544
{
24282545
LOCK(cs_desc_man);

src/wallet/scriptpubkeyman.h

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -511,17 +511,25 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
511511
using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<unsigned char>>>;
512512
using KeyMap = std::map<CKeyID, CKey>;
513513

514+
using Mnemonic = std::pair<SecureString, SecureString>;
515+
using MnemonicMap = std::map<CKeyID, Mnemonic>;
516+
using CryptedMnemonic = std::pair<std::vector<unsigned char>, std::vector<unsigned char>>;
517+
using CryptedMnemonicMap = std::map<CKeyID, CryptedMnemonic>;
518+
514519
ScriptPubKeyMap m_map_script_pub_keys GUARDED_BY(cs_desc_man);
515520
PubKeyMap m_map_pubkeys GUARDED_BY(cs_desc_man);
516521
int32_t m_max_cached_index = -1;
517522

518523
KeyMap m_map_keys GUARDED_BY(cs_desc_man);
519524
CryptedKeyMap m_map_crypted_keys GUARDED_BY(cs_desc_man);
520525

526+
MnemonicMap m_mnemonics GUARDED_BY(cs_desc_man);
527+
CryptedMnemonicMap m_crypted_mnemonics GUARDED_BY(cs_desc_man);
528+
521529
//! keeps track of whether Unlock has run a thorough check before
522530
bool m_decryption_thoroughly_checked = false;
523531

524-
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
532+
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
525533

526534
KeyMap GetKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
527535

@@ -564,7 +572,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
564572
bool IsHDEnabled() const override;
565573

566574
//! Setup descriptors based on the given CExtkey
567-
bool SetupDescriptorGeneration(const CExtKey& master_key, bool internal);
575+
bool SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, bool internal);
568576

569577
bool HavePrivateKeys() const override;
570578

@@ -590,8 +598,8 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
590598

591599
void SetCache(const DescriptorCache& cache);
592600

593-
bool AddKey(const CKeyID& key_id, const CKey& key);
594-
bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key);
601+
bool AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase);
602+
bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key, const std::vector<unsigned char>& mnemonic,const std::vector<unsigned char>& mnemonic_passphrase);
595603

596604
bool HasWalletDescriptor(const WalletDescriptor& desc) const;
597605
void UpdateWalletDescriptor(WalletDescriptor& descriptor);
@@ -603,6 +611,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
603611
const std::vector<CScript> GetScriptPubKeys() const;
604612

605613
bool GetDescriptorString(std::string& out, const bool priv) const;
614+
bool GetMnemonicString(SecureString& mnemonic_out, SecureString& mnemonic_passphrase_out) const;
606615

607616
void UpgradeDescriptorCache();
608617
};

0 commit comments

Comments
 (0)