@@ -537,7 +537,7 @@ static LoadResult LoadRecords(CWallet* pwallet, DatabaseBatch& batch, const std:
537
537
return LoadRecords (pwallet, batch, key, prefix, load_func);
538
538
}
539
539
540
- static DBErrors LoadLegacyWalletRecords (CWallet* pwallet, DatabaseBatch & batch, int last_client) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
540
+ static DBErrors LoadLegacyWalletRecords (CWallet* pwallet, WalletBatch & batch, int last_client) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)
541
541
{
542
542
AssertLockHeld (pwallet->cs_wallet );
543
543
DBErrors result = DBErrors::LOAD_OK;
@@ -550,7 +550,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
550
550
551
551
DataStream prefix;
552
552
prefix << type;
553
- std::unique_ptr<DatabaseCursor> cursor = batch.GetNewPrefixCursor (prefix);
553
+ std::unique_ptr<DatabaseCursor> cursor = batch.m_batch -> GetNewPrefixCursor (prefix);
554
554
if (!cursor) {
555
555
pwallet->WalletLogPrintf (" Error getting database cursor for '%s' records\n " , type);
556
556
return DBErrors::CORRUPT;
@@ -568,28 +568,28 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
568
568
569
569
// Load HD Chain
570
570
// Note: There should only be one HDCHAIN record with no data following the type
571
- LoadResult hd_chain_res = LoadRecords (pwallet, batch, DBKeys::HDCHAIN,
571
+ LoadResult hd_chain_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::HDCHAIN,
572
572
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
573
573
return LoadHDChain (pwallet, value, err) ? DBErrors:: LOAD_OK : DBErrors::CORRUPT;
574
574
});
575
575
result = std::max (result, hd_chain_res.m_result );
576
576
577
577
// Load unencrypted keys
578
- LoadResult key_res = LoadRecords (pwallet, batch, DBKeys::KEY,
578
+ LoadResult key_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::KEY,
579
579
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
580
580
return LoadKey (pwallet, key, value, err) ? DBErrors::LOAD_OK : DBErrors::CORRUPT;
581
581
});
582
582
result = std::max (result, key_res.m_result );
583
583
584
584
// Load encrypted keys
585
- LoadResult ckey_res = LoadRecords (pwallet, batch, DBKeys::CRYPTED_KEY,
585
+ LoadResult ckey_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::CRYPTED_KEY,
586
586
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
587
587
return LoadCryptedKey (pwallet, key, value, err) ? DBErrors::LOAD_OK : DBErrors::CORRUPT;
588
588
});
589
589
result = std::max (result, ckey_res.m_result );
590
590
591
591
// Load scripts
592
- LoadResult script_res = LoadRecords (pwallet, batch, DBKeys::CSCRIPT,
592
+ LoadResult script_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::CSCRIPT,
593
593
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& strErr) {
594
594
uint160 hash;
595
595
key >> hash;
@@ -612,13 +612,13 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
612
612
613
613
// Load keymeta
614
614
std::map<uint160, CHDChain> hd_chains;
615
- LoadResult keymeta_res = LoadRecords (pwallet, batch, DBKeys::KEYMETA,
616
- [&hd_chains] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& strErr) {
615
+ std::vector<CPubKey> updated_metas;
616
+ LoadResult keymeta_res = LoadRecords (pwallet, *batch.m_batch , DBKeys::KEYMETA,
617
+ [&hd_chains, &updated_metas] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& strErr) {
617
618
CPubKey vchPubKey;
618
619
key >> vchPubKey;
619
620
CKeyMetadata keyMeta;
620
621
value >> keyMeta;
621
- pwallet->GetOrCreateLegacyDataSPKM ()->LoadKeyMetadata (vchPubKey.GetID (), keyMeta);
622
622
623
623
// Extract some CHDChain info from this metadata if it has any
624
624
if (keyMeta.nVersion >= CKeyMetadata::VERSION_WITH_HDDATA && !keyMeta.hd_seed_id .IsNull () && keyMeta.hdKeypath .size () > 0 ) {
@@ -629,13 +629,32 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
629
629
uint32_t index = 0 ;
630
630
if (keyMeta.hdKeypath != " s" && keyMeta.hdKeypath != " m" ) {
631
631
std::vector<uint32_t > path;
632
- if (keyMeta.has_key_origin ) {
633
- // We have a key origin, so pull it from its path vector
634
- path = keyMeta.key_origin .path ;
635
- } else {
636
- // No key origin, have to parse the string
637
- if (!ParseHDKeypath (keyMeta.hdKeypath , path)) {
638
- strErr = " Error reading wallet database: keymeta with invalid HD keypath" ;
632
+ // Always parse the path string
633
+ if (!ParseHDKeypath (keyMeta.hdKeypath , path)) {
634
+ strErr = " Error reading wallet database: keymeta with invalid HD keypath" ;
635
+ return DBErrors::NONCRITICAL_ERROR;
636
+ }
637
+ // If there is a key origin, make sure that path matches
638
+ if (keyMeta.has_key_origin && path != keyMeta.key_origin .path ) {
639
+ // Detect if this metadata has a bad derivation path from a bug in inactive hd key derivation that was present in 0.21 and 22.x
640
+ // See https://github.com/bitcoin/bitcoin/issues/29109
641
+ // Markers for bad derivation:
642
+ // - 6 indexes
643
+ // - path[5] == path[2] + 1
644
+ // - path[0:2] == path[3:5]
645
+ // - path[3:6] matches hdKeypath
646
+ if (keyMeta.key_origin .path .size () == 6
647
+ && keyMeta.key_origin .path [5 ] == keyMeta.key_origin .path [2 ] + 1
648
+ && keyMeta.key_origin .path [0 ] == keyMeta.key_origin .path [3 ]
649
+ && keyMeta.key_origin .path [1 ] == keyMeta.key_origin .path [4 ]
650
+ && std::equal (keyMeta.key_origin .path .begin () + 3 , keyMeta.key_origin .path .end (), path.begin (), path.end ())
651
+ ) {
652
+ // Matches the pattern, just reset it to the parsed path
653
+ pwallet->WalletLogPrintf (" Repairing derivation path for %s\n " , HexStr (vchPubKey));
654
+ keyMeta.key_origin .path = path;
655
+ updated_metas.push_back (vchPubKey);
656
+ } else {
657
+ strErr = " keymeta with mismatched hdkeypath string and key origin derivation path" ;
639
658
return DBErrors::NONCRITICAL_ERROR;
640
659
}
641
660
}
@@ -679,10 +698,26 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
679
698
chain.nExternalChainCounter = std::max (chain.nExternalChainCounter , index + 1 );
680
699
}
681
700
}
701
+
702
+ pwallet->GetOrCreateLegacyDataSPKM ()->LoadKeyMetadata (vchPubKey.GetID (), keyMeta);
703
+
682
704
return DBErrors::LOAD_OK;
683
705
});
684
706
result = std::max (result, keymeta_res.m_result );
685
707
708
+ // Update any changed metadata
709
+ if (!updated_metas.empty ()) {
710
+ LegacyScriptPubKeyMan* spkm = pwallet->GetLegacyScriptPubKeyMan ();
711
+ Assert (spkm);
712
+ LOCK (spkm->cs_KeyStore );
713
+ for (const auto & pubkey : updated_metas) {
714
+ if (!batch.WriteKeyMetadata (spkm->mapKeyMetadata .at (pubkey.GetID ()), pubkey, true )) {
715
+ pwallet->WalletLogPrintf (" Unable to write repaired keymeta for %s\n " , HexStr (pubkey));
716
+ result = std::max (result, DBErrors::NONCRITICAL_ERROR);
717
+ }
718
+ }
719
+ }
720
+
686
721
// Set inactive chains
687
722
if (!hd_chains.empty ()) {
688
723
LegacyDataSPKM* legacy_spkm = pwallet->GetLegacyDataSPKM ();
@@ -699,7 +734,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
699
734
}
700
735
701
736
// Load watchonly scripts
702
- LoadResult watch_script_res = LoadRecords (pwallet, batch, DBKeys::WATCHS,
737
+ LoadResult watch_script_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::WATCHS,
703
738
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
704
739
CScript script;
705
740
key >> script;
@@ -713,7 +748,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
713
748
result = std::max (result, watch_script_res.m_result );
714
749
715
750
// Load watchonly meta
716
- LoadResult watch_meta_res = LoadRecords (pwallet, batch, DBKeys::WATCHMETA,
751
+ LoadResult watch_meta_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::WATCHMETA,
717
752
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
718
753
CScript script;
719
754
key >> script;
@@ -725,7 +760,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
725
760
result = std::max (result, watch_meta_res.m_result );
726
761
727
762
// Load keypool
728
- LoadResult pool_res = LoadRecords (pwallet, batch, DBKeys::POOL,
763
+ LoadResult pool_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::POOL,
729
764
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
730
765
int64_t nIndex;
731
766
key >> nIndex;
@@ -742,7 +777,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
742
777
// We don't want or need the default key, but if there is one set,
743
778
// we want to make sure that it is valid so that we can detect corruption
744
779
// Note: There should only be one DEFAULTKEY with nothing trailing the type
745
- LoadResult default_key_res = LoadRecords (pwallet, batch, DBKeys::DEFAULTKEY,
780
+ LoadResult default_key_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::DEFAULTKEY,
746
781
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
747
782
CPubKey default_pubkey;
748
783
try {
@@ -760,7 +795,7 @@ static DBErrors LoadLegacyWalletRecords(CWallet* pwallet, DatabaseBatch& batch,
760
795
result = std::max (result, default_key_res.m_result );
761
796
762
797
// "wkey" records are unsupported, if we see any, throw an error
763
- LoadResult wkey_res = LoadRecords (pwallet, batch, DBKeys::OLD_KEY,
798
+ LoadResult wkey_res = LoadRecords (pwallet, * batch. m_batch , DBKeys::OLD_KEY,
764
799
[] (CWallet* pwallet, DataStream& key, DataStream& value, std::string& err) {
765
800
err = " Found unsupported 'wkey' record, try loading with version 0.18" ;
766
801
return DBErrors::LOAD_FAIL;
@@ -1185,7 +1220,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
1185
1220
#endif
1186
1221
1187
1222
// Load legacy wallet keys
1188
- result = std::max (LoadLegacyWalletRecords (pwallet, *m_batch , last_client), result);
1223
+ result = std::max (LoadLegacyWalletRecords (pwallet, *this , last_client), result);
1189
1224
1190
1225
// Load descriptors
1191
1226
result = std::max (LoadDescriptorWalletRecords (pwallet, *m_batch, last_client), result);
0 commit comments