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