Skip to content

Commit 588a6e0

Browse files
Merge dashpay#6797: fix(rpc): return correct error codes in upgradetohd rpc
746e5f8 fix: always leave the wallet in a locked state afer `upgradetohd` (UdjinM6) b19f463 fix(rpc): return correct error codes in `upgradetohd` rpc (UdjinM6) Pull request description: ## Issue being fixed or feature implemented `upgradetohd` rpc returns wrong error codes ## What was done? Move wallet "upgrade to HD" code used in rpc only to `upgradetohd` rpc, return correct error codes. We also don't need all that logic to extract wallet master encryption key because it's already known when wallet is unlocked. ## How Has This Been Tested? Run tests ## Breaking Changes `upgradetohd` should return correct error codes now which could probably break things if you relied on them, see tests ## Checklist: - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have added or updated relevant unit/integration/functional/e2e tests - [ ] I have made corresponding changes to the documentation - [ ] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: knst: utACK 746e5f8 Tree-SHA512: 829d1f9d805f7a87f628a9c7f4ae93cb545c8113cea97f430639165dae124db17d389c2f5c663214b074da8ae541435ab7df2b672f78370fe883ac77b3cfed42
2 parents 83de4fa + 746e5f8 commit 588a6e0

File tree

4 files changed

+94
-155
lines changed

4 files changed

+94
-155
lines changed

src/wallet/rpc/wallet.cpp

Lines changed: 83 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -371,51 +371,98 @@ static RPCHelpMan upgradetohd()
371371
if (!pwallet) return NullUniValue;
372372

373373
bool generate_mnemonic = request.params[0].isNull() || request.params[0].get_str().empty();
374-
SecureString secureWalletPassphrase;
375-
secureWalletPassphrase.reserve(100);
376374

377-
if (request.params[2].isNull()) {
378-
if (pwallet->IsCrypted()) {
379-
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet encrypted but passphrase not supplied to RPC.");
375+
{
376+
LOCK(pwallet->cs_wallet);
377+
378+
SecureString secureWalletPassphrase;
379+
secureWalletPassphrase.reserve(100);
380+
381+
if (request.params[2].isNull()) {
382+
if (pwallet->IsCrypted()) {
383+
throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet encrypted but passphrase not supplied to RPC.");
384+
}
385+
} else {
386+
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
387+
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
388+
secureWalletPassphrase = request.params[2].get_str().c_str();
380389
}
381-
} else {
382-
// TODO: get rid of this .c_str() by implementing SecureString::operator=(std::string)
383-
// Alternately, find a way to make request.params[0] mlock()'d to begin with.
384-
secureWalletPassphrase = request.params[2].get_str().c_str();
385-
}
386390

387-
SecureString secureMnemonic;
388-
secureMnemonic.reserve(256);
389-
if (!generate_mnemonic) {
390-
secureMnemonic = request.params[0].get_str().c_str();
391-
}
391+
SecureString secureMnemonic;
392+
secureMnemonic.reserve(256);
393+
if (!generate_mnemonic) {
394+
secureMnemonic = request.params[0].get_str().c_str();
395+
}
392396

393-
SecureString secureMnemonicPassphrase;
394-
secureMnemonicPassphrase.reserve(256);
395-
if (!request.params[1].isNull()) {
396-
secureMnemonicPassphrase = request.params[1].get_str().c_str();
397-
}
397+
SecureString secureMnemonicPassphrase;
398+
secureMnemonicPassphrase.reserve(256);
399+
if (!request.params[1].isNull()) {
400+
secureMnemonicPassphrase = request.params[1].get_str().c_str();
401+
}
398402

399-
// TODO: breaking changes kept for v21!
400-
// instead upgradetohd let's use more straightforward 'sethdseed'
401-
constexpr bool is_v21 = false;
402-
const int previous_version{pwallet->GetVersion()};
403-
if (is_v21 && previous_version >= FEATURE_HD) {
404-
return JSONRPCError(RPC_WALLET_ERROR, "Already at latest version. Wallet version unchanged.");
405-
}
403+
// TODO: breaking changes kept for v21!
404+
// instead upgradetohd let's use more straightforward 'sethdseed'
405+
constexpr bool is_v21 = false;
406+
const int previous_version{pwallet->GetVersion()};
407+
if (is_v21 && previous_version >= FEATURE_HD) {
408+
return JSONRPCError(RPC_WALLET_ERROR, "Already at latest version. Wallet version unchanged.");
409+
}
406410

407-
bilingual_str error;
408-
const bool wallet_upgraded{pwallet->UpgradeToHD(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase, error)};
411+
// Do not do anything to HD wallets
412+
if (pwallet->IsHDEnabled()) {
413+
throw JSONRPCError(RPC_WALLET_ERROR, "Cannot upgrade a wallet to HD if it is already upgraded to HD");
414+
}
409415

410-
if (!secureWalletPassphrase.empty() && !pwallet->IsCrypted()) {
411-
if (!pwallet->EncryptWallet(secureWalletPassphrase)) {
412-
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet");
416+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
417+
throw JSONRPCError(RPC_WALLET_ERROR, "Private keys are disabled for this wallet");
413418
}
414-
}
415419

416-
if (!wallet_upgraded) {
417-
throw JSONRPCError(RPC_WALLET_ERROR, error.original);
418-
}
420+
pwallet->WalletLogPrintf("Upgrading wallet to HD\n");
421+
pwallet->SetMinVersion(FEATURE_HD);
422+
423+
if (pwallet->IsCrypted()) {
424+
if (secureWalletPassphrase.empty()) {
425+
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: Wallet encrypted but supplied empty wallet passphrase");
426+
}
427+
428+
// We are intentionally re-locking the wallet so we can validate passphrase
429+
// by verifying if it can unlock the wallet
430+
pwallet->Lock();
431+
432+
// Unlock the wallet
433+
if (!pwallet->Unlock(secureWalletPassphrase)) {
434+
throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect");
435+
}
436+
}
437+
438+
if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
439+
pwallet->SetupDescriptorScriptPubKeyMans(secureMnemonic, secureMnemonicPassphrase);
440+
} else {
441+
auto spk_man = pwallet->GetLegacyScriptPubKeyMan();
442+
if (!spk_man) {
443+
throw JSONRPCError(RPC_WALLET_ERROR, "Error: Legacy ScriptPubKeyMan is not available");
444+
}
445+
446+
if (pwallet->IsCrypted()) {
447+
pwallet->WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
448+
spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase, encryption_key);
449+
return true;
450+
});
451+
} else {
452+
spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase);
453+
}
454+
}
455+
456+
if (pwallet->IsCrypted()) {
457+
// Relock encrypted wallet
458+
pwallet->Lock();
459+
} else if (!secureWalletPassphrase.empty()) {
460+
// Encrypt non-encrypted wallet
461+
if (!pwallet->EncryptWallet(secureWalletPassphrase)) {
462+
throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet");
463+
}
464+
}
465+
} // pwallet->cs_wallet
419466

420467
// If you are generating new mnemonic it is assumed that the addresses have never gotten a transaction before, so you don't need to rescan for transactions
421468
bool rescan = request.params[3].isNull() ? !generate_mnemonic : request.params[3].get_bool();

src/wallet/wallet.cpp

Lines changed: 10 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -353,11 +353,17 @@ std::shared_ptr<CWallet> CreateWallet(WalletContext& context, const std::string&
353353
// TODO: drop this condition after removing option to create non-HD wallets
354354
// related backport bitcoin#11250
355355
if (wallet->GetVersion() >= FEATURE_HD) {
356-
if (!wallet->GenerateNewHDChain(/*secureMnemonic=*/"", /*secureMnemonicPassphrase=*/"", passphrase)) {
357-
error = Untranslated("Error: Failed to generate encrypted HD wallet");
358-
status = DatabaseStatus::FAILED_CREATE;
359-
return nullptr;
356+
auto spk_man = wallet->GetLegacyScriptPubKeyMan();
357+
if (!spk_man) {
358+
error = Untranslated("Error: Legacy ScriptPubKeyMan is not available");
359+
status = DatabaseStatus::FAILED_ENCRYPT;
360+
return nullptr;
360361
}
362+
363+
wallet->WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
364+
spk_man->GenerateNewHDChain(/*secureMnemonic=*/"", /*secureMnemonicPassphrase=*/"", encryption_key);
365+
return true;
366+
});
361367
}
362368
}
363369

@@ -3209,52 +3215,6 @@ bool CWallet::UpgradeWallet(int version, bilingual_str& error)
32093215
return true;
32103216
}
32113217

3212-
bool CWallet::UpgradeToHD(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase, bilingual_str& error)
3213-
{
3214-
LOCK(cs_wallet);
3215-
3216-
// Do not do anything to HD wallets
3217-
if (IsHDEnabled()) {
3218-
error = Untranslated("Cannot upgrade a wallet to HD if it is already upgraded to HD.");
3219-
return false;
3220-
}
3221-
3222-
if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
3223-
error = Untranslated("Private keys are disabled for this wallet");
3224-
return false;
3225-
}
3226-
3227-
WalletLogPrintf("Upgrading wallet to HD\n");
3228-
SetMinVersion(FEATURE_HD);
3229-
3230-
if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
3231-
if (IsCrypted()) {
3232-
if (secureWalletPassphrase.empty()) {
3233-
error = Untranslated("Error: Wallet encrypted but supplied empty wallet passphrase");
3234-
return false;
3235-
}
3236-
3237-
// Unlock the wallet
3238-
if (!Unlock(secureWalletPassphrase)) {
3239-
error = Untranslated("Error: The wallet passphrase entered was incorrect");
3240-
return false;
3241-
}
3242-
}
3243-
SetupDescriptorScriptPubKeyMans(secureMnemonic, secureMnemonicPassphrase);
3244-
3245-
if (IsCrypted()) {
3246-
// Relock the wallet
3247-
Lock();
3248-
}
3249-
} else {
3250-
if (!GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase, secureWalletPassphrase)) {
3251-
error = Untranslated("Failed to generate HD wallet");
3252-
return false;
3253-
}
3254-
}
3255-
return true;
3256-
}
3257-
32583218
const CAddressBookData* CWallet::FindAddressBookEntry(const CTxDestination& dest, bool allow_change) const
32593219
{
32603220
const auto& address_book_it = m_address_book.find(dest);
@@ -3785,64 +3745,6 @@ void CWallet::ConnectScriptPubKeyManNotifiers()
37853745
}
37863746
}
37873747

3788-
bool CWallet::GenerateNewHDChain(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase)
3789-
{
3790-
auto spk_man = GetLegacyScriptPubKeyMan();
3791-
if (!spk_man) {
3792-
throw std::runtime_error(strprintf("%s: spk_man is not available", __func__));
3793-
}
3794-
3795-
if (IsCrypted()) {
3796-
if (secureWalletPassphrase.empty()) {
3797-
throw std::runtime_error(strprintf("%s: encrypted but supplied empty wallet passphrase", __func__));
3798-
}
3799-
3800-
bool is_locked = IsLocked();
3801-
3802-
CCrypter crypter;
3803-
CKeyingMaterial vMasterKey;
3804-
3805-
// We are intentionally re-locking the wallet so we can validate vMasterKey
3806-
// by verifying if it can unlock the wallet
3807-
Lock();
3808-
3809-
LOCK(cs_wallet);
3810-
for (const auto& [_, master_key] : mapMasterKeys) {
3811-
CKeyingMaterial _vMasterKey;
3812-
if (!crypter.SetKeyFromPassphrase(secureWalletPassphrase, master_key.vchSalt, master_key.nDeriveIterations, master_key.nDerivationMethod)) {
3813-
return false;
3814-
}
3815-
// Try another key if it cannot be decrypted or the key is incapable of encrypting
3816-
if (!crypter.Decrypt(master_key.vchCryptedKey, _vMasterKey) || _vMasterKey.size() != WALLET_CRYPTO_KEY_SIZE) {
3817-
continue;
3818-
}
3819-
// The likelihood of the plaintext being gibberish but also of the expected size is low but not zero.
3820-
// If it can unlock the wallet, it's a good key.
3821-
if (Unlock(_vMasterKey)) {
3822-
vMasterKey = _vMasterKey;
3823-
break;
3824-
}
3825-
}
3826-
3827-
// We got a gibberish key...
3828-
if (vMasterKey.empty()) {
3829-
// Mimicking the error message of RPC_WALLET_PASSPHRASE_INCORRECT as it's possible
3830-
// that the user may see this error when interacting with the upgradetohd RPC
3831-
throw std::runtime_error("Error: The wallet passphrase entered was incorrect");
3832-
}
3833-
3834-
spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase, vMasterKey);
3835-
3836-
if (is_locked) {
3837-
Lock();
3838-
}
3839-
} else {
3840-
spk_man->GenerateNewHDChain(secureMnemonic, secureMnemonicPassphrase);
3841-
}
3842-
3843-
return true;
3844-
}
3845-
38463748
void CWallet::UpdateProgress(const std::string& title, int nProgress)
38473749
{
38483750
ShowProgress(title, nProgress);

src/wallet/wallet.h

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -919,10 +919,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
919919
/* Returns true if HD is enabled */
920920
bool IsHDEnabled() const;
921921

922-
// TODO: move it to scriptpubkeyman
923-
/* Generates a new HD chain */
924-
bool GenerateNewHDChain(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase = "");
925-
926922
/* Returns true if the wallet can give out new addresses. This means it has keys in the keypool or can generate new keys */
927923
bool CanGetAddresses(bool internal = false) const;
928924

@@ -979,9 +975,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
979975
/** Upgrade the wallet */
980976
bool UpgradeWallet(int version, bilingual_str& error);
981977

982-
/** Upgrade non-HD wallet to HD wallet */
983-
bool UpgradeToHD(const SecureString& secureMnemonic, const SecureString& secureMnemonicPassphrase, const SecureString& secureWalletPassphrase, bilingual_str& error);
984-
985978
//! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and m_external_spk_managers
986979
std::set<ScriptPubKeyMan*> GetActiveScriptPubKeyMans() const;
987980

test/functional/wallet_upgradetohd.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,7 @@ def run_test(self):
203203
node.wait_until_stopped()
204204
self.start_node(0, extra_args=['-rescan'])
205205
assert_raises_rpc_error(-13, "Error: Wallet encrypted but passphrase not supplied to RPC.", node.upgradetohd, mnemonic)
206-
if not self.options.descriptors:
207-
assert_raises_rpc_error(-1, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
208-
else:
209-
assert_raises_rpc_error(-4, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
206+
assert_raises_rpc_error(-14, "Error: The wallet passphrase entered was incorrect", node.upgradetohd, mnemonic, "", "wrongpass")
210207
assert node.upgradetohd(mnemonic, "", walletpass)
211208
if not self.options.descriptors:
212209
assert_raises_rpc_error(-13, "Error: Please enter the wallet passphrase with walletpassphrase first.", node.dumphdinfo)

0 commit comments

Comments
 (0)