mirror of
https://github.com/fluencelabs/tendermint
synced 2025-04-25 14:52:17 +00:00
235 lines
6.4 KiB
Markdown
235 lines
6.4 KiB
Markdown
|
# 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
|
||
|
|
||
|
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 `Vote` with a signature included -> they will need to
|
||
|
- need to modify some interfaces
|
||
|
|
||
|
### Neutral
|
||
|
|
||
|
not even the swiss are neutral
|