Skip to content

core/vm: reject duplicate bridge validators at Pasteur#3623

Merged
allformless merged 6 commits into
developfrom
fix/mendel-duplicate-validators
Jun 17, 2026
Merged

core/vm: reject duplicate bridge validators at Pasteur#3623
allformless merged 6 commits into
developfrom
fix/mendel-duplicate-validators

Conversation

@MatusKysel

@MatusKysel MatusKysel commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add Pasteur-specific precompile variants for BLS signature verification (0x66) and CometBFT light block validation (0x67)
  • reject duplicate bridge validator identities at Pasteur for both BLS pubkey aggregation inputs and light-client validator sets
  • add regression coverage for Pasteur precompile selection and duplicate-validator rejection paths

Details

This change introduces Pasteur-only behavior for the bridge validator precompiles.

For BLS signature verification, the Pasteur variant rejects inputs that repeat the same validator pubkey in the aggregated signer set. Pre-Pasteur behavior is preserved.

For CometBFT light block validation, the Pasteur variant validates that validator sets do not contain duplicate identities. The validation covers validator address, consensus pubkey, BLS key, and relayer
address, and is applied when decoding both the trusted consensus state and the incoming light block validator set.

Why

Duplicate bridge validators can allow malformed validator sets or signer lists to bypass uniqueness assumptions in bridge verification logic. Pasteur now enforces uniqueness at the precompile boundary instead
of relying on callers to sanitize inputs.

annielz
annielz previously approved these changes Mar 27, 2026

func (t validatorDuplicateTracker) check(idx int, value []byte) error {
key := string(value)
if firstIdx, ok := t.seen[key]; ok {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

are bls key and RelayerAddress allowed to stay unset? represent by zero value

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. Those two fields are allowed to be unset upstream. In our fixed-width decode path, "unset" is represented as zero-filled bytes, so I treat empty/nil and all-zero as the same unset state and exclude only
that case from duplicate detection.

// input:
// | chainID | height | nextValidatorSetHash | [{validator pubkey, voting power, relayer address, relayer bls pubkey}] |
// | 32 bytes | 8 bytes | 32 bytes | [{32 bytes, 8 bytes, 20 bytes, 48 bytes}] |
func DecodeConsensusState(input []byte) (ConsensusState, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how about

  1. func DecodeConsensusState(input []byte) (ConsensusState, error)
    --> func DecodeConsensusState(input []byte, requireUniqueValidators bool) (ConsensusState, error)
  2. delete DecodeConsensusStateWithValidation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

// input:
// consensus state length | consensus state | light block |
// 32 bytes | | |
func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.LightBlock, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

how about

  1. func DecodeLightBlockValidationInput(input []byte) (*ConsensusState, *types.LightBlock, error)
    --> func DecodeLightBlockValidationInput(input []byte, requireUniqueValidators bool) (*ConsensusState, *types.LightBlock, error)
  2. delete DecodeLightBlockValidationInputWithValidation

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

Comment thread core/vm/contracts_lightclient.go Outdated
return "COMET_BFT_LIGHT_BLOCK_VALIDATE_HERTZ"
}

type cometBFTLightBlockValidateHertzMendel struct {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

how about
cometBFTLightBlockValidateHertzMendel --> cometBFTLightBlockValidateMendel?
because when Mendel enabled, Hertz must be already enalbed

@allformless

Copy link
Copy Markdown
Collaborator

delay to Pasteur HF after discussion

@allformless allformless changed the title core/vm: reject duplicate bridge validators at Mendel core/vm: reject duplicate bridge validators at Pasteur Apr 1, 2026
@zlacfzy

zlacfzy commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

0x66 (blsSignatureVerify) is left unchanged — it is a generic BLS primitive and does not require unique pubkeys.

Comment thread core/vm/contracts.go Outdated
common.BytesToAddress([]byte{0x1, 0x00}): &p256Verify{eip7951: true},
}

var PrecompiledContractsPasteur = func() PrecompiledContracts {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

list precompiles directly like others

Comment thread core/vm/contracts.go Outdated
for k := range PrecompiledContractsPrague {
PrecompiledAddressesPrague = append(PrecompiledAddressesPrague, k)
}
for k := range PrecompiledContractsPasteur {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

move after PrecompiledContractsOsaka

@hashdit-bot

hashdit-bot Bot commented Jun 16, 2026

Copy link
Copy Markdown

Pull Request Review

This PR introduces Pasteur-specific precompile routing by adding a new PrecompiledContractsPasteur set and selecting it when rules.IsPasteur is enabled, including a Pasteur variant for light block validation at precompile 0x67. It updates CometBFT light block decoding/validation paths to optionally enforce validator uniqueness and wires this strict mode into the Pasteur validator, while keeping legacy behavior unchanged for earlier forks. The PR also adds regression tests to verify Pasteur precompile selection and rejection of duplicate validators in consensus state/light block inputs.

Sensitive Content

No sensitive content detected.

Security Issues

No serious security issues detected.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

@allformless allformless merged commit 02122b6 into develop Jun 17, 2026
7 of 8 checks passed
zlacfzy added a commit to zlacfzy/bsc that referenced this pull request Jun 17, 2026
… block at Pasteur

Builds on the Pasteur precompile set introduced in bnb-chain#3623.

The legacy v1 Tendermint light-client precompiles 0x64 (tmHeaderValidate)
and 0x65 (iavlMerkleProofValidate) are only reachable via the deprecated
BC<->BSC cross-chain stack (contracts/deprecated/), which no longer exists.
Rename the existing *Nano suspend handlers to tmHeaderValidateDeprecated /
iavlMerkleProofValidateDeprecated (error "deprecated") and map 0x64/0x65 to
them in the Pasteur set, deprecating them from Pasteur.

For the v2 cometBFT light-client (0x67), the verification work scales with
the light block's validator/signature count while the gas was flat. Price
the Pasteur variant's RequiredGas per input byte
(CometBFTLightBlockValidatePerByteGas) so gas tracks the real CPU/memory cost
(cf. the per-key pricing of 0x66 blsSignatureVerify). The pre-Pasteur Hertz
variant keeps the flat gas so earlier blocks replay identically.

Note: the per-byte rate is tunable; raising 0x67 gas affects the live
Greenfield/op-stack relayer gas budget and should be coordinated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zlacfzy added a commit to zlacfzy/bsc that referenced this pull request Jun 17, 2026
… block at Pasteur

Builds on the Pasteur precompile set introduced in bnb-chain#3623.

The legacy v1 Tendermint light-client precompiles 0x64 (tmHeaderValidate)
and 0x65 (iavlMerkleProofValidate) are only reachable via the deprecated
BC<->BSC cross-chain stack (contracts/deprecated/), which no longer exists.
Rename the existing *Nano suspend handlers to tmHeaderValidateDeprecated /
iavlMerkleProofValidateDeprecated (error "deprecated") and map 0x64/0x65 to
them in the Pasteur set, deprecating them from Pasteur.

For the v2 cometBFT light-client (0x67), the verification work scales with
the light block's validator/signature count while the gas was flat. Price
the Pasteur variant's RequiredGas per input byte
(CometBFTLightBlockValidatePerByteGas) so gas tracks the real CPU/memory cost
(cf. the per-key pricing of 0x66 blsSignatureVerify). The pre-Pasteur Hertz
variant keeps the flat gas so earlier blocks replay identically.

Note: the per-byte rate is tunable; raising 0x67 gas affects the live
Greenfield/op-stack relayer gas budget and should be coordinated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
allformless pushed a commit that referenced this pull request Jun 17, 2026
… block at Pasteur (#3726)

Builds on the Pasteur precompile set introduced in #3623.

The legacy v1 Tendermint light-client precompiles 0x64 (tmHeaderValidate)
and 0x65 (iavlMerkleProofValidate) are only reachable via the deprecated
BC<->BSC cross-chain stack (contracts/deprecated/), which no longer exists.
Rename the existing *Nano suspend handlers to tmHeaderValidateDeprecated /
iavlMerkleProofValidateDeprecated (error "deprecated") and map 0x64/0x65 to
them in the Pasteur set, deprecating them from Pasteur.

For the v2 cometBFT light-client (0x67), the verification work scales with
the light block's validator/signature count while the gas was flat. Price
the Pasteur variant's RequiredGas per input byte
(CometBFTLightBlockValidatePerByteGas) so gas tracks the real CPU/memory cost
(cf. the per-key pricing of 0x66 blsSignatureVerify). The pre-Pasteur Hertz
variant keeps the flat gas so earlier blocks replay identically.

Note: the per-byte rate is tunable; raising 0x67 gas affects the live
Greenfield/op-stack relayer gas budget and should be coordinated.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@allformless allformless added this to the v1.7.4 milestone Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants