New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add signing spec #3061
Merged
Merged
add signing spec #3061
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
# Validator Signing | ||
|
||
Here we specify the rules for validating a proposal and vote before signing. | ||
First we include some general notes on validating data structures common to both types. | ||
We then provide specific validation rules for each. Finally, we include validation rules to prevent double-sigining. | ||
|
||
## SignedMsgType | ||
|
||
The `SignedMsgType` is a single byte that refers to the type of the message | ||
being signed. It is defined in Go as follows: | ||
|
||
``` | ||
// SignedMsgType is a type of signed message in the consensus. | ||
type SignedMsgType byte | ||
|
||
const ( | ||
// Votes | ||
PrevoteType SignedMsgType = 0x01 | ||
PrecommitType SignedMsgType = 0x02 | ||
|
||
// Proposals | ||
ProposalType SignedMsgType = 0x20 | ||
) | ||
``` | ||
|
||
All signed messages must correspond to one of these types. | ||
|
||
## Timestamp | ||
|
||
Timestamp validation is subtle and there are currently no bounds placed on the | ||
timestamp included in a proposal or vote. It is expected that validators will honestly | ||
report their local clock time. The median of all timestamps | ||
included in a commit is used as the timestamp for the next block height. | ||
|
||
Timestamps are expected to be strictly monotonic for a given validator, though | ||
this is not currently enforced. | ||
|
||
## ChainID | ||
|
||
ChainID is an unstructured string with a max length of 50-bytes. | ||
In the future, the ChainID may become structured, and may take on longer lengths. | ||
For now, it is recommended that signers be configured for a particular ChainID, | ||
and to only sign votes and proposals corresponding to that ChainID. | ||
|
||
## BlockID | ||
|
||
BlockID is the structure used to represent the block: | ||
|
||
``` | ||
type BlockID struct { | ||
Hash []byte | ||
PartsHeader PartSetHeader | ||
} | ||
|
||
type PartSetHeader struct { | ||
Hash []byte | ||
Total int | ||
} | ||
``` | ||
|
||
To be included in a valid vote or proposal, BlockID must either represent a `nil` block, or a complete one. | ||
We introduce two methods, `BlockID.IsNil()` and `BlockID.IsComplete()` for these cases, respectively. | ||
|
||
`BlockID.IsNil()` returns true for BlockID `b` if each of the following | ||
are true: | ||
|
||
``` | ||
b.Hash == nil | ||
b.PartsHeader.Total == 0 | ||
b.PartsHeader.Hash == nil | ||
``` | ||
|
||
`BlockID.IsComplete()` returns true for BlockID `b` if each of the following | ||
are true: | ||
|
||
``` | ||
len(b.Hash) == 32 | ||
b.PartsHeader.Total > 0 | ||
len(b.PartsHeader.Hash) == 32 | ||
``` | ||
|
||
## Proposals | ||
|
||
The structure of a propsal for signing looks like: | ||
|
||
``` | ||
type CanonicalProposal struct { | ||
Type SignedMsgType // type alias for byte | ||
Height int64 `binary:"fixed64"` | ||
Round int64 `binary:"fixed64"` | ||
POLRound int64 `binary:"fixed64"` | ||
BlockID BlockID | ||
Timestamp time.Time | ||
ChainID string | ||
} | ||
``` | ||
|
||
A proposal is valid if each of the following lines evaluates to true for proposal `p`: | ||
|
||
``` | ||
p.Type == 0x20 | ||
p.Height > 0 | ||
p.Round >= 0 | ||
p.POLRound >= -1 | ||
p.BlockID.IsComplete() | ||
``` | ||
|
||
In other words, a proposal is valid for signing if it contains the type of a Proposal | ||
(0x20), has a positive, non-zero height, a | ||
non-negative round, a POLRound not less than -1, and a complete BlockID. | ||
|
||
## Votes | ||
|
||
The structure of a vote for signing looks like: | ||
|
||
``` | ||
type CanonicalVote struct { | ||
Type SignedMsgType // type alias for byte | ||
Height int64 `binary:"fixed64"` | ||
Round int64 `binary:"fixed64"` | ||
Timestamp time.Time | ||
BlockID BlockID | ||
ChainID string | ||
} | ||
``` | ||
|
||
A vote is valid if each of the following lines evaluates to true for vote `v`: | ||
|
||
``` | ||
v.Type == 0x1 || v.Type == 0x2 | ||
v.Height > 0 | ||
v.Round >= 0 | ||
v.BlockID.IsNil() || v.BlockID.IsValid() | ||
``` | ||
|
||
In other words, a vote is valid for signing if it contains the type of a Prevote | ||
or Precommit (0x1 or 0x2, respectively), has a positive, non-zero height, a | ||
non-negative round, and an empty or valid BlockID. | ||
|
||
## Invalid Votes and Proposals | ||
|
||
Votes and proposals which do not satisfy the above rules are considered invalid. | ||
Peers gossipping invalid votes and proposals may be disconnected from other peers on the network. | ||
Note, however, that there is not currently any explicit mechanism to punish validators signing votes or proposals that fail | ||
these basic validation rules. | ||
|
||
## Double Signing | ||
|
||
Signers must be careful not to sign conflicting messages, also known as "double signing" or "equivocating". | ||
Tendermint has mechanisms to publish evidence of validators that signed conflicting votes, so they can be punished | ||
by the application. Note Tendermint does not currently handle evidence of conflciting proposals, though it may in the future. | ||
|
||
### State | ||
|
||
To prevent such double signing, signers must track the height, round, and type of the last message signed. | ||
Assume the signer keeps the following state, `s`: | ||
|
||
``` | ||
type LastSigned struct { | ||
Height int64 | ||
Round int64 | ||
Type SignedMsgType // byte | ||
} | ||
``` | ||
|
||
After signing a vote or proposal `m`, the signer sets: | ||
|
||
``` | ||
s.Height = m.Height | ||
s.Round = m.Round | ||
s.Type = m.Type | ||
``` | ||
|
||
### Proposals | ||
|
||
A signer should only sign a proposal `p` if any of the following lines are true: | ||
|
||
``` | ||
p.Height > s.Height | ||
p.Height == s.Height && p.Round > s.Round | ||
``` | ||
|
||
In other words, a proposal should only be signed if it's at a higher height, or a higher round for the same height. | ||
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. | ||
|
||
### Votes | ||
|
||
A signer should only sign a vote `v` if any of the following lines are true: | ||
|
||
``` | ||
v.Height > s.Height | ||
v.Height == s.Height && v.Round > s.Round | ||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x1 && s.Step == 0x20 | ||
v.Height == s.Height && v.Round == s.Round && v.Step == 0x2 && s.Step != 0x2 | ||
``` | ||
|
||
In other words, a vote should only be signed if it's: | ||
|
||
- at a higher height | ||
- at a higher round for the same height | ||
- a prevote for the same height and round where we haven't signed a prevote or precommit (but have signed a proposal) | ||
- a precommit for the same height and round where we haven't signed a precommit (but have signed a proposal and/or a prevote) | ||
|
||
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. | ||
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. |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Introducing these methods in the go-code as well would make the validation more readable.