Skip to content

Validate and canonicalize mux validator pubkeys - #910

Open
pucedoteth wants to merge 1 commit into
flashbots:developfrom
pucedoteth:fix-mux-validator-pubkey-normalization
Open

Validate and canonicalize mux validator pubkeys#910
pucedoteth wants to merge 1 commit into
flashbots:developfrom
pucedoteth:fix-mux-validator-pubkey-normalization

Conversation

@pucedoteth

Copy link
Copy Markdown

The mux map is keyed on the raw validator_pubkeys strings from the config file, and getHeader looks a validator up with the raw pubkey segment from the request URL:

// server/service.go
pubkey = vars["pubkey"]
...
if mux, ok := m.muxMap[pubkey]; ok {

Neither side is validated or normalized, so two operator mistakes silently disable a mux instead of failing:

  1. A malformed pubkey is accepted at startup — truncated, mistyped, missing the 0x prefix, or even the empty string. It can never match a request.
  2. A correctly formed pubkey in a different case never matches — hex is case-insensitive, Go map keys are not.

In both cases mev-boost starts cleanly, reports the mux as loaded, and then quietly serves that validator from the default relay set. Nothing logs a warning. For a mux configured to restrict a validator to a specific relay set, that is a silent routing change.

The same raw comparison also defeats the duplicate check that already exists: the same pubkey listed in two muxes in different cases is not caught by ErrDuplicateValidatorPubkey, and silently lands in whichever entry is processed last.

Relay pubkeys already go through utils.HexToPubkey in types.NewRelayEntry — only the mux path skips validation.

Change

  • ValidateMuxEntries validates the encoding and stores the lowercased key.
  • GetConfigForValidator folds the case of the lookup key.

The check is syntactic (0x-prefixed, 48 bytes of hex) rather than a BLS point decompression. These strings are only ever lookup keys, and the getHeader pubkey is not curve-checked either, so requiring a valid curve point would be stricter than the matching logic — and would reject well-formed placeholder keys, including the one in the existing TestParseConfigWithMux fixture. Folding rather than parsing on lookup also keeps this off the getHeader hot path.

Behavior change

This rejects a mux config that was previously accepted: a malformed validator pubkey is now a startup error rather than a mux that never fires. That is the point of the change, but it is a visible difference for anyone currently running a typo, so flagging it explicitly. Happy to downgrade it to a warning-and-skip if you would rather not fail closed on startup.

Test plan

Four new tests, all failing before the change (verified by reverting only config/mux_config.go and server/service.go and keeping the tests):

test failure without the fix
TestValidateMuxEntriesRejectsMalformedPubkey malformed validator pubkey "0xdeadbeef" was accepted (also "", not-a-pubkey, over-long)
TestValidateMuxEntriesCanonicalizesPubkeys mux map is not keyed by the canonical lowercase pubkey
TestValidateMuxEntriesDetectsDuplicateAcrossCase duplicate not reported
TestGetConfigForValidatorIsCaseInsensitive expected the mux relay ..., got the default set

go test ./... passes (including the existing TestParseConfigWithMux), plus go test -race on the changed packages, go vet, and gofmt -d -s.


🤖 Written with Claude Code. Every result above comes from a local build and test run.

The mux map is keyed on the raw `validator_pubkeys` strings from the
config file, and getHeader looks a validator up with the raw pubkey
segment from the request URL:

    pubkey = vars["pubkey"]
    ...
    if mux, ok := m.muxMap[pubkey]; ok {

Neither side is validated or normalized, so two mistakes silently
disable a mux instead of failing:

  - A malformed pubkey (truncated, mistyped, missing 0x, or empty) is
    accepted at startup and can never match a request.
  - A correctly formed pubkey written in a different case never matches,
    because hex is case-insensitive but map keys are not.

In both cases mev-boost starts cleanly, reports the mux as loaded, and
then quietly serves that validator from the default relay set. Nothing
logs a warning. The same comparison also defeats the existing duplicate
check: the same pubkey in two muxes in different cases is not reported
by ErrDuplicateValidatorPubkey and silently lands in whichever entry is
processed last.

Relay pubkeys already go through utils.HexToPubkey in
types.NewRelayEntry; only the mux path skips validation.

Validate the encoding and lowercase the key when building the map, and
fold the case of the lookup key in GetConfigForValidator. The check is
syntactic rather than a BLS point decompression, since these strings are
only ever lookup keys and the getHeader pubkey is not curve-checked
either -- this keeps well-formed placeholder keys, such as the one in
the existing cli config test, working.

Note this now rejects a mux config that was previously accepted: a
malformed validator pubkey is a startup error instead of a mux that
never fires.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 23, 2026 00:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants