Skip to content

Missing Uniqueness Check for Consensus Public Key in ValidatorManagement #18

@nekomoto911

Description

@nekomoto911

Summary

The ValidatorManagement contract does not enforce uniqueness of consensusPubkey across validators. Multiple validators can register or rotate to the same consensus public key, which could cause issues at the consensus layer.

Severity

Medium - Potential consensus layer impact

Description

When registering a new validator via registerValidator() or rotating keys via rotateConsensusKey(), there is no validation to ensure the consensusPubkey is not already in use by another validator.

Current Behavior

The _validateRegistration() function checks:

  • ✅ StakePool is valid (created by factory)
  • ✅ Caller is the pool's operator
  • ✅ StakePool is not already registered as validator
  • ✅ Voting power meets minimum bond
  • ✅ Moniker length is valid
  • Missing: consensusPubkey uniqueness
// ValidatorManagement.sol - _createValidatorRecord()
// No check for duplicate pubkey before assignment
record.consensusPubkey = consensusPubkey;

Expected Behavior

The contract should reject registration or key rotation if the consensus public key is already assigned to another active validator.

Impact

  1. Signature Ambiguity: Two validators signing blocks with the same key could cause the consensus engine to be unable to distinguish the source
  2. Equivocation Detection: May trigger false-positive equivocation (double-signing) detection
  3. Security: A malicious actor could intentionally use another validator's pubkey to cause disruption

Suggested Fix

  1. Add a mapping to track pubkey usage:
/// @notice Tracks which validator is using each consensus pubkey
/// @dev keccak256(pubkey) => stakePool address (address(0) if unused)
mapping(bytes32 => address) internal _pubkeyToValidator;
  1. Add validation in _validateRegistration():
bytes32 keyHash = keccak256(consensusPubkey);
if (_pubkeyToValidator[keyHash] != address(0)) {
    revert Errors.DuplicateConsensusPubkey(consensusPubkey);
}
  1. Update mapping on registration and key rotation:
// In _createValidatorRecord()
_pubkeyToValidator[keccak256(consensusPubkey)] = stakePool;

// In rotateConsensusKey() - clear old, set new
delete _pubkeyToValidator[keccak256(validator.consensusPubkey)];
_pubkeyToValidator[keccak256(newPubkey)] = stakePool;
  1. Clear mapping when validator is removed (if applicable)

  2. Add new error to Errors.sol:

/// @notice Consensus public key is already in use by another validator
/// @param pubkey The duplicate consensus public key
error DuplicateConsensusPubkey(bytes pubkey);

Reference

Aptos's stake.move enforces consensus key uniqueness - this aligns with that security model.

Files Affected

  • src/staking/ValidatorManagement.sol
  • src/foundation/Errors.sol

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions