migrate: Handle HD chains that have identical seeds but different IDs

The seed ID is calculated from a pubkey produced by treating the seed as
a private key. This calculation includes a pubkey compression parameter,
even thought that compression is completely irrelevant for the usage of
the seed as a BIP 32 seed. Thus migration should detect if a seed has
been used multiple times by checking if the computed master key was
already processed.

The spkm_migration fuzzer needs to have it's added descriptors
accounting to be updated for this fix.
This commit is contained in:
Ava Chow
2026-06-01 14:37:57 -07:00
parent fbe628756c
commit de92208c2b
2 changed files with 20 additions and 11 deletions

View File

@@ -628,19 +628,28 @@ std::optional<MigrationData> LegacyDataSPKM::MigrateToDescriptor()
bool can_support_hd_split_feature = m_hd_chain.nVersion >= CHDChain::VERSION_HD_CHAIN_SPLIT;
std::set<CExtPubKey> master_xpubs;
for (const CHDChain& chain : chains) {
if (chain.seed_id.IsNull()) continue;
// Get the master xprv
CKey seed_key;
if (!GetKey(chain.seed_id, seed_key)) {
assert(false);
}
CExtKey master_key;
master_key.SetSeed(seed_key);
// Get the xpub and verify that we haven't already seen this xpub before
CExtPubKey master_xpub = master_key.Neuter();
const auto& [_, inserted] = master_xpubs.insert(master_xpub);
if (!inserted) continue;
for (int i = 0; i < 2; ++i) {
// Skip if doing internal chain and split chain is not supported
if (chain.seed_id.IsNull() || (i == 1 && !can_support_hd_split_feature)) {
if (i == 1 && !can_support_hd_split_feature) {
continue;
}
// Get the master xprv
CKey seed_key;
if (!GetKey(chain.seed_id, seed_key)) {
assert(false);
}
CExtKey master_key;
master_key.SetSeed(seed_key);
// Make the combo descriptor
std::string xpub = EncodeExtPubKey(master_key.Neuter());

View File

@@ -257,10 +257,10 @@ FUZZ_TARGET(spkm_migration, .init = initialize_spkm_migration)
bool add_inactive_hd_chain{fuzzed_data_provider.ConsumeBool() && !keys.empty()};
if (add_inactive_hd_chain) {
hd_key = PickValue(fuzzed_data_provider, keys);
CKey inactive_hd_key = PickValue(fuzzed_data_provider, keys);
hd_chain.nVersion = fuzzed_data_provider.ConsumeBool() ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE;
bool dup_chain = hd_chain.seed_id == hd_key.GetPubKey().GetID();
hd_chain.seed_id = hd_key.GetPubKey().GetID();
bool dup_chain = hd_key.IsValid() && std::equal(hd_key.begin(), hd_key.end(), inactive_hd_key.begin());
hd_chain.seed_id = inactive_hd_key.GetPubKey().GetID();
legacy_data.AddInactiveHDChain(hd_chain);
if (!dup_chain) added_chains++;
}