diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index e974a73fd8..eee0137d80 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -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") } - 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 { for _, d := range duplicates { 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 { go func(sig types.Signature) { defer wg.Done() - verified, _, err := x.verifyMsgSignature(types.VoteSigHash(&types.VoteForSign{ - ProposedBlockInfo: quorumCert.ProposedBlockInfo, - GapNumber: quorumCert.GapNumber, - }), sig, epochInfo.Masternodes) + verified, _, err := x.verifyMsgSignature(signedVoteObj, sig, epochInfo.Masternodes) if err != nil { log.Error("[verifyQC] Error while verfying QC message signatures", "Error", err) haveError = errors.New("error while verfying QC message signatures") diff --git a/consensus/XDPoS/engines/engine_v2/timeout.go b/consensus/XDPoS/engines/engine_v2/timeout.go index ff689f45ef..3f7fe8d855 100644 --- a/consensus/XDPoS/engines/engine_v2/timeout.go +++ b/consensus/XDPoS/engines/engine_v2/timeout.go @@ -140,7 +140,16 @@ func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.Time 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 { for _, d := range duplicates { 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 haveError error - signedTimeoutObj := types.TimeoutSigHash(&types.TimeoutForSign{ - Round: timeoutCert.Round, - GapNumber: timeoutCert.GapNumber, - }) - for _, signature := range signatures { go func(sig types.Signature) { defer wg.Done() diff --git a/consensus/XDPoS/engines/engine_v2/utils.go b/consensus/XDPoS/engines/engine_v2/utils.go index 0a0f2ad7a6..0b6d2401c6 100644 --- a/consensus/XDPoS/engines/engine_v2/utils.go +++ b/consensus/XDPoS/engines/engine_v2/utils.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "math/big" + "sync" "github.com/XinFinOrg/XDPoSChain/accounts" "github.com/XinFinOrg/XDPoSChain/common" @@ -75,20 +76,60 @@ func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.A return masternodes } -func UniqueSignatures(signatureSlice []types.Signature) ([]types.Signature, []types.Signature) { - keys := make(map[string]bool) - list := []types.Signature{} - duplicates := []types.Signature{} - for _, signature := range signatureSlice { - hexOfSig := common.Bytes2Hex(signature) - if _, value := keys[hexOfSig]; !value { - keys[hexOfSig] = true - list = append(list, signature) +func RecoverUniqueSigners(signedHash common.Hash, signatureList []types.Signature) ([]types.Signature, []types.Signature, error) { + if (signedHash == common.Hash{}) { + return nil, nil, errors.New("signedHash cannot be empty") + } + if len(signatureList) == 0 { + return []types.Signature{}, []types.Signature{}, nil + } + + 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 { - 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) {