You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
key-wallet derives wrong masternode operator BLS keys and Ed25519 platform node ids — they don't match DashSync/dashwallet-ios (and therefore don't match keys used in real on-chain ProRegTx registrations) for the same mnemonic.
These are different keys, not different serializations of one key.
The derivation paths match (m/9'/5'/3'/3' operator, m/9'/5'/3'/4' platform node, coin type 5 mainnet, all levels hardened — key-wallet/src/account/account_type.rs:452-471 vs DashSync DSAuthenticationKeysDerivationPath.m:83-93). The divergence is algorithmic — three independent bugs, each alone sufficient to produce different keys:
Bug 1 — hardened BLS child derivation has a spurious leading 0x00
key-wallet/src/derivation_bls_bip32.rs:176-186 builds the hardened-child HMAC input as 0x00 ‖ sk(32) ‖ i(4 BE) ‖ {0|1} (38 bytes) — the BIP32-secp256k1 hardened convention.
dashbls (the reference, used by DashSync via FFI) uses sk(32) ‖ i(4 BE) ‖ {0|1} (37 bytes, no leading zero) — bls-signatures/src/extendedprivatekey.cpp:89-102.
Since every level of the provider paths is hardened, the derived account diverges from the very first child. (Seed→master matches: both HMAC-SHA256 with key "BLS HD seed", seed‖0→sk / seed‖1→chaincode.)
Bug 2 — modern/IETF serialization where dashbls uses legacy
For non-hardened children (and in public derivation), dashbls serializes the parent public key with the legacy flag (sk.GetG1Element().Serialize(fLegacy), extendedprivatekey.cpp:99). key-wallet feeds blsful's modern to_bytes() into the HMAC (derivation_bls_bip32.rs:181-183, :313-322) and has no legacy-HD option. Same point, different bytes → different child keys. Output serialization is also modern where DashSync's KeyKind_BLS is legacy throughout.
Bug 3 — hybrid secp256k1→BLS/Ed25519 seed in the default wallet flow
derives a secp256k1 BIP32 key at the provider path from the wallet master,
reuses its 32 secret bytes as the BLS / SLIP-10 master seed,
then applies the provider path again on top (bls_account.rs:113-129, 258-273, 362-388; eddsa_account.rs:94, 258-271).
DashSync feeds the bip39 seed directly into BLS FromSeed / SLIP-10 and derives the path once, in the target curve. The hybrid double-application is structurally unable to match, independent of bugs 1-2. This also breaks the platform node id even though the SLIP-10 implementation itself (derivation_slip10.rs:93-118) is standard and correct.
Fix required (all three together)
Remove the leading 0x00 from the hardened BLS child HMAC input.
Use legacy G1 serialization in the BLS HD chain (input and output) to match dashbls/DashSync.
Feed the bip39 seed directly into the BLS/Ed25519 master and derive the provider path once, in the target scheme (drop the secp→BLS/Ed25519 hybrid and the double path application).
Reference implementation for test vectors: dashbls ExtendedPrivateKey::{FromSeed, PrivateChild} (bls-signatures/src/extendedprivatekey.cpp). DashSync entry point: DSKeyManager.m:89-96 (key_private_key_at_index_path, KeyKind_BLS legacy / KeyKind_ED25519).
Impact
Wallet UIs listing provider operator / platform node keys show keys that were never used on-chain.
Any future operator-key or platform-node-key signing from these accounts would use wrong keys.
Note: keys previously persisted from these accounts will change after the fix (correct — the old ones never corresponded to anything on-chain).
Related: #875/#876 (provider payload surfacing), #863/#864 (provider key matching — unaffected: the ECDSA owner/voting derivations are correct and match real registrations).
Summary
key-wallet derives wrong masternode operator BLS keys and Ed25519 platform node ids — they don't match DashSync/dashwallet-ios (and therefore don't match keys used in real on-chain ProRegTx registrations) for the same mnemonic.
User-verified repro: the same wallet yields
82c6e4f8d85aa7384126058a4eb277b8cea7489bb928930011adca5add01c2cd14560d7f508d8a838d5a49e1b45953b4(legacy serialization02c6e4f8…, same point)83ed4df2da49382be68cd8c2…bbd52e2e54059b0262dd93d0These are different keys, not different serializations of one key.
The derivation paths match (
m/9'/5'/3'/3'operator,m/9'/5'/3'/4'platform node, coin type 5 mainnet, all levels hardened —key-wallet/src/account/account_type.rs:452-471vs DashSyncDSAuthenticationKeysDerivationPath.m:83-93). The divergence is algorithmic — three independent bugs, each alone sufficient to produce different keys:Bug 1 — hardened BLS child derivation has a spurious leading
0x00key-wallet/src/derivation_bls_bip32.rs:176-186builds the hardened-child HMAC input as0x00 ‖ sk(32) ‖ i(4 BE) ‖ {0|1}(38 bytes) — the BIP32-secp256k1 hardened convention.dashbls (the reference, used by DashSync via FFI) uses
sk(32) ‖ i(4 BE) ‖ {0|1}(37 bytes, no leading zero) —bls-signatures/src/extendedprivatekey.cpp:89-102.Since every level of the provider paths is hardened, the derived account diverges from the very first child. (Seed→master matches: both HMAC-SHA256 with key
"BLS HD seed",seed‖0→sk /seed‖1→chaincode.)Bug 2 — modern/IETF serialization where dashbls uses legacy
For non-hardened children (and in public derivation), dashbls serializes the parent public key with the legacy flag (
sk.GetG1Element().Serialize(fLegacy),extendedprivatekey.cpp:99). key-wallet feeds blsful's modernto_bytes()into the HMAC (derivation_bls_bip32.rs:181-183,:313-322) and has no legacy-HD option. Same point, different bytes → different child keys. Output serialization is also modern where DashSync'sKeyKind_BLSis legacy throughout.Bug 3 — hybrid secp256k1→BLS/Ed25519 seed in the default wallet flow
The default account creation (
wallet/helper.rs:325,327→wallet/accounts.rs:107-124BLS /:176-193Ed25519):bls_account.rs:113-129, 258-273, 362-388;eddsa_account.rs:94, 258-271).DashSync feeds the bip39 seed directly into BLS
FromSeed/ SLIP-10 and derives the path once, in the target curve. The hybrid double-application is structurally unable to match, independent of bugs 1-2. This also breaks the platform node id even though the SLIP-10 implementation itself (derivation_slip10.rs:93-118) is standard and correct.Fix required (all three together)
0x00from the hardened BLS child HMAC input.Reference implementation for test vectors: dashbls
ExtendedPrivateKey::{FromSeed, PrivateChild}(bls-signatures/src/extendedprivatekey.cpp). DashSync entry point:DSKeyManager.m:89-96(key_private_key_at_index_path,KeyKind_BLSlegacy /KeyKind_ED25519).Impact
Related: #875/#876 (provider payload surfacing), #863/#864 (provider key matching — unaffected: the ECDSA owner/voting derivations are correct and match real registrations).