Skip to content

bug: VRF RNG shared across all calls due to mutable default argument validator selection is predictable #1733

Description

@Sertug17

Description

get_validators_for_transaction in backend/consensus/vrf.py uses a mutable default argument for the random number generator. The rng object is created once at module import time and shared across every call, making the validator selection sequence predictable.

Root Cause

def get_validators_for_transaction(
    nodes: list[dict],
    num_validators: int | None = None,
    rng=np.random.default_rng(seed=int(datetime.now().timestamp())),  # ← evaluated ONCE at import
) -> list[dict]:

Python evaluates default arguments at function definition time (i.e., module load time), not at call time. This means:

  • All calls share the same stateful RNG instance
  • The seed is int(datetime.now().timestamp()) knowable by anyone who knows when the process started
  • An attacker with knowledge of the server start time can predict the entire future sequence of validator selections

This fundamentally undermines the purpose of a VRF (Verifiable Random Function) in a consensus system.

Expected Behavior

Each call should use an independently seeded RNG, or the caller should inject a cryptographically secure RNG.

Fix

def get_validators_for_transaction(
    nodes: list[dict],
    num_validators: int | None = None,
    rng=None,
) -> list[dict]:
    if rng is None:
        rng = np.random.default_rng()
    ...

Severity

Critical undermines consensus security; validator selection becomes predictable.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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