github.com/noirx94/tendermintmp@v0.0.1/light/verifier.go (about)

     1  package light
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	tmmath "github.com/tendermint/tendermint/libs/math"
    10  	"github.com/tendermint/tendermint/types"
    11  )
    12  
    13  var (
    14  	// DefaultTrustLevel - new header can be trusted if at least one correct
    15  	// validator signed it.
    16  	DefaultTrustLevel = tmmath.Fraction{Numerator: 1, Denominator: 3}
    17  )
    18  
    19  // VerifyNonAdjacent verifies non-adjacent untrustedHeader against
    20  // trustedHeader. It ensures that:
    21  //
    22  //	a) trustedHeader can still be trusted (if not, ErrOldHeaderExpired is returned)
    23  //	b) untrustedHeader is valid (if not, ErrInvalidHeader is returned)
    24  //	c) trustLevel ([1/3, 1]) of trustedHeaderVals (or trustedHeaderNextVals)
    25  //  signed correctly (if not, ErrNewValSetCantBeTrusted is returned)
    26  //	d) more than 2/3 of untrustedVals have signed h2
    27  //    (otherwise, ErrInvalidHeader is returned)
    28  //  e) headers are non-adjacent.
    29  //
    30  // maxClockDrift defines how much untrustedHeader.Time can drift into the
    31  // future.
    32  func VerifyNonAdjacent(
    33  	trustedHeader *types.SignedHeader, // height=X
    34  	trustedVals *types.ValidatorSet, // height=X or height=X+1
    35  	untrustedHeader *types.SignedHeader, // height=Y
    36  	untrustedVals *types.ValidatorSet, // height=Y
    37  	trustingPeriod time.Duration,
    38  	now time.Time,
    39  	maxClockDrift time.Duration,
    40  	trustLevel tmmath.Fraction) error {
    41  
    42  	if untrustedHeader.Height == trustedHeader.Height+1 {
    43  		return errors.New("headers must be non adjacent in height")
    44  	}
    45  
    46  	if HeaderExpired(trustedHeader, trustingPeriod, now) {
    47  		return ErrOldHeaderExpired{trustedHeader.Time.Add(trustingPeriod), now}
    48  	}
    49  
    50  	if err := verifyNewHeaderAndVals(
    51  		untrustedHeader, untrustedVals,
    52  		trustedHeader,
    53  		now, maxClockDrift); err != nil {
    54  		return ErrInvalidHeader{err}
    55  	}
    56  
    57  	// Ensure that +`trustLevel` (default 1/3) or more of last trusted validators signed correctly.
    58  	err := trustedVals.VerifyCommitLightTrusting(trustedHeader.ChainID, untrustedHeader.Commit, trustLevel)
    59  	if err != nil {
    60  		switch e := err.(type) {
    61  		case types.ErrNotEnoughVotingPowerSigned:
    62  			return ErrNewValSetCantBeTrusted{e}
    63  		default:
    64  			return e
    65  		}
    66  	}
    67  
    68  	// Ensure that +2/3 of new validators signed correctly.
    69  	//
    70  	// NOTE: this should always be the last check because untrustedVals can be
    71  	// intentionally made very large to DOS the light client. not the case for
    72  	// VerifyAdjacent, where validator set is known in advance.
    73  	if err := untrustedVals.VerifyCommitLight(trustedHeader.ChainID, untrustedHeader.Commit.BlockID,
    74  		untrustedHeader.Height, untrustedHeader.Commit); err != nil {
    75  		return ErrInvalidHeader{err}
    76  	}
    77  
    78  	return nil
    79  }
    80  
    81  // VerifyAdjacent verifies directly adjacent untrustedHeader against
    82  // trustedHeader. It ensures that:
    83  //
    84  //  a) trustedHeader can still be trusted (if not, ErrOldHeaderExpired is returned)
    85  //  b) untrustedHeader is valid (if not, ErrInvalidHeader is returned)
    86  //  c) untrustedHeader.ValidatorsHash equals trustedHeader.NextValidatorsHash
    87  //  d) more than 2/3 of new validators (untrustedVals) have signed h2
    88  //    (otherwise, ErrInvalidHeader is returned)
    89  //  e) headers are adjacent.
    90  //
    91  // maxClockDrift defines how much untrustedHeader.Time can drift into the
    92  // future.
    93  func VerifyAdjacent(
    94  	trustedHeader *types.SignedHeader, // height=X
    95  	untrustedHeader *types.SignedHeader, // height=X+1
    96  	untrustedVals *types.ValidatorSet, // height=X+1
    97  	trustingPeriod time.Duration,
    98  	now time.Time,
    99  	maxClockDrift time.Duration) error {
   100  
   101  	if untrustedHeader.Height != trustedHeader.Height+1 {
   102  		return errors.New("headers must be adjacent in height")
   103  	}
   104  
   105  	if HeaderExpired(trustedHeader, trustingPeriod, now) {
   106  		return ErrOldHeaderExpired{trustedHeader.Time.Add(trustingPeriod), now}
   107  	}
   108  
   109  	if err := verifyNewHeaderAndVals(
   110  		untrustedHeader, untrustedVals,
   111  		trustedHeader,
   112  		now, maxClockDrift); err != nil {
   113  		return ErrInvalidHeader{err}
   114  	}
   115  
   116  	// Check the validator hashes are the same
   117  	if !bytes.Equal(untrustedHeader.ValidatorsHash, trustedHeader.NextValidatorsHash) {
   118  		err := fmt.Errorf("expected old header next validators (%X) to match those from new header (%X)",
   119  			trustedHeader.NextValidatorsHash,
   120  			untrustedHeader.ValidatorsHash,
   121  		)
   122  		return err
   123  	}
   124  
   125  	// Ensure that +2/3 of new validators signed correctly.
   126  	if err := untrustedVals.VerifyCommitLight(trustedHeader.ChainID, untrustedHeader.Commit.BlockID,
   127  		untrustedHeader.Height, untrustedHeader.Commit); err != nil {
   128  		return ErrInvalidHeader{err}
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // Verify combines both VerifyAdjacent and VerifyNonAdjacent functions.
   135  func Verify(
   136  	trustedHeader *types.SignedHeader, // height=X
   137  	trustedVals *types.ValidatorSet, // height=X or height=X+1
   138  	untrustedHeader *types.SignedHeader, // height=Y
   139  	untrustedVals *types.ValidatorSet, // height=Y
   140  	trustingPeriod time.Duration,
   141  	now time.Time,
   142  	maxClockDrift time.Duration,
   143  	trustLevel tmmath.Fraction) error {
   144  
   145  	if untrustedHeader.Height != trustedHeader.Height+1 {
   146  		return VerifyNonAdjacent(trustedHeader, trustedVals, untrustedHeader, untrustedVals,
   147  			trustingPeriod, now, maxClockDrift, trustLevel)
   148  	}
   149  
   150  	return VerifyAdjacent(trustedHeader, untrustedHeader, untrustedVals, trustingPeriod, now, maxClockDrift)
   151  }
   152  
   153  func verifyNewHeaderAndVals(
   154  	untrustedHeader *types.SignedHeader,
   155  	untrustedVals *types.ValidatorSet,
   156  	trustedHeader *types.SignedHeader,
   157  	now time.Time,
   158  	maxClockDrift time.Duration) error {
   159  
   160  	if err := untrustedHeader.ValidateBasic(trustedHeader.ChainID); err != nil {
   161  		return fmt.Errorf("untrustedHeader.ValidateBasic failed: %w", err)
   162  	}
   163  
   164  	if untrustedHeader.Height <= trustedHeader.Height {
   165  		return fmt.Errorf("expected new header height %d to be greater than one of old header %d",
   166  			untrustedHeader.Height,
   167  			trustedHeader.Height)
   168  	}
   169  
   170  	if !untrustedHeader.Time.After(trustedHeader.Time) {
   171  		return fmt.Errorf("expected new header time %v to be after old header time %v",
   172  			untrustedHeader.Time,
   173  			trustedHeader.Time)
   174  	}
   175  
   176  	if !untrustedHeader.Time.Before(now.Add(maxClockDrift)) {
   177  		return fmt.Errorf("new header has a time from the future %v (now: %v; max clock drift: %v)",
   178  			untrustedHeader.Time,
   179  			now,
   180  			maxClockDrift)
   181  	}
   182  
   183  	if !bytes.Equal(untrustedHeader.ValidatorsHash, untrustedVals.Hash()) {
   184  		return fmt.Errorf("expected new header validators (%X) to match those that were supplied (%X) at height %d",
   185  			untrustedHeader.ValidatorsHash,
   186  			untrustedVals.Hash(),
   187  			untrustedHeader.Height,
   188  		)
   189  	}
   190  
   191  	return nil
   192  }
   193  
   194  // ValidateTrustLevel checks that trustLevel is within the allowed range [1/3,
   195  // 1]. If not, it returns an error. 1/3 is the minimum amount of trust needed
   196  // which does not break the security model.
   197  func ValidateTrustLevel(lvl tmmath.Fraction) error {
   198  	if lvl.Numerator*3 < lvl.Denominator || // < 1/3
   199  		lvl.Numerator > lvl.Denominator || // > 1
   200  		lvl.Denominator == 0 {
   201  		return fmt.Errorf("trustLevel must be within [1/3, 1], given %v", lvl)
   202  	}
   203  	return nil
   204  }
   205  
   206  // HeaderExpired return true if the given header expired.
   207  func HeaderExpired(h *types.SignedHeader, trustingPeriod time.Duration, now time.Time) bool {
   208  	expirationTime := h.Time.Add(trustingPeriod)
   209  	return !expirationTime.After(now)
   210  }
   211  
   212  // VerifyBackwards verifies an untrusted header with a height one less than
   213  // that of an adjacent trusted header. It ensures that:
   214  //
   215  // 	a) untrusted header is valid
   216  //  b) untrusted header has a time before the trusted header
   217  //  c) that the LastBlockID hash of the trusted header is the same as the hash
   218  //  of the trusted header
   219  //
   220  //  For any of these cases ErrInvalidHeader is returned.
   221  func VerifyBackwards(untrustedHeader, trustedHeader *types.Header) error {
   222  	if err := untrustedHeader.ValidateBasic(); err != nil {
   223  		return ErrInvalidHeader{err}
   224  	}
   225  
   226  	if untrustedHeader.ChainID != trustedHeader.ChainID {
   227  		return ErrInvalidHeader{errors.New("header belongs to another chain")}
   228  	}
   229  
   230  	if !untrustedHeader.Time.Before(trustedHeader.Time) {
   231  		return ErrInvalidHeader{
   232  			fmt.Errorf("expected older header time %v to be before new header time %v",
   233  				untrustedHeader.Time,
   234  				trustedHeader.Time)}
   235  	}
   236  
   237  	if !bytes.Equal(untrustedHeader.Hash(), trustedHeader.LastBlockID.Hash) {
   238  		return ErrInvalidHeader{
   239  			fmt.Errorf("older header hash %X does not match trusted header's last block %X",
   240  				untrustedHeader.Hash(),
   241  				trustedHeader.LastBlockID.Hash)}
   242  	}
   243  
   244  	return nil
   245  }