@@ -371,51 +371,101 @@ 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+ bool is_locked = pwallet->IsLocked ();
424+
425+ if (pwallet->IsCrypted ()) {
426+ if (secureWalletPassphrase.empty ()) {
427+ throw JSONRPCError (RPC_WALLET_PASSPHRASE_INCORRECT, " Error: Wallet encrypted but supplied empty wallet passphrase" );
428+ }
429+
430+ // We are intentionally re-locking the wallet so we can validate passphrase
431+ // by verifying if it can unlock the wallet
432+ pwallet->Lock ();
433+
434+ // Unlock the wallet
435+ if (!pwallet->Unlock (secureWalletPassphrase)) {
436+ throw JSONRPCError (RPC_WALLET_PASSPHRASE_INCORRECT, " Error: The wallet passphrase entered was incorrect" );
437+ }
438+ }
439+
440+ if (pwallet->IsWalletFlagSet (WALLET_FLAG_DESCRIPTORS)) {
441+ pwallet->SetupDescriptorScriptPubKeyMans (secureMnemonic, secureMnemonicPassphrase);
442+ } else {
443+ auto spk_man = pwallet->GetLegacyScriptPubKeyMan ();
444+ if (!spk_man) {
445+ throw JSONRPCError (RPC_WALLET_ERROR, " Error: Legacy ScriptPubKeyMan is not available" );
446+ }
447+
448+ if (pwallet->IsCrypted ()) {
449+ pwallet->WithEncryptionKey ([&](const CKeyingMaterial& encryption_key) {
450+ spk_man->GenerateNewHDChain (secureMnemonic, secureMnemonicPassphrase, encryption_key);
451+ return true ;
452+ });
453+ } else {
454+ spk_man->GenerateNewHDChain (secureMnemonic, secureMnemonicPassphrase);
455+ }
456+ }
457+
458+ if (is_locked) {
459+ // Relock the wallet
460+ pwallet->Lock ();
461+ }
462+
463+ if (!secureWalletPassphrase.empty () && !pwallet->IsCrypted ()) {
464+ if (!pwallet->EncryptWallet (secureWalletPassphrase)) {
465+ throw JSONRPCError (RPC_WALLET_ENCRYPTION_FAILED, " Failed to encrypt HD wallet" );
466+ }
467+ }
468+ } // pwallet->cs_wallet
419469
420470 // 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
421471 bool rescan = request.params [3 ].isNull () ? !generate_mnemonic : request.params [3 ].get_bool ();
0 commit comments