github.com/vipernet-xyz/tm@v0.34.24/spec/consensus/signing.md (about)

     1  # Validator Signing
     2  
     3  Here we specify the rules for validating a proposal and vote before signing.
     4  First we include some general notes on validating data structures common to both types.
     5  We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining.
     6  
     7  ## SignedMsgType
     8  
     9  The `SignedMsgType` is a single byte that refers to the type of the message
    10  being signed. It is defined in Go as follows:
    11  
    12  ```go
    13  // SignedMsgType is a type of signed message in the consensus.
    14  type SignedMsgType byte
    15  
    16  const (
    17   // Votes
    18   PrevoteType   SignedMsgType = 0x01
    19   PrecommitType SignedMsgType = 0x02
    20  
    21   // Proposals
    22   ProposalType SignedMsgType = 0x20
    23  )
    24  ```
    25  
    26  All signed messages must correspond to one of these types.
    27  
    28  ## Timestamp
    29  
    30  Timestamp validation is subtle and there are currently no bounds placed on the
    31  timestamp included in a proposal or vote. It is expected that validators will honestly
    32  report their local clock time. The median of all timestamps
    33  included in a commit is used as the timestamp for the next block height.
    34  
    35  Timestamps are expected to be strictly monotonic for a given validator, though
    36  this is not currently enforced.
    37  
    38  ## ChainID
    39  
    40  ChainID is an unstructured string with a max length of 50-bytes.
    41  In the future, the ChainID may become structured, and may take on longer lengths.
    42  For now, it is recommended that signers be configured for a particular ChainID,
    43  and to only sign votes and proposals corresponding to that ChainID.
    44  
    45  ## BlockID
    46  
    47  BlockID is the structure used to represent the block:
    48  
    49  ```go
    50  type BlockID struct {
    51   Hash        []byte
    52   PartsHeader PartSetHeader
    53  }
    54  
    55  type PartSetHeader struct {
    56   Hash  []byte
    57   Total int
    58  }
    59  ```
    60  
    61  To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one.
    62  We introduce two methods, `BlockID.IsZero()` and `BlockID.IsComplete()` for these cases, respectively.
    63  
    64  `BlockID.IsZero()` returns true for BlockID `b` if each of the following
    65  are true:
    66  
    67  ```go
    68  b.Hash == nil
    69  b.PartsHeader.Total == 0
    70  b.PartsHeader.Hash == nil
    71  ```
    72  
    73  `BlockID.IsComplete()` returns true for BlockID `b` if each of the following
    74  are true:
    75  
    76  ```go
    77  len(b.Hash) == 32
    78  b.PartsHeader.Total > 0
    79  len(b.PartsHeader.Hash) == 32
    80  ```
    81  
    82  ## Proposals
    83  
    84  The structure of a proposal for signing looks like:
    85  
    86  ```go
    87  type CanonicalProposal struct {
    88   Type      SignedMsgType // type alias for byte
    89   Height    int64         `binary:"fixed64"`
    90   Round     int64         `binary:"fixed64"`
    91   POLRound  int64         `binary:"fixed64"`
    92   BlockID   BlockID
    93   Timestamp time.Time
    94   ChainID   string
    95  }
    96  ```
    97  
    98  A proposal is valid if each of the following lines evaluates to true for proposal `p`:
    99  
   100  ```go
   101  p.Type == 0x20
   102  p.Height > 0
   103  p.Round >= 0
   104  p.POLRound >= -1
   105  p.BlockID.IsComplete()
   106  ```
   107  
   108  In other words, a proposal is valid for signing if it contains the type of a Proposal
   109  (0x20), has a positive, non-zero height, a
   110  non-negative round, a POLRound not less than -1, and a complete BlockID.
   111  
   112  ## Votes
   113  
   114  The structure of a vote for signing looks like:
   115  
   116  ```go
   117  type CanonicalVote struct {
   118   Type      SignedMsgType // type alias for byte
   119   Height    int64         `binary:"fixed64"`
   120   Round     int64         `binary:"fixed64"`
   121   BlockID   BlockID
   122   Timestamp time.Time
   123   ChainID   string
   124  }
   125  ```
   126  
   127  A vote is valid if each of the following lines evaluates to true for vote `v`:
   128  
   129  ```go
   130  v.Type == 0x1 || v.Type == 0x2
   131  v.Height > 0
   132  v.Round >= 0
   133  v.BlockID.IsZero() || v.BlockID.IsComplete()
   134  ```
   135  
   136  In other words, a vote is valid for signing if it contains the type of a Prevote
   137  or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a
   138  non-negative round, and an empty or valid BlockID.
   139  
   140  ## Invalid Votes and Proposals
   141  
   142  Votes and proposals which do not satisfy the above rules are considered invalid.
   143  Peers gossipping invalid votes and proposals may be disconnected from other peers on the network.
   144  Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail
   145  these basic validation rules.
   146  
   147  ## Double Signing
   148  
   149  Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating".
   150  Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished
   151  by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future.
   152  
   153  ### State
   154  
   155  To prevent such double signing, signers must track the height, round, and type of the last message signed.
   156  Assume the signer keeps the following state, `s`:
   157  
   158  ```go
   159  type LastSigned struct {
   160   Height int64
   161   Round int64
   162   Type SignedMsgType // byte
   163  }
   164  ```
   165  
   166  After signing a vote or proposal `m`, the signer sets:
   167  
   168  ```go
   169  s.Height = m.Height
   170  s.Round = m.Round
   171  s.Type = m.Type
   172  ```
   173  
   174  ### Proposals
   175  
   176  A signer should only sign a proposal `p` if any of the following lines are true:
   177  
   178  ```go
   179  p.Height > s.Height
   180  p.Height == s.Height && p.Round > s.Round
   181  ```
   182  
   183  In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height.
   184  Once a proposal or vote has been signed for a given height and round, a proposal should never be signed for the same height and round.
   185  
   186  ### Votes
   187  
   188  A signer should only sign a vote `v` if any of the following lines are true:
   189  
   190  ```go
   191  v.Height > s.Height
   192  v.Height == s.Height && v.Round > s.Round
   193  v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20
   194  v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2
   195  ```
   196  
   197  In other words, a vote should only be signed if it's:
   198  
   199  - at a higher height
   200  - at a higher round for the same height
   201  - a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal)
   202  - a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote)
   203  
   204  This means that once a validator signs a prevote for a given height and round, the only other message it can sign for that height and round is a precommit.
   205  And once a validator signs a precommit for a given height and round, it must not sign any other message for that same height and round.
   206  
   207  Note this includes votes for `nil`, ie. where `BlockID.IsZero()` is true. If a
   208  signer has already signed a vote where `BlockID.IsZero()` is true, it cannot
   209  sign another vote with the same type for the same height and round where
   210  `BlockID.IsComplete()` is true. Thus only a single vote of a particular type
   211  (ie. 0x01 or 0x02) can be signed for the same height and round.
   212  
   213  ### Other Rules
   214  
   215  According to the rules of Tendermint consensus, once a validator precommits for
   216  a block, they become "locked" on that block, which means they can't prevote for
   217  another block unless they see sufficient justification (ie. a polka from a
   218  higher round). For more details, see the [consensus
   219  spec](https://arxiv.org/abs/1807.04938).
   220  
   221  Violating this rule is known as "amnesia". In contrast to equivocation,
   222  which is easy to detect, amnesia is difficult to detect without access to votes
   223  from all the validators, as this is what constitutes the justification for
   224  "unlocking". Hence, amnesia is not punished within the protocol, and cannot
   225  easily be prevented by a signer. If enough validators simultaneously commit an
   226  amnesia attack, they may cause a fork of the blockchain, at which point an
   227  off-chain protocol must be engaged to collect votes from all the validators and
   228  determine who misbehaved. For more details, see [fork
   229  detection](https://github.com/vipernet-xyz/tm/pull/3978).