Created
February 9, 2026 10:03
-
-
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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