package types import ( "errors" "fmt" "github.com/tendermint/tendermint/crypto/batch" "github.com/tendermint/tendermint/crypto/tmhash" tmmath "github.com/tendermint/tendermint/libs/math" ) // VerifyCommit verifies +2/3 of the set had signed the given commit. // // It checks all the signatures! While it's safe to exit as soon as we have // 2/3+ signatures, doing so would impact incentivization logic in the ABCI // application that depends on the LastCommitInfo sent in BeginBlock, which // includes which validators signed. For instance, Gaia incentivizes proposers // with a bonus for including more than +2/3 of the signatures. func VerifyCommit(chainID string, vals *ValidatorSet, blockID BlockID, height int64, commit *Commit) error { // run a basic validation of the arguments if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil { return err } // calculate voting power needed. Note that total voting power is capped to // 1/8th of max int64 so this operation should never overflow votingPowerNeeded := vals.TotalVotingPower() * 2 / 3 // ignore all absent signatures ignore := func(c CommitSig) bool { return c.Absent() } // only count the signatures that are for the block count := func(c CommitSig) bool { return c.ForBlock() } // attempt to batch verify cacheSignBytes, success, err := tryVerifyCommitBatch( chainID, vals, commit, votingPowerNeeded, ignore, count, true, true) if err != nil { return err } if success { return nil } // if verification failed or is not supported then fallback to single verification return verifyCommitSingle(chainID, vals, commit, votingPowerNeeded, cacheSignBytes, ignore, count, true, true) } // LIGHT CLIENT VERIFICATION METHODS // VerifyCommitLight verifies +2/3 of the set had signed the given commit. // // This method is primarily used by the light client and does not check all the // signatures. func VerifyCommitLight(chainID string, vals *ValidatorSet, blockID BlockID, height int64, commit *Commit) error { // run a basic validation of the arguments if err := verifyBasicValsAndCommit(vals, commit, height, blockID); err != nil { return err } // calculate voting power needed votingPowerNeeded := vals.TotalVotingPower() * 2 / 3 // ignore all commit signatures that are not for the block ignore := func(c CommitSig) bool { return !c.ForBlock() } // count all the remaining signatures count := func(c CommitSig) bool { return true } // attempt to batch verify cacheSignBytes, success, err := tryVerifyCommitBatch( chainID, vals, commit, votingPowerNeeded, ignore, count, false, true) if err != nil { return err } if success { return nil } // if verification failed or is not supported then fallback to single verification return verifyCommitSingle(chainID, vals, commit, votingPowerNeeded, cacheSignBytes, ignore, count, false, true) } // VerifyCommitLightTrusting verifies that trustLevel of the validator set signed // this commit. // // NOTE the given validators do not necessarily correspond to the validator set // for this commit, but there may be some intersection. // // This method is primarily used by the light client and does not check all the // signatures. func VerifyCommitLightTrusting(chainID string, vals *ValidatorSet, commit *Commit, trustLevel tmmath.Fraction) error { // sanity checks if vals == nil { return errors.New("nil validator set") } if trustLevel.Denominator == 0 { return errors.New("trustLevel has zero Denominator") } if commit == nil { return errors.New("nil commit") } // safely calculate voting power needed. totalVotingPowerMulByNumerator, overflow := safeMul(vals.TotalVotingPower(), int64(trustLevel.Numerator)) if overflow { return errors.New("int64 overflow while calculating voting power needed. please provide smaller trustLevel numerator") } votingPowerNeeded := totalVotingPowerMulByNumerator / int64(trustLevel.Denominator) // ignore all commit signatures that are not for the block ignore := func(c CommitSig) bool { return !c.ForBlock() } // count all the remaining signatures count := func(c CommitSig) bool { return true } // attempt to batch verify commit. As the validator set doesn't necessarily // correspond with the validator set that signed the block we need to look // up by address rather than index. cacheSignBytes, success, err := tryVerifyCommitBatch( chainID, vals, commit, votingPowerNeeded, ignore, count, false, false) if err != nil { return err } if success { return nil } // attempt with single verification return verifyCommitSingle(chainID, vals, commit, votingPowerNeeded, cacheSignBytes, ignore, count, false, false) } // ValidateHash returns an error if the hash is not empty, but its // size != tmhash.Size. func ValidateHash(h []byte) error { if len(h) > 0 && len(h) != tmhash.Size { return fmt.Errorf("expected size to be %d bytes, got %d bytes", tmhash.Size, len(h), ) } return nil } // Batch verification // tryVerifyCommitBatch attempts to batch verify. If it is not supported or // verification fails it returns false. If there is an error in the signatures // or the way that they are counted an error is returned. A cache of all the // commits in byte form is returned in case it needs to be used again for single // verification func tryVerifyCommitBatch( chainID string, vals *ValidatorSet, commit *Commit, votingPowerNeeded int64, ignoreSig func(CommitSig) bool, countSig func(CommitSig) bool, countAllSignatures bool, lookUpByIndex bool, ) (map[string][]byte, bool, error) { var ( val *Validator valIdx int32 seenVals = make(map[int32]int, len(commit.Signatures)) talliedVotingPower int64 = 0 // we keep a cache of the signed bytes to make it quicker to verify // individually if we need to cacheSignBytes = make(map[string][]byte, len(commit.Signatures)) ) // attempt to create a batch verifier bv, ok := batch.CreateBatchVerifier(vals.GetProposer().PubKey) // check if batch verification is supported if !ok || len(commit.Signatures) < 2 { return cacheSignBytes, false, nil } for idx, commitSig := range commit.Signatures { // skip over signatures that should be ignored if ignoreSig(commitSig) { continue } // If the vals and commit have a 1-to-1 correspondance we can retrieve // them by index else we need to retrieve them by address if lookUpByIndex { val = vals.Validators[idx] } else { valIdx, val = vals.GetByAddress(commitSig.ValidatorAddress) // if the signature doesn't belong to anyone in the validator set // then we just skip over it if val == nil { continue } // because we are getting validators by address we need to make sure // that the same validator doesn't commit twice if firstIndex, ok := seenVals[valIdx]; ok { secondIndex := idx return cacheSignBytes, false, fmt.Errorf("double vote from %v (%d and %d)", val, firstIndex, secondIndex) } seenVals[valIdx] = idx } // Validate signature. voteSignBytes := commit.VoteSignBytes(chainID, int32(idx)) // cache the signBytes in case batch verification fails cacheSignBytes[string(val.PubKey.Bytes())] = voteSignBytes // add the key, sig and message to the verifier if err := bv.Add(val.PubKey, voteSignBytes, commitSig.Signature); err != nil { return cacheSignBytes, false, err } // If this signature counts then add the voting power of the validator // to the tally if countSig(commitSig) { talliedVotingPower += val.VotingPower } // if we don't need to verify all signatures and already have sufficient // voting power we can break from batching and verify all the signatures if !countAllSignatures && talliedVotingPower > votingPowerNeeded { break } } // ensure that we have batched together enough signatures to exceed the // voting power needed else there is no need to even verify if got, needed := talliedVotingPower, votingPowerNeeded; got <= needed { return cacheSignBytes, false, ErrNotEnoughVotingPowerSigned{Got: got, Needed: needed} } // attempt to verify the batch. If this fails, fall back to single // verification if bv.Verify() { // success return cacheSignBytes, true, nil } // verification failed return cacheSignBytes, false, nil } // Single Verification // verifyCommitSingle single verifies commits. // If a key does not support batch verification, or batch verification fails this will be used // This method is used to check all the signatures included in a commit. // It is used in consensus for validating a block LastCommit. // CONTRACT: both commit and validator set should have passed validate basic func verifyCommitSingle( chainID string, vals *ValidatorSet, commit *Commit, votingPowerNeeded int64, cachedVals map[string][]byte, ignoreSig func(CommitSig) bool, countSig func(CommitSig) bool, countAllSignatures bool, lookUpByIndex bool, ) error { var ( val *Validator valIdx int32 seenVals = make(map[int32]int, len(commit.Signatures)) talliedVotingPower int64 = 0 voteSignBytes []byte ) for idx, commitSig := range commit.Signatures { if ignoreSig(commitSig) { continue } // If the vals and commit have a 1-to-1 correspondance we can retrieve // them by index else we need to retrieve them by address if lookUpByIndex { val = vals.Validators[idx] } else { valIdx, val = vals.GetByAddress(commitSig.ValidatorAddress) // if the signature doesn't belong to anyone in the validator set // then we just skip over it if val == nil { continue } // because we are getting validators by address we need to make sure // that the same validator doesn't commit twice if firstIndex, ok := seenVals[valIdx]; ok { secondIndex := idx return fmt.Errorf("double vote from %v (%d and %d)", val, firstIndex, secondIndex) } seenVals[valIdx] = idx } // Check if we have the validator in the cache if cachedVote, ok := cachedVals[string(val.PubKey.Bytes())]; !ok { voteSignBytes = commit.VoteSignBytes(chainID, int32(idx)) } else { voteSignBytes = cachedVote } if !val.PubKey.VerifySignature(voteSignBytes, commitSig.Signature) { return fmt.Errorf("wrong signature (#%d): %X", idx, commitSig.Signature) } // If this signature counts then add the voting power of the validator // to the tally if countSig(commitSig) { talliedVotingPower += val.VotingPower } // check if we have enough signatures and can thus exit early if !countAllSignatures && talliedVotingPower > votingPowerNeeded { return nil } } if got, needed := talliedVotingPower, votingPowerNeeded; got <= needed { return ErrNotEnoughVotingPowerSigned{Got: got, Needed: needed} } return nil } func verifyBasicValsAndCommit(vals *ValidatorSet, commit *Commit, height int64, blockID BlockID) error { if vals == nil { return errors.New("nil validator set") } if commit == nil { return errors.New("nil commit") } if vals.Size() != len(commit.Signatures) { return NewErrInvalidCommitSignatures(vals.Size(), len(commit.Signatures)) } // Validate Height and BlockID. if height != commit.Height { return NewErrInvalidCommitHeight(height, commit.Height) } if !blockID.Equals(commit.BlockID) { return fmt.Errorf("invalid commit -- wrong block ID: want %v, got %v", blockID, commit.BlockID) } return nil }