celestia_core/vote/
sign_vote.rs

1use bytes::BufMut;
2use celestia_core_proto::Error as ProtobufError;
3
4use crate::{chain, prelude::*, privval::RemoteSignerError, Vote};
5
6/// SignVoteRequest is a request to sign a vote
7#[derive(Clone, PartialEq, Eq, Debug)]
8pub struct SignVoteRequest {
9    /// Vote
10    pub vote: Vote,
11    /// Chain ID
12    pub chain_id: chain::Id,
13}
14
15impl SignVoteRequest {
16    /// Create signable bytes from Vote.
17    pub fn to_signable_bytes<B>(&self, sign_bytes: &mut B) -> Result<bool, ProtobufError>
18    where
19        B: BufMut,
20    {
21        self.vote
22            .to_signable_bytes(self.chain_id.clone(), sign_bytes)
23    }
24
25    /// Create signable vector from Vote.
26    pub fn to_signable_vec(&self) -> Result<Vec<u8>, ProtobufError> {
27        self.vote.to_signable_vec(self.chain_id.clone())
28    }
29}
30
31/// SignedVoteResponse is a response containing a signed vote or an error
32#[derive(Clone, PartialEq, Eq, Debug)]
33pub struct SignedVoteResponse {
34    /// Optional Vote
35    pub vote: Option<Vote>,
36    /// Optional error
37    pub error: Option<RemoteSignerError>,
38}
39
40// =============================================================================
41// Protobuf conversions
42// =============================================================================
43
44tendermint_pb_modules! {
45    use super::{SignVoteRequest, SignedVoteResponse};
46    use crate::{Error, prelude::*};
47    use pb::privval::{
48        SignVoteRequest as RawSignVoteRequest, SignedVoteResponse as RawSignedVoteResponse,
49    };
50
51    impl Protobuf<RawSignVoteRequest> for SignVoteRequest {}
52
53    impl TryFrom<RawSignVoteRequest> for SignVoteRequest {
54        type Error = Error;
55
56        fn try_from(value: RawSignVoteRequest) -> Result<Self, Self::Error> {
57            let vote = value.vote.ok_or_else(Error::no_vote_found)?.try_into()?;
58
59            let chain_id = value.chain_id.try_into()?;
60
61            Ok(SignVoteRequest { vote, chain_id })
62        }
63    }
64
65    impl From<SignVoteRequest> for RawSignVoteRequest {
66        fn from(value: SignVoteRequest) -> Self {
67            RawSignVoteRequest {
68                vote: Some(value.vote.into()),
69                chain_id: value.chain_id.as_str().to_owned(),
70            }
71        }
72    }
73
74    impl Protobuf<RawSignedVoteResponse> for SignedVoteResponse {}
75
76    impl TryFrom<RawSignedVoteResponse> for SignedVoteResponse {
77        type Error = Error;
78
79        fn try_from(value: RawSignedVoteResponse) -> Result<Self, Self::Error> {
80            Ok(SignedVoteResponse {
81                vote: value.vote.map(TryFrom::try_from).transpose()?,
82                error: value.error.map(TryFrom::try_from).transpose()?,
83            })
84        }
85    }
86
87    impl From<SignedVoteResponse> for RawSignedVoteResponse {
88        fn from(value: SignedVoteResponse) -> Self {
89            RawSignedVoteResponse {
90                vote: value.vote.map(Into::into),
91                error: value.error.map(Into::into),
92            }
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use core::{
100        convert::{TryFrom, TryInto},
101        str::FromStr,
102    };
103    use std::println;
104
105    use time::macros::datetime;
106
107    use crate::{
108        account::Id as AccountId,
109        block::{parts::Header, Height, Id as BlockId, Round},
110        chain::Id as ChainId,
111        hash::Algorithm,
112        prelude::*,
113        signature::{Ed25519Signature, Signature},
114        vote::{CanonicalVote, SignVoteRequest, Type, ValidatorIndex},
115        Hash, Vote,
116    };
117
118    #[test]
119    fn test_vote_serialization() {
120        let dt = datetime!(2017-12-25 03:00:01.234 UTC);
121        let vote = Vote {
122            vote_type: Type::Prevote,
123            height: Height::from(12345_u32),
124            round: Round::from(2_u16),
125            timestamp: Some(dt.try_into().unwrap()),
126            block_id: Some(BlockId {
127                hash: Hash::try_from(b"DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA".to_vec()).unwrap(),
128                part_set_header: Header::new(
129                    1_000_000,
130                    Hash::try_from(b"0022446688AACCEE1133557799BBDDFF".to_vec()).unwrap(),
131                )
132                .unwrap(),
133            }),
134            validator_address: AccountId::try_from(vec![
135                0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, 0xf4,
136                0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35,
137            ])
138            .unwrap(),
139            validator_index: ValidatorIndex::try_from(56789).unwrap(),
140            signature: Signature::new(vec![
141                130u8, 246, 183, 50, 153, 248, 28, 57, 51, 142, 55, 217, 194, 24, 134, 212, 233,
142                100, 211, 10, 24, 174, 179, 117, 41, 65, 141, 134, 149, 239, 65, 174, 217, 42, 6,
143                184, 112, 17, 7, 97, 255, 221, 252, 16, 60, 144, 30, 212, 167, 39, 67, 35, 118,
144                192, 133, 130, 193, 115, 32, 206, 152, 91, 173, 10,
145            ])
146            .unwrap(),
147        };
148
149        let mut got = vec![];
150
151        let request = SignVoteRequest {
152            vote,
153            chain_id: ChainId::from_str("test_chain_id").unwrap(),
154        };
155
156        // Option 1 using bytes:
157        let _have = request.to_signable_bytes(&mut got);
158        // Option 2 using Vec<u8>:
159        let got2 = request.to_signable_vec().unwrap();
160
161        // the following vector is generated via:
162        // import (
163        // "fmt"
164        // prototypes "github.com/tendermint/tendermint/proto/tendermint/types"
165        // "github.com/tendermint/tendermint/types"
166        // "strings"
167        // "time"
168        // )
169        // func voteSerialize() {
170        // stamp, _ := time.Parse(time.RFC3339Nano, "2017-12-25T03:00:01.234Z")
171        // vote := &types.Vote{
172        // Type:      prototypes.PrevoteType, // pre-vote
173        // Height:    12345,
174        // Round:     2,
175        // Timestamp: stamp,
176        // BlockID: types.BlockID{
177        // Hash: []byte("DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA"),
178        // PartSetHeader: types.PartSetHeader{
179        // Total: 1000000,
180        // Hash:  []byte("0022446688AACCEE1133557799BBDDFF"),
181        // },
182        // },
183        // ValidatorAddress: []byte{0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21,
184        // 0xf2, 0x48, 0x2a, 0xf4, 0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35}, ValidatorIndex: 56789}
185        // signBytes := types.VoteSignBytes("test_chain_id", vote.ToProto())
186        // fmt.Println(strings.Join(strings.Split(fmt.Sprintf("%v", signBytes), " "), ", "))
187        // }
188
189        let want = vec![
190            124, 8, 1, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 2, 0, 0, 0, 0, 0, 0, 0, 34, 74, 10, 32,
191            68, 69, 65, 68, 66, 69, 69, 70, 68, 69, 65, 68, 66, 69, 69, 70, 66, 65, 70, 66, 65, 70,
192            66, 65, 70, 66, 65, 70, 66, 65, 70, 65, 18, 38, 8, 192, 132, 61, 18, 32, 48, 48, 50,
193            50, 52, 52, 54, 54, 56, 56, 65, 65, 67, 67, 69, 69, 49, 49, 51, 51, 53, 53, 55, 55, 57,
194            57, 66, 66, 68, 68, 70, 70, 42, 11, 8, 177, 211, 129, 210, 5, 16, 128, 157, 202, 111,
195            50, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95, 105, 100,
196        ];
197        assert_eq!(got, want);
198        assert_eq!(got2, want);
199    }
200
201    #[test]
202    // Test vote encoding with a malformed block_id (no hash) which is considered nil in Go.
203    fn test_vote_encoding_with_empty_block_id() {
204        let dt = datetime!(2017-12-25 03:00:01.234 UTC);
205        let vote = Vote {
206            vote_type: Type::Prevote,
207            height: Height::from(12345_u32),
208            round: Round::from(2_u16),
209            timestamp: Some(dt.try_into().unwrap()),
210            block_id: Some(BlockId {
211                hash: Hash::try_from(b"".to_vec()).unwrap(),
212                part_set_header: Header::new(
213                    1_000_000,
214                    Hash::try_from(b"0022446688AACCEE1133557799BBDDFF".to_vec()).unwrap(),
215                )
216                .unwrap(),
217            }),
218            validator_address: AccountId::try_from(vec![
219                0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, 0xf4,
220                0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35,
221            ])
222            .unwrap(),
223            validator_index: ValidatorIndex::try_from(56789).unwrap(),
224            signature: Signature::new(vec![
225                130u8, 246, 183, 50, 153, 248, 28, 57, 51, 142, 55, 217, 194, 24, 134, 212, 233,
226                100, 211, 10, 24, 174, 179, 117, 41, 65, 141, 134, 149, 239, 65, 174, 217, 42, 6,
227                184, 112, 17, 7, 97, 255, 221, 252, 16, 60, 144, 30, 212, 167, 39, 67, 35, 118,
228                192, 133, 130, 193, 115, 32, 206, 152, 91, 173, 10,
229            ])
230            .unwrap(),
231        };
232
233        let request = SignVoteRequest {
234            vote,
235            chain_id: ChainId::from_str("test_chain_id").unwrap(),
236        };
237
238        let got = request.to_signable_vec().unwrap();
239
240        // the following vector is generated via:
241        // import (
242        // "fmt"
243        // prototypes "github.com/tendermint/tendermint/proto/tendermint/types"
244        // "github.com/tendermint/tendermint/types"
245        // "strings"
246        // "time"
247        // )
248        // func voteSerialize() {
249        // stamp, _ := time.Parse(time.RFC3339Nano, "2017-12-25T03:00:01.234Z")
250        // vote := &types.Vote{
251        // Type:      prototypes.PrevoteType, // pre-vote
252        // Height:    12345,
253        // Round:     2,
254        // Timestamp: stamp,
255        // BlockID: types.BlockID{
256        // Hash: []byte(""),
257        // PartSetHeader: types.PartSetHeader{
258        // Total: 1000000,
259        // Hash:  []byte("0022446688AACCEE1133557799BBDDFF"),
260        // },
261        // },
262        // ValidatorAddress: []byte{0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21,
263        // 0xf2, 0x48, 0x2a, 0xf4, 0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35}, ValidatorIndex: 56789}
264        // signBytes := types.VoteSignBytes("test_chain_id", vote.ToProto())
265        // fmt.Println(strings.Join(strings.Split(fmt.Sprintf("%v", signBytes), " "), ", "))
266        // }
267
268        let want = vec![
269            90, 8, 1, 17, 57, 48, 0, 0, 0, 0, 0, 0, 25, 2, 0, 0, 0, 0, 0, 0, 0, 34, 40, 18, 38, 8,
270            192, 132, 61, 18, 32, 48, 48, 50, 50, 52, 52, 54, 54, 56, 56, 65, 65, 67, 67, 69, 69,
271            49, 49, 51, 51, 53, 53, 55, 55, 57, 57, 66, 66, 68, 68, 70, 70, 42, 11, 8, 177, 211,
272            129, 210, 5, 16, 128, 157, 202, 111, 50, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105,
273            110, 95, 105, 100,
274        ];
275        assert_eq!(got, want);
276    }
277
278    tendermint_pb_modules! {
279        use super::*;
280        use pb::types::CanonicalVote as RawCanonicalVote;
281
282        #[test]
283        fn test_sign_bytes_compatibility() {
284            let cv = CanonicalVote::new(Vote::default(), ChainId::try_from("A").unwrap());
285            let mut got = vec![];
286            // SignBytes are encoded using MarshalBinary and not MarshalBinaryBare
287            Protobuf::<RawCanonicalVote>::encode_length_delimited(&cv, &mut got).unwrap();
288            let want = vec![
289                0x10, 0x8, 0x1, 0x11, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x32, 0x1,
290                0x41,
291            ]; // Todo: Get these bytes from Go. During protobuf upgrade we didn't get to generate them.
292            assert_eq!(got, want);
293
294            // with proper (fixed size) height and round (Precommit):
295            {
296                let vt_precommit = Vote {
297                    height: Height::from(1_u32),
298                    round: Round::from(1_u16),
299                    vote_type: Type::Precommit,
300                    ..Default::default()
301                };
302                println!("{vt_precommit:?}");
303                let cv_precommit = CanonicalVote::new(vt_precommit, ChainId::try_from("A").unwrap());
304                let got = Protobuf::<RawCanonicalVote>::encode_vec(&cv_precommit).unwrap();
305                let want = vec![
306                    0x8,  // (field_number << 3) | wire_type
307                    0x2,  // PrecommitType
308                    0x11, // (field_number << 3) | wire_type
309                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // height
310                    0x19, // (field_number << 3) | wire_type
311                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // round
312                    0x2a, // (field_number << 3) | wire_type
313                    0x0,  // timestamp
314                    0x32, // (field_number << 3) | wire_type
315                    // remaining fields (chain ID):
316                    0x1, 0x41,
317                ];
318                assert_eq!(got, want);
319            }
320            // with proper (fixed size) height and round (Prevote):
321            {
322                let vt_prevote = Vote {
323                    height: Height::from(1_u32),
324                    round: Round::from(1_u16),
325                    vote_type: Type::Prevote,
326                    ..Default::default()
327                };
328
329                let cv_prevote = CanonicalVote::new(vt_prevote, ChainId::try_from("A").unwrap());
330
331                let got = Protobuf::<RawCanonicalVote>::encode_vec(&cv_prevote).unwrap();
332
333                let want = vec![
334                    0x8,  // (field_number << 3) | wire_type
335                    0x1,  // PrevoteType
336                    0x11, // (field_number << 3) | wire_type
337                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // height
338                    0x19, // (field_number << 3) | wire_type
339                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // round
340                    0x2a, // (field_number << 3) | wire_type
341                    0x0,  // timestamp
342                    0x32, // (field_number << 3) | wire_type
343                    // remaining fields (chain ID):
344                    0x1, 0x41,
345                ];
346                assert_eq!(got, want);
347            }
348        }
349
350        #[test]
351        fn test_deserialization() {
352            let encoded = vec![
353                10, 188, 1, 8, 1, 16, 185, 96, 24, 2, 34, 74, 10, 32, 222, 173, 190, 239, 222, 173,
354                190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
355                0, 0, 0, 0, 18, 38, 8, 192, 132, 61, 18, 32, 0, 34, 68, 102, 136, 170, 204, 238, 17,
356                51, 85, 119, 153, 187, 221, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42,
357                11, 8, 177, 211, 129, 210, 5, 16, 128, 157, 202, 111, 50, 20, 163, 178, 204, 221, 113,
358                134, 241, 104, 95, 33, 242, 72, 42, 244, 251, 52, 70, 168, 75, 53, 56, 213, 187, 3, 66,
359                64, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
360                1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
361                1, 1, 1, 1, 1, 1, 1, 18, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95, 105,
362                100,
363            ]; // Todo: Double-check the Go implementation, this was self-generated.
364            let dt = datetime!(2017-12-25 03:00:01.234 UTC);
365            let vote = Vote {
366                validator_address: AccountId::try_from(vec![
367                    0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, 0xf4,
368                    0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35,
369                ])
370                .unwrap(),
371                validator_index: ValidatorIndex::try_from(56789).unwrap(),
372                height: Height::from(12345_u32),
373                round: Round::from(2_u16),
374                timestamp: Some(dt.try_into().unwrap()),
375                vote_type: Type::Prevote,
376                block_id: Some(BlockId {
377                    hash: Hash::from_hex_upper(Algorithm::Sha256, "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA")
378                        .unwrap(),
379                    part_set_header: Header::new(
380                        1_000_000,
381                        Hash::from_hex_upper(Algorithm::Sha256, "0022446688AACCEE1133557799BBDDFF")
382                            .unwrap(),
383                    )
384                    .unwrap(),
385                }),
386                signature: Signature::new(vec![1; Ed25519Signature::BYTE_SIZE]).unwrap(),
387            };
388            let want = SignVoteRequest {
389                vote,
390                chain_id: ChainId::from_str("test_chain_id").unwrap(),
391            };
392            let got = <SignVoteRequest as Protobuf<pb::privval::SignVoteRequest>>::decode_vec(
393                &encoded
394            ).unwrap();
395            assert_eq!(got, want);
396        }
397
398        #[test]
399        fn test_vote_rountrip_with_sig() {
400            let dt = datetime!(2017-12-25 03:00:01.234 UTC);
401            let vote = Vote {
402                validator_address: AccountId::try_from(vec![
403                    0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, 0xf4,
404                    0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35,
405                ])
406                .unwrap(),
407                validator_index: ValidatorIndex::try_from(56789).unwrap(),
408                height: Height::from(12345_u32),
409                round: Round::from(2_u16),
410                timestamp: Some(dt.try_into().unwrap()),
411                vote_type: Type::Prevote,
412                block_id: Some(BlockId {
413                    hash: Hash::from_hex_upper(Algorithm::Sha256, "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA")
414                        .unwrap(), // Hash::new(Algorithm::Sha256,
415                    // b"hash".to_vec().as_slice()).unwrap(),
416                    part_set_header: Header::new(
417                        1_000_000,
418                        Hash::from_hex_upper(Algorithm::Sha256, "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA")
419                            .unwrap(),
420                    )
421                    .unwrap(),
422                }),
423                // signature: None,
424                signature: Signature::new(vec![
425                    130u8, 246, 183, 50, 153, 248, 28, 57, 51, 142, 55, 217, 194, 24, 134, 212, 233,
426                    100, 211, 10, 24, 174, 179, 117, 41, 65, 141, 134, 149, 239, 65, 174, 217, 42, 6,
427                    184, 112, 17, 7, 97, 255, 221, 252, 16, 60, 144, 30, 212, 167, 39, 67, 35, 118,
428                    192, 133, 130, 193, 115, 32, 206, 152, 91, 173, 10,
429                ])
430                .unwrap(),
431            };
432            let got = Protobuf::<pb::types::Vote>::encode_vec(&vote).unwrap();
433            let v = <Vote as Protobuf::<pb::types::Vote>>::decode_vec(&got).unwrap();
434
435            assert_eq!(v, vote);
436            // SignVoteRequest
437            {
438                let svr = SignVoteRequest {
439                    vote,
440                    chain_id: ChainId::from_str("test_chain_id").unwrap(),
441                };
442                let mut got = vec![];
443                let _have = Protobuf::<pb::privval::SignVoteRequest>::encode(&svr, &mut got);
444
445                let svr2 = <SignVoteRequest as Protobuf<pb::privval::SignVoteRequest>>::decode(
446                    got.as_ref()
447                ).unwrap();
448                assert_eq!(svr, svr2);
449            }
450        }
451    }
452}