Skip to content

dash-spv uses the quorumsCLSigs signature for quorum member selection without checking it against the work block's coinbase #1262

Open

Description

@PastaPastaPasta

Since v20 (DIP-0029), the members of a quorum are chosen with a modifier built from the ChainLock signature in the coinbase of the quorum's work block. When that coinbase carries no ChainLock, the modifier uses the work block's hash instead. Dash Core reads the signature with GetNonNullCoinbaseChainlock (src/evo/cbtx.cpp) and builds the modifier in GetHashModifierFromWorkBlock (src/llmq/utils.cpp). An mnlistdiff repeats the same signature for each new quorum in quorumsCLSigs, and sends an all-zero signature when the coinbase has none (CSimplifiedMNListDiff::BuildQuorumChainlockInfo, src/evo/smldiff.cpp).

dash-spv uses the quorumsCLSigs signature as it arrives:

  • MasternodeList::apply_diff and MasternodeList::from_diff store it on each new non-rotating quorum (dash/src/sml/masternode_list/apply_diff.rs:92-130, dash/src/sml/masternode_list/from_diff.rs:48-89).
  • validate_quorum passes it to LLMQModifierType::new_quorum_modifier_type (dash-spv/src/sml_engine/validation.rs:34-48). That function switches to the block-hash modifier when the signature is all zeros (dash/src/sml/quorum_entry/quorum_modifier_type.rs:103-114).
  • For rotated quorums, rotation_cl_sigs_by_work_height keys the QRInfo diffs' quorumsCLSigs to work blocks (dash-spv/src/sml_engine.rs:647-732), and the quarter signatures feed member selection the same way.

Nothing checks the signature against the work block. Since #1200, the quorum commitments themselves are bound to their block: the diff's coinbase is proven against the header, and the list must hash to its merkleRootQuorums (dash-spv/src/validation/masternode_list.rs:29-58). quorumsCLSigs, however, isn't part of any coinbase commitment.

So with a different signature, or an all-zero one in place of a real one, dash-spv selects a different member set. A commitment that is valid on chain then fails its signature check and is marked Invalid. Later lists copy that status onto the same quorum (dash-spv/src/sml_engine.rs:1077-1096), and re-validation uses the same stored signature. A ChainLock is only accepted from a Verified quorum (dash-spv/src/validation/chainlock.rs:77), so ChainLocks from that quorum are refused.

Proposed fix: take the signature from the work block's coinbase, as Dash Core does, instead of from quorumsCLSigs.

  • The engine already applies a diff ending at each work block it validates against: the work-block lists for non-rotating quorums, and the QRInfo quarter diffs for rotated ones. That diff's coinbase is proven against the stored header.
  • Its CbTx (version 3 or later) holds best_cl_signature.
  • Following GetNonNullCoinbaseChainlock, the signature counts only when the payload's height matches the block, the signature is valid and bestCLHeightDiff is below the height. Otherwise the modifier uses the work block's hash.
  • quorumsCLSigs can then be checked against it, or dropped from member selection.

Open design point: where to keep the work block's signature between applying its diff and validating a quorum. It could be a field on MasternodeList, which changes the persisted snapshot format, or a height-keyed map in the engine that is pruned with the lists.

Test: a non-rotating quorum whose quorumsCLSigs entry differs from its work block's coinbase signature. A real commitment, captured together with its work block's diff, must still verify when the diff carries a wrong signature or the zero signature. That fails on dev today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions