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.

234 lines
6.4 KiB

  1. # ADR 024: SignBytes and validator types in privval
  2. ## Context
  3. Currently, the messages exchanged between tendermint and a (potentially remote) signer/validator,
  4. namely votes, proposals, and heartbeats, are encoded as a JSON string
  5. (e.g., via `Vote.SignBytes(...)`) and then
  6. signed . JSON encoding is sub-optimal for both, hardware wallets
  7. and for usage in ethereum smart contracts. Both is laid down in detail in [issue#1622].
  8. Also, there are currently no differences between sign-request and -replies. Also, there is no possibility
  9. for a remote signer to include an error code or message in case something went wrong.
  10. The messages exchanged between tendermint and a remote signer currently live in
  11. [privval/socket.go] and encapsulate the corresponding types in [types].
  12. [privval/socket.go]: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/privval/socket.go#L496-L502
  13. [issue#1622]: https://github.com/tendermint/tendermint/issues/1622
  14. [types]: https://github.com/tendermint/tendermint/tree/master/types
  15. ## Decision
  16. - restructure vote, proposal, and heartbeat such that their encoding is easily parseable by
  17. hardware devices and smart contracts using a binary encoding format ([amino] in this case)
  18. - split up the messages exchanged between tendermint and remote signers into requests and
  19. responses (see details below)
  20. - include an error type in responses
  21. ### Overview
  22. ```
  23. +--------------+ +----------------+
  24. | | SignXRequest | |
  25. |Remote signer |<---------------------+ tendermint |
  26. | (e.g. KMS) | | |
  27. | +--------------------->| |
  28. +--------------+ SignedXReply +----------------+
  29. SignXRequest {
  30. x: X
  31. }
  32. SignedXReply {
  33. x: X
  34. sig: Signature // []byte
  35. err: Error{
  36. code: int
  37. desc: string
  38. }
  39. }
  40. ```
  41. TODO: Alternatively, the type `X` might directly include the signature. A lot of places expect a vote with a
  42. signature and do not necessarily deal with "Replies".
  43. Still exploring what would work best here.
  44. This would look like (exemplified using X = Vote):
  45. ```
  46. Vote {
  47. // all fields besides signature
  48. }
  49. SignedVote {
  50. Vote Vote
  51. Signature []byte
  52. }
  53. SignVoteRequest {
  54. Vote Vote
  55. }
  56. SignedVoteReply {
  57. Vote SignedVote
  58. Err Error
  59. }
  60. ```
  61. **Note:** There was a related discussion around including a fingerprint of, or, the whole public-key
  62. into each sign-request to tell the signer which corresponding private-key to
  63. use to sign the message. This is particularly relevant in the context of the KMS
  64. but is currently not considered in this ADR.
  65. [amino]: https://github.com/tendermint/go-amino/
  66. ### Vote
  67. As explained in [issue#1622] `Vote` will be changed to contain the following fields
  68. (notation in protobuf-like syntax for easy readability):
  69. ```proto
  70. // vanilla protobuf / amino encoded
  71. message Vote {
  72. Version fixed32
  73. Height sfixed64
  74. Round sfixed32
  75. VoteType fixed32
  76. Timestamp Timestamp // << using protobuf definition
  77. BlockID BlockID // << as already defined
  78. ChainID string // at the end because length could vary a lot
  79. }
  80. // this is an amino registered type; like currently privval.SignVoteMsg:
  81. // registered with "tendermint/socketpv/SignVoteRequest"
  82. message SignVoteRequest {
  83. Vote vote
  84. }
  85. // amino registered type
  86. // registered with "tendermint/socketpv/SignedVoteReply"
  87. message SignedVoteReply {
  88. Vote Vote
  89. Signature Signature
  90. Err Error
  91. }
  92. // we will use this type everywhere below
  93. message Error {
  94. Type uint // error code
  95. Description string // optional description
  96. }
  97. ```
  98. The `ChainID` gets moved into the vote message directly. Previously, it was injected
  99. using the [Signable] interface method `SignBytes(chainID string) []byte`. Also, the
  100. signature won't be included directly, only in the corresponding `SignedVoteReply` message.
  101. [Signable]: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/types/signable.go#L9-L11
  102. ### Proposal
  103. ```proto
  104. // vanilla protobuf / amino encoded
  105. message Proposal {
  106. Height sfixed64
  107. Round sfixed32
  108. Timestamp Timestamp // << using protobuf definition
  109. BlockPartsHeader PartSetHeader // as already defined
  110. POLRound sfixed32
  111. POLBlockID BlockID // << as already defined
  112. }
  113. // amino registered with "tendermint/socketpv/SignProposalRequest"
  114. message SignProposalRequest {
  115. Proposal proposal
  116. }
  117. // amino registered with "tendermint/socketpv/SignProposalReply"
  118. message SignProposalReply {
  119. Prop Proposal
  120. Sig Signature
  121. Err Error // as defined above
  122. }
  123. ```
  124. ### Heartbeat
  125. **TODO**: clarify if heartbeat also needs a fixed offset and update the fields accordingly:
  126. ```proto
  127. message Heartbeat {
  128. ValidatorAddress Address
  129. ValidatorIndex int
  130. Height int64
  131. Round int
  132. Sequence int
  133. }
  134. // amino registered with "tendermint/socketpv/SignHeartbeatRequest"
  135. message SignHeartbeatRequest {
  136. Hb Heartbeat
  137. }
  138. // amino registered with "tendermint/socketpv/SignHeartbeatReply"
  139. message SignHeartbeatReply {
  140. Hb Heartbeat
  141. Sig Signature
  142. Err Error // as defined above
  143. }
  144. ```
  145. ## PubKey
  146. TBA - this needs further thoughts: e.g. what todo like in the case of the KMS which holds
  147. several keys? How does it know with which key to reply?
  148. ## SignBytes
  149. `SignBytes` will not require a `ChainID` parameter:
  150. ```golang
  151. type Signable interface {
  152. SignBytes() []byte
  153. }
  154. ```
  155. And the implementation for vote, heartbeat, proposal will look like:
  156. ```golang
  157. // type T is one of vote, sign, proposal
  158. func (tp *T) SignBytes() []byte {
  159. bz, err := cdc.MarshalBinary(tp)
  160. if err != nil {
  161. panic(err)
  162. }
  163. return bz
  164. }
  165. ```
  166. ## Status
  167. Partially Accepted
  168. ## Consequences
  169. ### Positive
  170. The most relevant positive effect is that the signing bytes can easily be parsed by a
  171. hardware module and a smart contract. Besides that:
  172. - clearer separation between requests and responses
  173. - added error messages enable better error handling
  174. ### Negative
  175. - relatively huge change / refactoring touching quite some code
  176. - lot's of places assume a `Vote` with a signature included -> they will need to
  177. - need to modify some interfaces
  178. ### Neutral
  179. not even the swiss are neutral