Skip to content

key-wallet: BLS operator & Ed25519 platform-node key derivation diverges from DashSync/dashbls (wrong masternode keys) #878

Description

@QuantumExplorer

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

  • dashwallet-ios (correct, matches on-chain): operator keypair 0 pubkey 82c6e4f8d85aa7384126058a4eb277b8cea7489bb928930011adca5add01c2cd14560d7f508d8a838d5a49e1b45953b4 (legacy serialization 02c6e4f8…, same point)
  • key-wallet: derived Key #0 = 83ed4df2da49382be68cd8c2…bbd52e2e54059b0262dd93d0

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

The default account creation (wallet/helper.rs:325,327wallet/accounts.rs:107-124 BLS / :176-193 Ed25519):

  1. derives a secp256k1 BIP32 key at the provider path from the wallet master,
  2. reuses its 32 secret bytes as the BLS / SLIP-10 master seed,
  3. 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)

  1. Remove the leading 0x00 from the hardened BLS child HMAC input.
  2. Use legacy G1 serialization in the BLS HD chain (input and output) to match dashbls/DashSync.
  3. 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.
  • Matching a wallet's derived operator key / node id against ProRegTx payloads (e.g. masternode ownership detection, feat(platform-wallet): wallet masternode list with DML-derived status in iOS platform#4116) can never succeed.
  • 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions