Skip to content

Instantly share code, notes, and snippets.

@moscowchill
Created February 9, 2026 10:03
Show Gist options
  • Select an option

  • Save moscowchill/c795de08b069c58806b758bec5b70557 to your computer and use it in GitHub Desktop.

Select an option

Save moscowchill/c795de08b069c58806b758bec5b70557 to your computer and use it in GitHub Desktop.
PoC: BSC double-sign evidence self-comparison bug (core/types/vote.go:110)
package types
import (
"bytes"
"testing"
"github.com/ethereum/go-ethereum/common"
)
// TestSelfComparisonBug_DifferentAddressesAccepted proves that
// NewSlashIndicatorFinalityEvidenceWrapper accepts votes from DIFFERENT
// validators as valid double-sign evidence. The guard on line 110 compares
// vote1.VoteAddress with ITSELF (always equal), so the address check is
// dead code.
func TestSelfComparisonBug_DifferentAddressesAccepted(t *testing.T) {
// Create two votes from DIFFERENT validators (different VoteAddress)
var addr1 BLSPublicKey
var addr2 BLSPublicKey
for i := range addr1 {
addr1[i] = 0xAA
}
for i := range addr2 {
addr2[i] = 0xBB
}
// Sanity check: these addresses are indeed different
if bytes.Equal(addr1[:], addr2[:]) {
t.Fatal("test setup error: addresses should be different")
}
vote1 := &VoteEnvelope{
VoteAddress: addr1,
Data: &VoteData{
SourceNumber: 100,
SourceHash: common.HexToHash("0x1111"),
TargetNumber: 200,
TargetHash: common.HexToHash("0x2222"),
},
}
vote2 := &VoteEnvelope{
VoteAddress: addr2, // DIFFERENT validator
Data: &VoteData{
SourceNumber: 100,
SourceHash: common.HexToHash("0x1111"),
TargetNumber: 200,
TargetHash: common.HexToHash("0x3333"), // Different target (conflicting vote)
},
}
// BUG: This should return nil (rejecting evidence from different validators)
// but it returns a valid evidence wrapper because the address check is broken.
evidence := NewSlashIndicatorFinalityEvidenceWrapper(vote1, vote2)
if evidence != nil {
t.Errorf("BUG CONFIRMED: Evidence was constructed for votes from DIFFERENT validators!\n"+
" vote1.VoteAddress: %x\n"+
" vote2.VoteAddress: %x\n"+
" evidence.VoteAddr: %s\n"+
" The address check on line 110 compares vote1 with ITSELF.\n"+
" This allows false slashing of innocent validators.",
addr1[:4], addr2[:4], evidence.VoteAddr)
} else {
t.Log("FIXED: Evidence correctly rejected for votes from different validators")
}
}
// TestSelfComparisonBug_IdenticalVotesAccepted proves that even completely
// identical votes (same address, same data, same signature) are accepted as
// "valid" double-sign evidence. A validator can be slashed by duplicating
// a single legitimate vote.
func TestSelfComparisonBug_IdenticalVotesAccepted(t *testing.T) {
var addr BLSPublicKey
for i := range addr {
addr[i] = 0xCC
}
vote := &VoteEnvelope{
VoteAddress: addr,
Data: &VoteData{
SourceNumber: 100,
SourceHash: common.HexToHash("0x1111"),
TargetNumber: 200,
TargetHash: common.HexToHash("0x2222"),
},
}
// Pass the SAME vote as both arguments — this is not a double-sign,
// it's a single vote duplicated. The function should ideally reject this
// (or at minimum, the caller should check the votes differ).
evidence := NewSlashIndicatorFinalityEvidenceWrapper(vote, vote)
if evidence != nil {
t.Errorf("BUG: Identical votes accepted as double-sign evidence!\n"+
" A single vote can be duplicated to create slash evidence.\n"+
" evidence.VoteAddr: %s\n"+
" VoteA.TarHash: %s\n"+
" VoteB.TarHash: %s\n"+
" These are the SAME vote.",
evidence.VoteAddr, evidence.VoteA.TarHash, evidence.VoteB.TarHash)
} else {
t.Log("Evidence correctly rejected for identical votes")
}
}
// TestSelfComparisonBug_Diagnosis directly tests the broken comparison
// to make the bug crystal clear.
func TestSelfComparisonBug_Diagnosis(t *testing.T) {
var addr1 BLSPublicKey
var addr2 BLSPublicKey
for i := range addr1 {
addr1[i] = 0xAA
}
for i := range addr2 {
addr2[i] = 0xBB
}
// This is what line 110 actually does (compares vote1 with itself):
buggyCheck := !bytes.Equal(addr1[:], addr1[:])
t.Logf("Buggy check (vote1 vs vote1): !bytes.Equal(addr1, addr1) = %v", buggyCheck)
// Always false — the guard never triggers
// This is what line 110 SHOULD do (compare vote1 with vote2):
correctCheck := !bytes.Equal(addr1[:], addr2[:])
t.Logf("Correct check (vote1 vs vote2): !bytes.Equal(addr1, addr2) = %v", correctCheck)
// True when addresses differ — the guard would correctly reject
if buggyCheck == false && correctCheck == true {
t.Error("BUG CONFIRMED: The self-comparison always passes, " +
"while the correct cross-comparison would reject different addresses")
}
}
// TestCorrectBehavior_SameAddressConflictingVotes verifies that legitimate
// double-sign evidence (same validator, conflicting votes) is correctly
// accepted. This is the intended use case.
func TestCorrectBehavior_SameAddressConflictingVotes(t *testing.T) {
var addr BLSPublicKey
for i := range addr {
addr[i] = 0xDD
}
vote1 := &VoteEnvelope{
VoteAddress: addr,
Data: &VoteData{
SourceNumber: 100,
SourceHash: common.HexToHash("0x1111"),
TargetNumber: 200,
TargetHash: common.HexToHash("0x2222"),
},
}
vote2 := &VoteEnvelope{
VoteAddress: addr, // SAME validator
Data: &VoteData{
SourceNumber: 100,
SourceHash: common.HexToHash("0x1111"),
TargetNumber: 200,
TargetHash: common.HexToHash("0x3333"), // Different target = equivocation
},
}
evidence := NewSlashIndicatorFinalityEvidenceWrapper(vote1, vote2)
if evidence == nil {
t.Fatal("Legitimate double-sign evidence was rejected — this is a regression")
}
t.Logf("Correct: Legitimate double-sign evidence accepted for validator %x...", addr[:4])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment