ADR 024: SignBytes and validator types in privval
Context
Currently, the messages exchanged between tendermint and a (potentially remote) signer/validator,
namely votes, proposals, and heartbeats, are encoded as a JSON string
(e.g., via Vote.SignBytes(...)) and then
signed . JSON encoding is sub-optimal for both, hardware wallets
and for usage in ethereum smart contracts. Both is laid down in detail in issue#1622.
Also, there are currently no differences between sign-request and -replies. Also, there is no possibility for a remote signer to include an error code or message in case something went wrong. The messages exchanged between tendermint and a remote signer currently live in privval/socket.go and encapsulate the corresponding types in types.
Decision
- restructure vote, proposal, and heartbeat such that their encoding is easily parseable by hardware devices and smart contracts using a binary encoding format (amino in this case)
- split up the messages exchanged between tendermint and remote signers into requests and responses (see details below)
- include an error type in responses
Overview
+--------------+ +----------------+| | SignXRequest | ||Remote signer |<---------------------+ tendermint || (e.g. KMS) | | || +--------------------->| |+--------------+ SignedXReply +----------------+SignXRequest {x: X}SignedXReply {x: Xsig: Signature // []byteerr: Error{code: intdesc: string}}
TODO: Alternatively, the type X might directly include the signature. A lot of places expect a vote with a
signature and do not necessarily deal with “Replies”.
Still exploring what would work best here.
This would look like (exemplified using X = Vote):
Vote {// all fields besides signature}SignedVote {Vote VoteSignature []byte}SignVoteRequest {Vote Vote}SignedVoteReply {Vote SignedVoteErr Error}
Note: There was a related discussion around including a fingerprint of, or, the whole public-key into each sign-request to tell the signer which corresponding private-key to use to sign the message. This is particularly relevant in the context of the KMS but is currently not considered in this ADR.
Vote
As explained in issue#1622 Vote will be changed to contain the following fields
(notation in protobuf-like syntax for easy readability):
// vanilla protobuf / amino encodedmessage Vote {Version fixed32Height sfixed64Round sfixed32VoteType fixed32Timestamp Timestamp // << using protobuf definitionBlockID BlockID // << as already definedChainID string // at the end because length could vary a lot}// this is an amino registered type; like currently privval.SignVoteMsg:// registered with "tendermint/socketpv/SignVoteRequest"message SignVoteRequest {Vote vote}// amino registered type// registered with "tendermint/socketpv/SignedVoteReply"message SignedVoteReply {Vote VoteSignature SignatureErr Error}// we will use this type everywhere belowmessage Error {Type uint // error codeDescription string // optional description}
The ChainID gets moved into the vote message directly. Previously, it was injected
using the Signable interface method SignBytes(chainID string) []byte. Also, the
signature won’t be included directly, only in the corresponding SignedVoteReply message.
Proposal
// vanilla protobuf / amino encodedmessage Proposal {Height sfixed64Round sfixed32Timestamp Timestamp // << using protobuf definitionBlockPartsHeader PartSetHeader // as already definedPOLRound sfixed32POLBlockID BlockID // << as already defined}// amino registered with "tendermint/socketpv/SignProposalRequest"message SignProposalRequest {Proposal proposal}// amino registered with "tendermint/socketpv/SignProposalReply"message SignProposalReply {Prop ProposalSig SignatureErr Error // as defined above}
Heartbeat
TODO: clarify if heartbeat also needs a fixed offset and update the fields accordingly:
message Heartbeat {ValidatorAddress AddressValidatorIndex intHeight int64Round intSequence int}// amino registered with "tendermint/socketpv/SignHeartbeatRequest"message SignHeartbeatRequest {Hb Heartbeat}// amino registered with "tendermint/socketpv/SignHeartbeatReply"message SignHeartbeatReply {Hb HeartbeatSig SignatureErr Error // as defined above}
PubKey
TBA - this needs further thoughts: e.g. what todo like in the case of the KMS which holds several keys? How does it know with which key to reply?
SignBytes
SignBytes will not require a ChainID parameter:
type Signable interface {SignBytes() []byte}
And the implementation for vote, heartbeat, proposal will look like:
// type T is one of vote, sign, proposalfunc (tp *T) SignBytes() []byte {bz, err := cdc.MarshalBinary(tp)if err != nil {panic(err)}return bz}
Status
DRAFT
Consequences
Positive
The most relevant positive effect is that the signing bytes can easily be parsed by a hardware module and a smart contract. Besides that:
- clearer separation between requests and responses
- added error messages enable better error handling
Negative
- relatively huge change / refactoring touching quite some code
- lot’s of places assume a
Votewith a signature included -> they will need to - need to modify some interfaces
Neutral
not even the swiss are neutral
