# 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]. [privval/socket.go]: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/privval/socket.go#L496-L502 [issue#1622]: https://github.com/tendermint/tendermint/issues/1622 [types]: https://github.com/tendermint/tendermint/tree/master/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: X sig: Signature // []byte err: Error{ code: int desc: 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 Vote Signature []byte } SignVoteRequest { Vote Vote } SignedVoteReply { Vote SignedVote Err 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. [amino]: https://github.com/tendermint/go-amino/ ### Vote As explained in [issue#1622] `Vote` will be changed to contain the following fields (notation in protobuf-like syntax for easy readability): ```proto // vanilla protobuf / amino encoded message Vote { Version fixed32 Height sfixed64 Round sfixed32 VoteType fixed32 Timestamp Timestamp // << using protobuf definition BlockID BlockID // << as already defined ChainID 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 Vote Signature Signature Err Error } // we will use this type everywhere below message Error { Type uint // error code Description 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. [Signable]: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/types/signable.go#L9-L11 ### Proposal ```proto // vanilla protobuf / amino encoded message Proposal { Height sfixed64 Round sfixed32 Timestamp Timestamp // << using protobuf definition BlockPartsHeader PartSetHeader // as already defined POLRound sfixed32 POLBlockID BlockID // << as already defined } // amino registered with "tendermint/socketpv/SignProposalRequest" message SignProposalRequest { Proposal proposal } // amino registered with "tendermint/socketpv/SignProposalReply" message SignProposalReply { Prop Proposal Sig Signature Err Error // as defined above } ``` ### Heartbeat **TODO**: clarify if heartbeat also needs a fixed offset and update the fields accordingly: ```proto message Heartbeat { ValidatorAddress Address ValidatorIndex int Height int64 Round int Sequence int } // amino registered with "tendermint/socketpv/SignHeartbeatRequest" message SignHeartbeatRequest { Hb Heartbeat } // amino registered with "tendermint/socketpv/SignHeartbeatReply" message SignHeartbeatReply { Hb Heartbeat Sig Signature Err 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: ```golang type Signable interface { SignBytes() []byte } ``` And the implementation for vote, heartbeat, proposal will look like: ```golang // type T is one of vote, sign, proposal func (tp *T) SignBytes() []byte { bz, err := cdc.MarshalBinary(tp) if err != nil { panic(err) } return bz } ``` ## Status Partially Accepted ## 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 `Vote` with a signature included -> they will need to - need to modify some interfaces ### Neutral not even the swiss are neutral