You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

6.4 KiB

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: 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.

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 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.

Proposal

// 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:

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:

type Signable interface {
	SignBytes() []byte
}

And the implementation for vote, heartbeat, proposal will look like:

// 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