Skip to content

Commit e3df697

Browse files
committed
feat: implement mnemonic for descriptor wallets
1 parent f33f584 commit e3df697

File tree

10 files changed

+250
-50
lines changed

10 files changed

+250
-50
lines changed

src/serialize.h

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

28+
#include <support/allocators/secure.h>
2829
#include <prevector.h>
2930
#include <span.h>
3031

@@ -816,9 +817,16 @@ struct VectorFormatter
816817
/**
817818
* string
818819
*/
819-
template<typename Stream, typename C> void Serialize(Stream& os, const std::basic_string<C>& str);
820-
template<typename Stream, typename C> void Unserialize(Stream& is, std::basic_string<C>& str);
820+
template<typename Stream, typename A, typename B, typename C> void Serialize(Stream& os, const std::basic_string<A, B, C>& str);
821+
template<typename Stream, typename A, typename B, typename C> void Unserialize(Stream& is, std::basic_string<A, B, C>& str);
821822

823+
/**
824+
* SecureString
825+
*/
826+
/*
827+
template<typename Stream, typename C> void Serialize(Stream& os, const SecureString& str);
828+
template<typename Stream, typename C> void Unserialize(Stream& is, SecureString& str);
829+
*/
822830
/**
823831
* prevector
824832
* prevectors of unsigned char are a special case and are intended to be serialized as a single opaque blob.
@@ -947,16 +955,16 @@ struct DefaultFormatter
947955
/**
948956
* string
949957
*/
950-
template<typename Stream, typename C>
951-
void Serialize(Stream& os, const std::basic_string<C>& str)
958+
template<typename Stream, typename A, typename B, typename C>
959+
void Serialize(Stream& os, const std::basic_string<A, B, C>& str)
952960
{
953961
WriteCompactSize(os, str.size());
954962
if (!str.empty())
955963
os.write(MakeByteSpan(str));
956964
}
957965

958-
template<typename Stream, typename C>
959-
void Unserialize(Stream& is, std::basic_string<C>& str)
966+
template<typename Stream, typename A, typename B, typename C>
967+
void Unserialize(Stream& is, std::basic_string<A, B, C>& str)
960968
{
961969
unsigned int nSize = ReadCompactSize(is);
962970
str.resize(nSize);

src/wallet/rpcdump.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1964,6 +1964,8 @@ RPCHelpMan listdescriptors()
19641964
{
19651965
{RPCResult::Type::OBJ, "", "", {
19661966
{RPCResult::Type::STR, "desc", "Descriptor string representation"},
1967+
{RPCResult::Type::STR, "mnemonic", "The mnemonic for this Descriptor wallet (bip39, english words). Presented only if private=true and created with mnemonic"},
1968+
{RPCResult::Type::STR, "mnemonicpassphrase", "The mnemonic passphrase for this Descriptor wallet (bip39). Presented only if private=true and created with mnemonic"},
19671969
{RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
19681970
{RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
19691971
{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"},
@@ -2010,6 +2012,14 @@ RPCHelpMan listdescriptors()
20102012
if (!desc_spk_man->GetDescriptorString(descriptor, priv)) {
20112013
throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string.");
20122014
}
2015+
if (priv) {
2016+
SecureString mnemonic;
2017+
SecureString mnemonic_passphrase;
2018+
if (desc_spk_man->GetMnemonicString(mnemonic, mnemonic_passphrase) && !mnemonic.empty()) {
2019+
spk.pushKV("mnemonic", mnemonic.c_str());
2020+
spk.pushKV("mnemonicpassphrase", mnemonic_passphrase.c_str());
2021+
}
2022+
}
20132023
spk.pushKV("desc", descriptor);
20142024
spk.pushKV("timestamp", wallet_descriptor.creation_time);
20152025
const bool active = active_spk_mans.count(desc_spk_man) != 0;

src/wallet/scriptpubkeyman.cpp

Lines changed: 129 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
bool LegacyScriptPubKeyMan::GetNewDestination(CTxDestination& dest, bilingual_str& error)
@@ -1844,6 +1845,7 @@ bool DescriptorScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master
18441845
keyFail = true;
18451846
break;
18461847
}
1848+
// TODO: test for mnemonics
18471849
keyPass = true;
18481850
if (m_decryption_thoroughly_checked)
18491851
break;
@@ -1870,15 +1872,34 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle
18701872
{
18711873
const CKey &key = key_in.second;
18721874
CPubKey pubkey = key.GetPubKey();
1875+
assert(pubkey.GetID() == key_in.first);
1876+
const auto mnemonic_in = m_mnemonics.find(key_in.first);
18731877
CKeyingMaterial secret(key.begin(), key.end());
18741878
std::vector<unsigned char> crypted_secret;
18751879
if (!EncryptSecret(master_key, secret, pubkey.GetHash(), crypted_secret)) {
18761880
return false;
18771881
}
1882+
std::vector<unsigned char> crypted_mnemonic;
1883+
std::vector<unsigned char> crypted_mnemonic_passphrase;
1884+
if (mnemonic_in != m_mnemonics.end()) {
1885+
const Mnemonic mnemonic = mnemonic_in->second;
1886+
1887+
CKeyingMaterial mnemonic_secret(mnemonic.first.begin(), mnemonic.first.end());
1888+
CKeyingMaterial mnemonic_passphrase_secret(mnemonic.second.begin(), mnemonic.second.end());
1889+
if (!EncryptSecret(master_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) {
1890+
return false;
1891+
}
1892+
if (!EncryptSecret(master_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) {
1893+
return false;
1894+
}
1895+
}
1896+
18781897
m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
1879-
batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret);
1898+
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase);
1899+
batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase);
18801900
}
18811901
m_map_keys.clear();
1902+
m_mnemonics.clear();
18821903
return true;
18831904
}
18841905

@@ -2003,12 +2024,13 @@ void DescriptorScriptPubKeyMan::AddDescriptorKey(const CKey& key, const CPubKey
20032024
{
20042025
LOCK(cs_desc_man);
20052026
WalletBatch batch(m_storage.GetDatabase());
2006-
if (!AddDescriptorKeyWithDB(batch, key, pubkey)) {
2027+
// TODO: add mnemonic here too
2028+
if (!AddDescriptorKeyWithDB(batch, key, pubkey, "", "")) {
20072029
throw std::runtime_error(std::string(__func__) + ": writing descriptor private key failed");
20082030
}
20092031
}
20102032

2011-
bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey)
2033+
bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase)
20122034
{
20132035
AssertLockHeld(cs_desc_man);
20142036
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
@@ -2025,22 +2047,37 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const
20252047
}
20262048

20272049
std::vector<unsigned char> crypted_secret;
2050+
std::vector<unsigned char> crypted_mnemonic;
2051+
std::vector<unsigned char> crypted_mnemonic_passphrase;
20282052
CKeyingMaterial secret(key.begin(), key.end());
2053+
CKeyingMaterial mnemonic_secret(mnemonic.begin(), mnemonic.end());
2054+
CKeyingMaterial mnemonic_passphrase_secret(mnemonic_passphrase.begin(), mnemonic_passphrase.end());
20292055
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
2030-
return EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret);
2056+
if (!EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret)) return false;
2057+
if (!mnemonic.empty()) {
2058+
if (!EncryptSecret(encryption_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) {
2059+
return false;
2060+
}
2061+
if (!EncryptSecret(encryption_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) {
2062+
return false;
2063+
}
2064+
}
2065+
return true;
20312066
})) {
20322067
return false;
20332068
}
20342069

20352070
m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret);
2036-
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret);
2071+
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase);
2072+
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase);
20372073
} else {
20382074
m_map_keys[pubkey.GetID()] = key;
2039-
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey());
2075+
m_mnemonics[pubkey.GetID()] = make_pair(mnemonic, mnemonic_passphrase);
2076+
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey(), mnemonic, mnemonic_passphrase);
20402077
}
20412078
}
20422079

2043-
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, bool internal)
2080+
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, bool internal)
20442081
{
20452082
LOCK(cs_desc_man);
20462083
assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
@@ -2050,6 +2087,16 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
20502087
return false;
20512088
}
20522089

2090+
if (!secure_mnemonic.empty()) {
2091+
// TODO: remove duplicated code with AddKey()
2092+
SecureVector seed_key_tmp;
2093+
CMnemonic::ToSeed(secure_mnemonic, secure_mnemonic_passphrase, seed_key_tmp);
2094+
2095+
CExtKey master_key_tmp;
2096+
master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp));
2097+
assert(master_key == master_key_tmp);
2098+
}
2099+
20532100
int64_t creation_time = GetTime();
20542101

20552102
std::string xpub = EncodeExtPubKey(master_key.Neuter());
@@ -2070,7 +2117,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_
20702117

20712118
// Store the master private key, and descriptor
20722119
WalletBatch batch(m_storage.GetDatabase());
2073-
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())) {
2120+
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey(), secure_mnemonic, secure_mnemonic_passphrase)) {
20742121
throw std::runtime_error(std::string(__func__) + ": writing descriptor master private key failed");
20752122
}
20762123
if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) {
@@ -2354,21 +2401,34 @@ void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache)
23542401
}
23552402
}
23562403

2357-
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key)
2404+
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase)
23582405
{
23592406
LOCK(cs_desc_man);
2407+
if (!mnemonic.empty()) {
2408+
// TODO: remove duplicated code with AddKey()
2409+
SecureVector seed_key_tmp;
2410+
CMnemonic::ToSeed(mnemonic, mnemonic_passphrase, seed_key_tmp);
2411+
2412+
CExtKey master_key_tmp;
2413+
master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp));
2414+
assert(key == master_key_tmp.key);
2415+
}
2416+
23602417
m_map_keys[key_id] = key;
2418+
m_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase);
2419+
23612420
return true;
23622421
}
23632422

2364-
bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key)
2423+
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)
23652424
{
23662425
LOCK(cs_desc_man);
23672426
if (!m_map_keys.empty()) {
23682427
return false;
23692428
}
23702429

23712430
m_map_crypted_keys[key_id] = make_pair(pubkey, crypted_key);
2431+
m_crypted_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase);
23722432
return true;
23732433
}
23742434

@@ -2410,7 +2470,6 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool
24102470

24112471
FlatSigningProvider provider;
24122472
provider.keys = GetKeys();
2413-
24142473
if (priv) {
24152474
// For the private version, always return the master key to avoid
24162475
// exposing child private keys. The risk implications of exposing child
@@ -2421,6 +2480,65 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool
24212480
return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache);
24222481
}
24232482

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

src/wallet/scriptpubkeyman.h

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

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

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

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

523-
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
531+
bool AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) EXCLUSIVE_LOCKS_REQUIRED(cs_desc_man);
524532

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

@@ -562,7 +570,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
562570
bool IsHDEnabled() const override;
563571

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

567575
bool HavePrivateKeys() const override;
568576

@@ -588,8 +596,8 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
588596

589597
void SetCache(const DescriptorCache& cache);
590598

591-
bool AddKey(const CKeyID& key_id, const CKey& key);
592-
bool AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key);
599+
bool AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase);
600+
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);
593601

594602
bool HasWalletDescriptor(const WalletDescriptor& desc) const;
595603
void UpdateWalletDescriptor(WalletDescriptor& descriptor);
@@ -601,6 +609,7 @@ class DescriptorScriptPubKeyMan : public ScriptPubKeyMan
601609
const std::vector<CScript> GetScriptPubKeys() const;
602610

603611
bool GetDescriptorString(std::string& out, const bool priv) const;
612+
bool GetMnemonicString(SecureString& mnemonic_out, SecureString& mnemonic_passphrase_out) const;
604613

605614
void UpgradeDescriptorCache();
606615
};

0 commit comments

Comments
 (0)