Revision c5c088b1f87bf7fdb0fcb9d01b30da1e1264d0fa authored by tycho garen on 04 March 2022, 20:41:58 UTC, committed by tycho garen on 04 March 2022, 20:41:58 UTC
1 parent 1c7c3a0
Raw File
adr-024-sign-bytes.md
# 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
back to top