oak_techniques: [OAK-T14.001]
spec_id: oak-detection-T14.001
version: 0.1.0
maturity: emerging
maintainer: "@iZonex"
license: Apache-2.0

scope: |
  Detect validator-layer events that produce slashing — both adversarial
  (forced equivocation, slashing-as-MEV, mass-slash via consensus-client
  bug, slashing-claim race) and operator-procedural (DVT-failover /
  hot-standby / maintenance-window correlated self-slashing). The
  dominant *observed* failure mode at v0.1 is operator-side correlated
  self-slashing (sub-case e); adversarial sub-cases remain
  characterised in academic literature without a field-confirmed
  external-attacker anchor. Excludes: T14.002 (MEV-Boost relay attack);
  T14.003 (restaking-cascading risk — composes when a slash propagates
  across AVSs); T11 (off-chain key compromise — keys are not stolen
  here, the validator signs its own slashable message).

data_sources: [beacon_attester_slashings, beacon_proposer_slashings,
               cosmos_slashing_events, polkadot_offences,
               consensus_client_inventory, dvt_cluster_telemetry,
               slashing_protection_state, mempool_report_log]

detection_logic:
  description: |
    Five orthogonal paths matching the five sub-cases. PATH A (operator-
    procedural correlated self-slashing): a cluster of validators tied
    to one operator family / DVT cluster slashed within a tight epoch
    window — the dominant observed failure mode. PATH B (forced-
    equivocation / griefing): validator enters a slashable state
    immediately after processing a specific external mempool / gossip
    input that traces to an unrelated address. PATH C (slashing-as-MEV):
    slashing event correlated with profitable backrun by a related
    actor capturing whistleblower reward, post-ejection reorg, or LST
    depeg. PATH D (consensus-client correlated bug): mass slashing
    correlated with a single client-version cluster. PATH E (slashing-
    claim race / report stripping): a single proposer is consistently
    the inclusion address for slashing reports whose content was first
    surfaced by a third party.
  pseudocode: |
    SLASHING_EVENTS = beacon_attester_slashings + beacon_proposer_slashings
                       + cosmos_slashing_events + polkadot_offences

    # PATH A — operator-procedural correlated self-slashing
    for each window W of size correlation_window:
      slashed ← SLASHING_EVENTS in W
      groups ← group_by(slashed, key = validator_to_operator_or_dvt(v))
      for op, cohort in groups.items():
        if |cohort| ≥ correlation_min_validators
           and op ∉ slashing_allowlist:
          emit(PATH_A, operator_or_dvt=op, cohort_size=|cohort|,
               window=W, severity=critical)

    # PATH B — forced-equivocation / griefing causation trace
    for each slashing s on validator v:
      window ← [s.t − causation_window, s.t]
      inputs ← gossip_or_mempool_inputs_to(v, window)
      for inp in inputs:
        if traces_to_unrelated_address(inp, v.operator)
           and is_known_griefing_pattern(inp):
          emit(PATH_B, validator=v, slashing=s, input=inp, severity=high)

    # PATH C — slashing-as-MEV / whistleblower-reward capture
    for each slashing s with whistleblower_reward(s) > 0:
      reporter ← s.report.proposer
      benefit ← post_event_profit(reporter ∪ funder_graph_cluster(reporter,
                                                                    hops=trace_hops),
                                   window = post_event_window)
      if benefit > min_mev_benefit:
        emit(PATH_C, slashing=s, reporter, benefit_usd=benefit,
             severity=high)

    # PATH D — consensus-client correlated bug
    for each window W:
      slashed ← SLASHING_EVENTS in W
      by_client ← group_by(slashed, key = consensus_client_of(v))
      for client, cohort in by_client.items():
        share_of_active ← active_share(client)
        if |cohort| > client_correlation_min
           and (|cohort| / total_slashed(W)) > client_correlation_share
           and share_of_active < single_client_dominance_floor:
          emit(PATH_D, client=client, cohort_size=|cohort|,
               severity=critical)

    # PATH E — slashing-claim race / report stripping
    for each block b containing a slashing report R:
      proposer ← b.proposer
      mempool_origin ← first_seen_in_mempool(R, before = b.t)
      if mempool_origin ≠ None and mempool_origin.author ≠ proposer:
        recent ← report_count(proposer, window = recent_window)
        if recent.steal_rate > steal_rate_threshold:
          emit(PATH_E, proposer, sample_report=R, steal_rate=recent.steal_rate,
               severity=high)

parameters:
  correlation_window:           { type: duration, default: 1h }
  correlation_min_validators:   { type: integer,  default: 5 }
  slashing_allowlist:           { type: list,     default: [] }
  causation_window:             { type: duration, default: 60s }
  trace_hops:                   { type: integer,  default: 3 }
  post_event_window:            { type: duration, default: 1h }
  min_mev_benefit:              { type: number,   default: 10000 }
  client_correlation_min:       { type: integer,  default: 50 }
  client_correlation_share:     { type: number,   default: 0.6 }
  single_client_dominance_floor: { type: number,  default: 0.5 }
  recent_window:                { type: duration, default: 30d }
  steal_rate_threshold:         { type: number,   default: 0.4 }

output_alert: [oak_technique, detection_path, severity, chain,
               operator_or_dvt, validator, cohort_size, client,
               proposer, evidence]

test_fixtures:
  positive:
    - 2025-09-ssv-network-mass-slashing       # canonical PATH A operator-procedural correlated self-slashing
    - 2024-eigenlayer-restaking-airdrop       # AVS slashing-condition cohort context
  negative:
    - "Single isolated slashing on a validator with no operator cluster overlap and no causation trace"
    - "Anti-slashing-database-protected operator on routine restart with no duplicate signatures"

false_positive_modes:
  - planned validator-set rotation by a known operator (allowlist-suppressed)
  - PATH C false positives where the proposer is also the legitimate independent reporter (require mempool_origin from a distinct author)
  - PATH D triggered by a chain-wide reorg / volatility event affecting all clients proportionally — require disproportionate single-client share
  - PATH E false positives from public mempool latency where the proposer's own copy lands first organically — require sustained steal_rate above threshold

mitigations: [OAK-M02, OAK-M05, OAK-M16, OAK-M17, OAK-M22]

reference_implementations:
  - { target: prysm-slasher,        chain: ethereum, url: "" }
  - { target: lighthouse-slasher,   chain: ethereum, url: "" }
  - { target: tenderduty,           chain: cosmos,   url: "" }
  - { target: ssv-monitor,          chain: ethereum, url: "" }
  - { target: web3signer,           chain: ethereum, url: "" }
