diff --git a/Cargo.lock b/Cargo.lock index c28ffae530..12e685a23e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,7 +1206,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -1637,7 +1637,7 @@ dependencies = [ [[package]] name = "dash-network" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "bincode", "bincode_derive", @@ -1648,7 +1648,7 @@ dependencies = [ [[package]] name = "dash-network-seeds" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "dash-network", ] @@ -1725,7 +1725,7 @@ dependencies = [ [[package]] name = "dash-spv" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "async-trait", "chrono", @@ -1754,7 +1754,7 @@ dependencies = [ [[package]] name = "dashcore" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "anyhow", "base64-compat", @@ -1780,12 +1780,12 @@ dependencies = [ [[package]] name = "dashcore-private" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" [[package]] name = "dashcore-rpc" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "dashcore-rpc-json", "hex", @@ -1798,7 +1798,7 @@ dependencies = [ [[package]] name = "dashcore-rpc-json" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "bincode", "dashcore", @@ -1813,7 +1813,7 @@ dependencies = [ [[package]] name = "dashcore_hashes" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "bincode", "dashcore-private", @@ -2428,7 +2428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2489,7 +2489,7 @@ checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -2867,7 +2867,7 @@ dependencies = [ [[package]] name = "git-state" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" [[package]] name = "glob" @@ -3552,7 +3552,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2 0.5.10", "system-configuration", "tokio", "tower-service", @@ -3803,7 +3803,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -4034,7 +4034,7 @@ dependencies = [ [[package]] name = "key-wallet" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "aes", "async-trait", @@ -4063,7 +4063,7 @@ dependencies = [ [[package]] name = "key-wallet-ffi" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "cbindgen 0.29.4", "dash-network", @@ -4079,7 +4079,7 @@ dependencies = [ [[package]] name = "key-wallet-manager" version = "0.45.0" -source = "git+https://github.com/dashpay/rust-dashcore?rev=38c689d97ddce0483d4ca455ade32efb019eb68d#38c689d97ddce0483d4ca455ade32efb019eb68d" +source = "git+https://github.com/dashpay/rust-dashcore?rev=19690d31b37ba12adc00f6bf2f8413d97e2b669d#19690d31b37ba12adc00f6bf2f8413d97e2b669d" dependencies = [ "async-trait", "bincode", @@ -4596,7 +4596,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5658,7 +5658,7 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.2", "rustls", - "socket2 0.6.4", + "socket2 0.5.10", "thiserror 2.0.18", "tokio", "tracing", @@ -5696,9 +5696,9 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.4", + "socket2 0.5.10", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6489,7 +6489,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6502,7 +6502,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -6561,7 +6561,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -7421,7 +7421,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] @@ -8870,7 +8870,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3eeee5da3f..9399166eb2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,14 +50,14 @@ members = [ ] [workspace.dependencies] -dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } -dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } -dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } -key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } -key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } -key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } -dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } -dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "38c689d97ddce0483d4ca455ade32efb019eb68d" } +dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } +dash-network-seeds = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } +dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } +key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } +key-wallet-ffi = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } +key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } +dash-network = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } +dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "19690d31b37ba12adc00f6bf2f8413d97e2b669d" } tokio-metrics = "0.5" diff --git a/packages/rs-platform-wallet-ffi/src/persistence.rs b/packages/rs-platform-wallet-ffi/src/persistence.rs index fc33ff6246..345a1bcc62 100644 --- a/packages/rs-platform-wallet-ffi/src/persistence.rs +++ b/packages/rs-platform-wallet-ffi/src/persistence.rs @@ -3000,20 +3000,38 @@ unsafe fn address_info_from_ffi( } /// Restore persisted `AddressInfo` rows into a managed account's -/// `AddressPool`. Plain upsert: a persisted row overwrites the gap-limit +/// `AddressPool`. Upsert: a persisted row overwrites the gap-limit /// default `ManagedWalletInfo::from_wallet` pre-derived at the same /// index, and the reverse-lookup maps + `highest_*` watermarks are /// extended to cover indices past that default gap window. /// /// The persisted row is authoritative for its typed public key — the -/// [`CoreAddressEntryFFI`] row now carries the full typed key (ECDSA-33 / +/// [`CoreAddressEntryFFI`] row carries the full typed key (ECDSA-33 / /// BLS-48 / EdDSA-32) with a [`KeyTypeTagFFI`] discriminator, so BLS /// operator and Ed25519 platform-node keys survive the round-trip in the -/// row itself. No merge against the pre-derived entry is needed or -/// wanted. +/// row itself — with ONE legacy exception: rows persisted before the +/// typed-key column existed carry an empty key (`public_key: None`). +/// Overwriting a pre-derived typed entry with such a row would strip the +/// in-memory BLS operator pubkeys that `from_wallet` derived from the +/// account xpub, silently breaking operator-ownership matching for every +/// pre-typed-key store. So when the incoming row has no key but the +/// pre-derived entry at that index does, the existing typed key is kept +/// (post-migration rows always carry their key, making this a no-op for +/// them). Legacy Ed25519 platform-node rows cannot be recovered this way +/// (hardened-only — no public derivation) nor migrated from the removed +/// account-level batch (its data was dropped by the schema migration); +/// per the pre-release convention those stores re-derive on +/// delete+re-import. fn restore_address_pool(pool: &mut AddressPool, infos: Vec) { - for info in infos { + for mut info in infos { let idx = info.index; + if info.public_key.is_none() { + if let Some(existing) = pool.addresses.get(&idx) { + if existing.public_key.is_some() { + info.public_key = existing.public_key.clone(); + } + } + } pool.address_index.insert(info.address.clone(), idx); pool.script_pubkey_index .insert(info.script_pubkey.clone(), idx); @@ -5920,6 +5938,57 @@ mod tests { } } + /// A LEGACY row (persisted before the typed-key column: empty key, + /// `public_key: None` after decode) must NOT strip the typed key the + /// gap-limit prederivation put at the same index — pre-typed-key + /// stores otherwise lose their in-memory BLS operator pubkeys at + /// load and masternode operator-ownership matching silently breaks + /// (post-migration rows always carry their key, so the preservation + /// is a no-op for them). Also pins the inverse: a legacy row at an + /// index with no prederived entry restores key-less rather than + /// inventing anything. + #[test] + fn legacy_keyless_row_keeps_prederived_typed_key() { + let mut pool = AddressPool::new_without_generation( + DerivationPath::from_str("m/9'/1'/3'").expect("static path must parse"), + AddressPoolType::AbsentHardened, + 5, + Network::Testnet, + ); + + // Prederived typed entry, as `ManagedWalletInfo::from_wallet` + // seeds BLS operator pools from the account xpub. + let bls = vec![0xE7u8; 48]; + let prederived = typed_key_test_address_info(7, Some(PublicKeyType::BLS(bls.clone()))); + pool.addresses.insert(7, prederived); + + // Legacy rows: same index key-less, plus one at a fresh index. + let legacy_same_idx = typed_key_test_address_info(7, None); + let legacy_fresh_idx = typed_key_test_address_info(9, None); + restore_address_pool(&mut pool, vec![legacy_same_idx, legacy_fresh_idx]); + + match &pool + .addresses + .get(&7) + .expect("entry 7 must exist") + .public_key + { + Some(PublicKeyType::BLS(bytes)) => assert_eq!( + bytes, &bls, + "legacy key-less row must keep the prederived BLS key" + ), + other => panic!("prederived BLS key was stripped, got {:?}", other), + } + assert!( + pool.addresses + .get(&9) + .expect("entry 9 must exist") + .public_key + .is_none(), + "a legacy row with no prederived counterpart stays key-less" + ); + } + /// `account_xpub` must survive the persist→restore byte round-trip — it is /// the key the seed-binding self-check (`PlatformWallet::verify_seed_binds`) /// later compares the resolver-derived xpub against, so a corrupted restore diff --git a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentCoreAddress.swift b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentCoreAddress.swift index dfb9f63741..a51598ac98 100644 --- a/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentCoreAddress.swift +++ b/packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentCoreAddress.swift @@ -28,9 +28,16 @@ public final class PersistentCoreAddress { /// pre-column stores openable: without it, lightweight migration /// fails with "missing attribute values on mandatory destination /// attribute" and the container refuses to load — a launch crash on - /// every device that has existing rows. Defaulted rows read as - /// ECDSA until the next Rust address-pool persist pulse re-tags - /// typed entries, which it does on every load. + /// every device that has existing rows. Defaulted legacy rows read + /// as ECDSA with an empty `publicKey` until the next Rust + /// address-pool persist pulse (pool extension / address-used / + /// registration — NOT plain load, which only reads the snapshot) + /// re-emits them with typed keys. On load, Rust's + /// `restore_address_pool` keeps the pre-derived typed key when a + /// legacy row arrives key-less, so in-memory BLS operator matching + /// is unaffected; legacy Ed25519 platform-node keys are hardened-only + /// and re-derivable only via delete+re-import (pre-release + /// convention). public var keyType: UInt8 = 0 /// `AddressPoolTypeTagFFI` raw value — 0 External, 1 Internal, /// 2 Absent, 3 AbsentHardened.