forked from bitcoin/bitcoin
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat: mnemonic support for descriptor wallets #6570
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
Open
knst
wants to merge
12
commits into
dashpay:develop
Choose a base branch
from
knst:mnemonic-descriptors
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
ec433a3
refactor: drop -usehd=1 from some functional tests so far as it is de…
knst 5c445a6
fix: expand text commentary for RPC encryptwallet
knst 5201390
chore: add todo to wallet_descriptor.py for importdescriptor RPC
knst be780d5
feat: implement mnemonic for descriptor wallets
knst 758757c
test: make functional test wallet_mnemonicbits.py works for descripto…
knst c05f943
fixup
knst 5b4db62
fix: text message for HD wallets (both descriptors and non-descriptors)
knst a8f3f61
fix: do not reset seed / mnemonic for descriptor wallets in case of e…
knst c83e1db
test: adjust commentary, ensure cb private key is "inactive" descriptor
knst dbe7673
fix: intermittent error in wallet_mnemonic.py due to different order …
knst f9d26e5
fix: better raise error in wallet_mnemonic
knst 39b3da7
test: multiple fixes in wallet_mnemonics to fix review comments
knst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
#include <util/strencodings.h> | ||
#include <util/system.h> | ||
#include <util/translation.h> | ||
#include <wallet/bip39.h> | ||
#include <wallet/scriptpubkeyman.h> | ||
|
||
bool LegacyScriptPubKeyMan::GetNewDestination(CTxDestination& dest, bilingual_str& error) | ||
|
@@ -1848,6 +1849,7 @@ bool DescriptorScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master | |
keyFail = true; | ||
break; | ||
} | ||
// TODO: test for mnemonics | ||
keyPass = true; | ||
if (m_decryption_thoroughly_checked) | ||
break; | ||
|
@@ -1874,15 +1876,34 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle | |
{ | ||
const CKey &key = key_in.second; | ||
CPubKey pubkey = key.GetPubKey(); | ||
assert(pubkey.GetID() == key_in.first); | ||
const auto mnemonic_in = m_mnemonics.find(key_in.first); | ||
CKeyingMaterial secret(key.begin(), key.end()); | ||
std::vector<unsigned char> crypted_secret; | ||
if (!EncryptSecret(master_key, secret, pubkey.GetHash(), crypted_secret)) { | ||
return false; | ||
} | ||
std::vector<unsigned char> crypted_mnemonic; | ||
std::vector<unsigned char> crypted_mnemonic_passphrase; | ||
if (mnemonic_in != m_mnemonics.end()) { | ||
const Mnemonic mnemonic = mnemonic_in->second; | ||
|
||
CKeyingMaterial mnemonic_secret(mnemonic.first.begin(), mnemonic.first.end()); | ||
CKeyingMaterial mnemonic_passphrase_secret(mnemonic.second.begin(), mnemonic.second.end()); | ||
if (!EncryptSecret(master_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) { | ||
return false; | ||
} | ||
if (!EncryptSecret(master_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) { | ||
return false; | ||
} | ||
} | ||
|
||
m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret); | ||
batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret); | ||
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase); | ||
batch->WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase); | ||
} | ||
m_map_keys.clear(); | ||
m_mnemonics.clear(); | ||
return true; | ||
} | ||
|
||
|
@@ -2007,12 +2028,13 @@ void DescriptorScriptPubKeyMan::AddDescriptorKey(const CKey& key, const CPubKey | |
{ | ||
LOCK(cs_desc_man); | ||
WalletBatch batch(m_storage.GetDatabase()); | ||
if (!AddDescriptorKeyWithDB(batch, key, pubkey)) { | ||
// TODO: add mnemonic here too | ||
if (!AddDescriptorKeyWithDB(batch, key, pubkey, "", "")) { | ||
throw std::runtime_error(std::string(__func__) + ": writing descriptor private key failed"); | ||
} | ||
} | ||
|
||
bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey) | ||
bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const CKey& key, const CPubKey &pubkey, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) | ||
{ | ||
AssertLockHeld(cs_desc_man); | ||
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); | ||
|
@@ -2029,22 +2051,37 @@ bool DescriptorScriptPubKeyMan::AddDescriptorKeyWithDB(WalletBatch& batch, const | |
} | ||
|
||
std::vector<unsigned char> crypted_secret; | ||
std::vector<unsigned char> crypted_mnemonic; | ||
std::vector<unsigned char> crypted_mnemonic_passphrase; | ||
CKeyingMaterial secret(key.begin(), key.end()); | ||
CKeyingMaterial mnemonic_secret(mnemonic.begin(), mnemonic.end()); | ||
CKeyingMaterial mnemonic_passphrase_secret(mnemonic_passphrase.begin(), mnemonic_passphrase.end()); | ||
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { | ||
return EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret); | ||
if (!EncryptSecret(encryption_key, secret, pubkey.GetHash(), crypted_secret)) return false; | ||
if (!mnemonic.empty()) { | ||
if (!EncryptSecret(encryption_key, mnemonic_secret, pubkey.GetHash(), crypted_mnemonic)) { | ||
return false; | ||
} | ||
if (!EncryptSecret(encryption_key, mnemonic_passphrase_secret, pubkey.GetHash(), crypted_mnemonic_passphrase)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
})) { | ||
return false; | ||
} | ||
|
||
m_map_crypted_keys[pubkey.GetID()] = make_pair(pubkey, crypted_secret); | ||
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret); | ||
m_crypted_mnemonics[pubkey.GetID()] = make_pair(crypted_mnemonic, crypted_mnemonic_passphrase); | ||
return batch.WriteCryptedDescriptorKey(GetID(), pubkey, crypted_secret, crypted_mnemonic, crypted_mnemonic_passphrase); | ||
} else { | ||
m_map_keys[pubkey.GetID()] = key; | ||
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey()); | ||
m_mnemonics[pubkey.GetID()] = make_pair(mnemonic, mnemonic_passphrase); | ||
return batch.WriteDescriptorKey(GetID(), pubkey, key.GetPrivKey(), mnemonic, mnemonic_passphrase); | ||
} | ||
} | ||
|
||
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, bool internal) | ||
bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_key, const SecureString& secure_mnemonic, const SecureString& secure_mnemonic_passphrase, bool internal) | ||
{ | ||
LOCK(cs_desc_man); | ||
assert(m_storage.IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)); | ||
|
@@ -2054,6 +2091,16 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ | |
return false; | ||
} | ||
|
||
if (!secure_mnemonic.empty()) { | ||
// TODO: remove duplicated code with AddKey() | ||
SecureVector seed_key_tmp; | ||
CMnemonic::ToSeed(secure_mnemonic, secure_mnemonic_passphrase, seed_key_tmp); | ||
|
||
CExtKey master_key_tmp; | ||
master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp)); | ||
assert(master_key == master_key_tmp); | ||
} | ||
|
||
int64_t creation_time = GetTime(); | ||
|
||
std::string xpub = EncodeExtPubKey(master_key.Neuter()); | ||
|
@@ -2074,7 +2121,7 @@ bool DescriptorScriptPubKeyMan::SetupDescriptorGeneration(const CExtKey& master_ | |
|
||
// Store the master private key, and descriptor | ||
WalletBatch batch(m_storage.GetDatabase()); | ||
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey())) { | ||
if (!AddDescriptorKeyWithDB(batch, master_key.key, master_key.key.GetPubKey(), secure_mnemonic, secure_mnemonic_passphrase)) { | ||
throw std::runtime_error(std::string(__func__) + ": writing descriptor master private key failed"); | ||
} | ||
if (!batch.WriteDescriptor(GetID(), m_wallet_descriptor)) { | ||
|
@@ -2355,21 +2402,34 @@ void DescriptorScriptPubKeyMan::SetCache(const DescriptorCache& cache) | |
} | ||
} | ||
|
||
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key) | ||
bool DescriptorScriptPubKeyMan::AddKey(const CKeyID& key_id, const CKey& key, const SecureString& mnemonic, const SecureString& mnemonic_passphrase) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Avoid duplication with |
||
{ | ||
LOCK(cs_desc_man); | ||
if (!mnemonic.empty()) { | ||
// TODO: remove duplicated code with AddKey() | ||
SecureVector seed_key_tmp; | ||
CMnemonic::ToSeed(mnemonic, mnemonic_passphrase, seed_key_tmp); | ||
|
||
CExtKey master_key_tmp; | ||
master_key_tmp.SetSeed(MakeByteSpan(seed_key_tmp)); | ||
assert(key == master_key_tmp.key); | ||
} | ||
|
||
m_map_keys[key_id] = key; | ||
m_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase); | ||
|
||
return true; | ||
} | ||
|
||
bool DescriptorScriptPubKeyMan::AddCryptedKey(const CKeyID& key_id, const CPubKey& pubkey, const std::vector<unsigned char>& crypted_key) | ||
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) | ||
{ | ||
LOCK(cs_desc_man); | ||
if (!m_map_keys.empty()) { | ||
return false; | ||
} | ||
|
||
m_map_crypted_keys[key_id] = make_pair(pubkey, crypted_key); | ||
m_crypted_mnemonics[key_id] = make_pair(mnemonic, mnemonic_passphrase); | ||
return true; | ||
} | ||
|
||
|
@@ -2411,7 +2471,6 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool | |
|
||
FlatSigningProvider provider; | ||
provider.keys = GetKeys(); | ||
|
||
if (priv) { | ||
// For the private version, always return the master key to avoid | ||
// exposing child private keys. The risk implications of exposing child | ||
|
@@ -2422,6 +2481,65 @@ bool DescriptorScriptPubKeyMan::GetDescriptorString(std::string& out, const bool | |
return m_wallet_descriptor.descriptor->ToNormalizedString(provider, out, &m_wallet_descriptor.cache); | ||
} | ||
|
||
bool DescriptorScriptPubKeyMan::GetMnemonicString(SecureString& mnemonic_out, SecureString& mnemonic_passphrase_out) const | ||
{ | ||
LOCK(cs_desc_man); | ||
|
||
mnemonic_out.clear(); | ||
mnemonic_passphrase_out.clear(); | ||
|
||
if (m_mnemonics.empty() && m_crypted_mnemonics.empty()) { | ||
WalletLogPrintf("%s: Descriptor wallet has no mnemonic defined\n", __func__); | ||
return false; | ||
} | ||
if (m_storage.IsLocked(false)) return false; | ||
|
||
if (m_mnemonics.size() + m_crypted_mnemonics.size() > 1) { | ||
WalletLogPrintf("%s: ERROR: One descriptor has multiple mnemonics. Can't match it\n", __func__); | ||
return false; | ||
} | ||
if (m_storage.HasEncryptionKeys() && !m_storage.IsLocked(true)) { | ||
if (!m_crypted_mnemonics.empty() && m_map_crypted_keys.size() != 1) { | ||
WalletLogPrintf("%s: ERROR: can't choose encryption key for mnemonic out of %lld\n", __func__, m_map_crypted_keys.size()); | ||
return false; | ||
} | ||
const CPubKey& pubkey = m_map_crypted_keys.begin()->second.first; | ||
const auto mnemonic = m_crypted_mnemonics.begin()->second; | ||
const std::vector<unsigned char>& crypted_mnemonic = mnemonic.first; | ||
const std::vector<unsigned char>& crypted_mnemonic_passphrase = mnemonic.second; | ||
|
||
SecureVector mnemonic_v; | ||
SecureVector mnemonic_passphrase_v; | ||
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { | ||
return DecryptSecret(encryption_key, crypted_mnemonic, pubkey.GetHash(), mnemonic_v); | ||
})) { | ||
LogPrintf("can't decrypt mnemonic pubkey %s crypted: %s\n", pubkey.GetHash().ToString(), HexStr(crypted_mnemonic)); | ||
return false; | ||
} | ||
if (!crypted_mnemonic_passphrase.empty()) { | ||
if (!m_storage.WithEncryptionKey([&](const CKeyingMaterial& encryption_key) { | ||
return DecryptSecret(encryption_key, crypted_mnemonic_passphrase, pubkey.GetHash(), mnemonic_passphrase_v); | ||
})) { | ||
LogPrintf("can't decrypt mnemonic-passphrase\n"); | ||
return false; | ||
} | ||
} | ||
|
||
std::copy(mnemonic_v.begin(), mnemonic_v.end(), std::back_inserter(mnemonic_out)); | ||
std::copy(mnemonic_passphrase_v.begin(), mnemonic_passphrase_v.end(), std::back_inserter(mnemonic_passphrase_out)); | ||
|
||
return true; | ||
} | ||
if (m_mnemonics.empty()) return false; | ||
|
||
const auto mnemonic_it = m_mnemonics.begin(); | ||
|
||
mnemonic_out = mnemonic_it->second.first; | ||
mnemonic_passphrase_out = mnemonic_it->second.second; | ||
|
||
return true; | ||
} | ||
|
||
void DescriptorScriptPubKeyMan::UpgradeDescriptorCache() | ||
{ | ||
LOCK(cs_desc_man); | ||
|
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Refactor to reduce duplication in encryption logic.
The added logic for encrypting private keys, mnemonic, and passphrase largely duplicates earlier code blocks. Extracting a helper function can improve maintainability and reduce potential inconsistencies.