consensus: use signer pubkey to check for unique signatures, close XFN-03 (#1643)

* use signer pubkey to check for unique signatures

* add error handling

* add go routines to process Ecrecover concurrently
add input validation and early return

* optimize
This commit is contained in:
Wanwiset Peerapatanapokin 2025-11-10 21:56:46 +04:00 committed by GitHub
parent da92ad7ccf
commit 366c99ece3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 74 additions and 22 deletions

View file

@ -812,7 +812,17 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *
return errors.New("fail to verify QC due to failure in getting epoch switch info") return errors.New("fail to verify QC due to failure in getting epoch switch info")
} }
signatures, duplicates := UniqueSignatures(quorumCert.Signatures) signedVoteObj := types.VoteSigHash(&types.VoteForSign{
ProposedBlockInfo: quorumCert.ProposedBlockInfo,
GapNumber: quorumCert.GapNumber,
})
signatures, duplicates, err := RecoverUniqueSigners(signedVoteObj, quorumCert.Signatures)
if err != nil {
log.Error("[verifyQC] Error while getting unique signatures from QC", "qcBlockNum", quorumCert.ProposedBlockInfo.Number, "qcRound", quorumCert.ProposedBlockInfo.Round, "qcBlockHash", quorumCert.ProposedBlockInfo.Hash, "qcSignLen", len(quorumCert.Signatures), "error", err)
return err
}
if len(duplicates) != 0 { if len(duplicates) != 0 {
for _, d := range duplicates { for _, d := range duplicates {
log.Warn("[verifyQC] duplicated signature in QC", "duplicate", common.Bytes2Hex(d)) log.Warn("[verifyQC] duplicated signature in QC", "duplicate", common.Bytes2Hex(d))
@ -835,10 +845,7 @@ func (x *XDPoS_v2) verifyQC(blockChainReader consensus.ChainReader, quorumCert *
for _, signature := range signatures { for _, signature := range signatures {
go func(sig types.Signature) { go func(sig types.Signature) {
defer wg.Done() defer wg.Done()
verified, _, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{ verified, _, err := x.verifyMsgSignature(signedVoteObj, sig, epochInfo.Masternodes)
ProposedBlockInfo: quorumCert.ProposedBlockInfo,
GapNumber: quorumCert.GapNumber,
}), sig, epochInfo.Masternodes)
if err != nil { if err != nil {
log.Error("[verifyQC] Error while verfying QC message signatures", "Error", err) log.Error("[verifyQC] Error while verfying QC message signatures", "Error", err)
haveError = errors.New("error while verfying QC message signatures") haveError = errors.New("error while verfying QC message signatures")

View file

@ -140,7 +140,16 @@ func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.Time
return errors.New("empty master node lists from snapshot") return errors.New("empty master node lists from snapshot")
} }
signatures, duplicates := UniqueSignatures(timeoutCert.Signatures) signedTimeoutObj := types.TimeoutSigHash(&types.TimeoutForSign{
Round: timeoutCert.Round,
GapNumber: timeoutCert.GapNumber,
})
signatures, duplicates, err := RecoverUniqueSigners(signedTimeoutObj, timeoutCert.Signatures)
if err != nil {
log.Error("[verifyTC] Error while getting unique signatures", "tcRound", timeoutCert.Round, "tcGapNumber", timeoutCert.GapNumber, "tcSignLen", len(timeoutCert.Signatures), "error", err)
return err
}
if len(duplicates) != 0 { if len(duplicates) != 0 {
for _, d := range duplicates { for _, d := range duplicates {
log.Warn("[verifyQC] duplicated signature in QC", "duplicate", common.Bytes2Hex(d)) log.Warn("[verifyQC] duplicated signature in QC", "duplicate", common.Bytes2Hex(d))
@ -164,11 +173,6 @@ func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.Time
var mutex sync.Mutex var mutex sync.Mutex
var haveError error var haveError error
signedTimeoutObj := types.TimeoutSigHash(&types.TimeoutForSign{
Round: timeoutCert.Round,
GapNumber: timeoutCert.GapNumber,
})
for _, signature := range signatures { for _, signature := range signatures {
go func(sig types.Signature) { go func(sig types.Signature) {
defer wg.Done() defer wg.Done()

View file

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
"sync"
"github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
@ -75,20 +76,60 @@ func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.A
return masternodes return masternodes
} }
func UniqueSignatures(signatureSlice []types.Signature) ([]types.Signature, []types.Signature) { func RecoverUniqueSigners(signedHash common.Hash, signatureList []types.Signature) ([]types.Signature, []types.Signature, error) {
keys := make(map[string]bool) if (signedHash == common.Hash{}) {
list := []types.Signature{} return nil, nil, errors.New("signedHash cannot be empty")
duplicates := []types.Signature{} }
for _, signature := range signatureSlice { if len(signatureList) == 0 {
hexOfSig := common.Bytes2Hex(signature) return []types.Signature{}, []types.Signature{}, nil
if _, value := keys[hexOfSig]; !value { }
keys[hexOfSig] = true
list = append(list, signature) type Message struct {
pubkey common.Address
sig types.Signature
}
result := make(chan Message, len(signatureList))
errCh := make(chan error, len(signatureList))
var wg sync.WaitGroup
wg.Add(len(signatureList))
for _, signature := range signatureList {
go func(sig types.Signature) {
defer wg.Done()
pubkey, err := crypto.Ecrecover(signedHash.Bytes(), signature)
if err != nil {
log.Error("[UniqueSignatures] error while recovering public key", "error", err, "signature", common.Bytes2Hex(signature), "signedHash", signedHash.Hex())
errCh <- err
return
}
var signerAddress common.Address
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
result <- Message{pubkey: signerAddress, sig: sig}
}(signature)
}
wg.Wait()
close(result)
close(errCh)
if len(errCh) > 0 {
return nil, nil, <-errCh
}
keys := make(map[string]struct{})
uniqueSigners := make([]types.Signature, 0, len(result))
duplicates := make([]types.Signature, 0, len(result))
for r := range result {
pubkeyHex := r.pubkey.Hex()
if _, ok := keys[pubkeyHex]; !ok {
keys[pubkeyHex] = struct{}{}
uniqueSigners = append(uniqueSigners, r.sig)
} else { } else {
duplicates = append(duplicates, signature) log.Warn("[UniqueSignatures] duplicate signing found", "pubkey", pubkeyHex, "signedMessage", signedHash.Hex(), "signature", r.sig)
duplicates = append(duplicates, r.sig)
} }
} }
return list, duplicates
return uniqueSigners, duplicates, nil
} }
func (x *XDPoS_v2) signSignature(signingHash common.Hash) (types.Signature, error) { func (x *XDPoS_v2) signSignature(signingHash common.Hash) (types.Signature, error) {