tendermint/vote/
sign_vote.rs

1use bytes::BufMut;
2use tendermint_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 into_signable_vec(self) -> Vec<u8> {
27        self.vote.into_signable_vec(self.chain_id)
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::str::FromStr;
100    use std::println;
101
102    use time::macros::datetime;
103
104    use crate::{
105        account::Id as AccountId,
106        block::{parts::Header, Height, Id as BlockId, Round},
107        chain::Id as ChainId,
108        hash::Algorithm,
109        prelude::*,
110        signature::Signature,
111        vote::{CanonicalVote, SignVoteRequest, Type, ValidatorIndex},
112        Hash, Vote,
113    };
114
115    #[test]
116    fn test_vote_serialization() {
117        let dt = datetime!(2017-12-25 03:00:01.234 UTC);
118        let vote = Vote {
119            vote_type: Type::Prevote,
120            height: Height::from(12345_u32),
121            round: Round::from(2_u16),
122            timestamp: Some(dt.try_into().unwrap()),
123            block_id: Some(BlockId {
124                hash: Hash::try_from(b"DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA".to_vec()).unwrap(),
125                part_set_header: Header::new(
126                    1_000_000,
127                    Hash::try_from(b"0022446688AACCEE1133557799BBDDFF".to_vec()).unwrap(),
128                )
129                .unwrap(),
130            }),
131            validator_address: AccountId::try_from(vec![
132                0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, 0xf4,
133                0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35,
134            ])
135            .unwrap(),
136            validator_index: ValidatorIndex::try_from(56789).unwrap(),
137            signature: Signature::new(vec![
138                130u8, 246, 183, 50, 153, 248, 28, 57, 51, 142, 55, 217, 194, 24, 134, 212, 233,
139                100, 211, 10, 24, 174, 179, 117, 41, 65, 141, 134, 149, 239, 65, 174, 217, 42, 6,
140                184, 112, 17, 7, 97, 255, 221, 252, 16, 60, 144, 30, 212, 167, 39, 67, 35, 118,
141                192, 133, 130, 193, 115, 32, 206, 152, 91, 173, 10,
142            ])
143            .unwrap(),
144            // TODO: test serialization of extensions
145            extension: vec![],
146            extension_signature: None,
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.into_signable_vec();
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            // TODO: test serialization of extensions
232            extension: vec![],
233            extension_signature: None,
234        };
235
236        let request = SignVoteRequest {
237            vote,
238            chain_id: ChainId::from_str("test_chain_id").unwrap(),
239        };
240
241        let got = request.into_signable_vec();
242
243        // the following vector is generated via:
244        // import (
245        // "fmt"
246        // prototypes "github.com/tendermint/tendermint/proto/tendermint/types"
247        // "github.com/tendermint/tendermint/types"
248        // "strings"
249        // "time"
250        // )
251        // func voteSerialize() {
252        // stamp, _ := time.Parse(time.RFC3339Nano, "2017-12-25T03:00:01.234Z")
253        // vote := &types.Vote{
254        // Type:      prototypes.PrevoteType, // pre-vote
255        // Height:    12345,
256        // Round:     2,
257        // Timestamp: stamp,
258        // BlockID: types.BlockID{
259        // Hash: []byte(""),
260        // PartSetHeader: types.PartSetHeader{
261        // Total: 1000000,
262        // Hash:  []byte("0022446688AACCEE1133557799BBDDFF"),
263        // },
264        // },
265        // ValidatorAddress: []byte{0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21,
266        // 0xf2, 0x48, 0x2a, 0xf4, 0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35}, ValidatorIndex: 56789}
267        // signBytes := types.VoteSignBytes("test_chain_id", vote.ToProto())
268        // fmt.Println(strings.Join(strings.Split(fmt.Sprintf("%v", signBytes), " "), ", "))
269        // }
270
271        let want = vec![
272            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,
273            192, 132, 61, 18, 32, 48, 48, 50, 50, 52, 52, 54, 54, 56, 56, 65, 65, 67, 67, 69, 69,
274            49, 49, 51, 51, 53, 53, 55, 55, 57, 57, 66, 66, 68, 68, 70, 70, 42, 11, 8, 177, 211,
275            129, 210, 5, 16, 128, 157, 202, 111, 50, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105,
276            110, 95, 105, 100,
277        ];
278        assert_eq!(got, want);
279    }
280
281    tendermint_pb_modules! {
282        use super::*;
283        use pb::types::CanonicalVote as RawCanonicalVote;
284        use crate::{Time, account, signature::Ed25519Signature};
285
286        /// Returns a dummy value to be used in tests.
287        pub fn dummy_vote() -> Vote {
288            Vote {
289                vote_type: Type::Prevote,
290                height: Default::default(),
291                round: Default::default(),
292                block_id: None,
293                timestamp: Some(Time::unix_epoch()),
294                validator_address: account::Id::new([0; account::LENGTH]),
295                validator_index: ValidatorIndex::try_from(0_i32).unwrap(),
296                // Could have reused crate::test::dummy_signature, except that
297                // this Default impl is defined outside of #[cfg(test)].
298                signature: Some(Signature::from(Ed25519Signature::from_bytes(
299                    &[0; Ed25519Signature::BYTE_SIZE],
300                ))),
301                extension: Default::default(),
302                extension_signature: None,
303            }
304        }
305
306        #[test]
307        fn test_sign_bytes_compatibility() {
308            let cv = CanonicalVote::new(dummy_vote(), ChainId::try_from("A").unwrap());
309            let mut got = vec![];
310            // SignBytes are encoded using MarshalBinary and not MarshalBinaryBare
311            Protobuf::<RawCanonicalVote>::encode_length_delimited(cv, &mut got).unwrap();
312            let want = vec![
313                0x10, 0x8, 0x1, 0x11, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2a, 0x0, 0x32, 0x1,
314                0x41,
315            ]; // Todo: Get these bytes from Go. During protobuf upgrade we didn't get to generate them.
316            assert_eq!(got, want);
317
318            // with proper (fixed size) height and round (Precommit):
319            {
320                let vt_precommit = Vote {
321                    height: Height::from(1_u32),
322                    round: Round::from(1_u16),
323                    vote_type: Type::Precommit,
324                    ..dummy_vote()
325                };
326                println!("{vt_precommit:?}");
327                let cv_precommit = CanonicalVote::new(vt_precommit, ChainId::try_from("A").unwrap());
328                let got = Protobuf::<RawCanonicalVote>::encode_vec(cv_precommit);
329                let want = vec![
330                    0x8,  // (field_number << 3) | wire_type
331                    0x2,  // PrecommitType
332                    0x11, // (field_number << 3) | wire_type
333                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // height
334                    0x19, // (field_number << 3) | wire_type
335                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // round
336                    0x2a, // (field_number << 3) | wire_type
337                    0x0,  // timestamp
338                    0x32, // (field_number << 3) | wire_type
339                    // remaining fields (chain ID):
340                    0x1, 0x41,
341                ];
342                assert_eq!(got, want);
343            }
344            // with proper (fixed size) height and round (Prevote):
345            {
346                let vt_prevote = Vote {
347                    height: Height::from(1_u32),
348                    round: Round::from(1_u16),
349                    vote_type: Type::Prevote,
350                    ..dummy_vote()
351                };
352
353                let cv_prevote = CanonicalVote::new(vt_prevote, ChainId::try_from("A").unwrap());
354
355                let got = Protobuf::<RawCanonicalVote>::encode_vec(cv_prevote);
356
357                let want = vec![
358                    0x8,  // (field_number << 3) | wire_type
359                    0x1,  // PrevoteType
360                    0x11, // (field_number << 3) | wire_type
361                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // height
362                    0x19, // (field_number << 3) | wire_type
363                    0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,  // round
364                    0x2a, // (field_number << 3) | wire_type
365                    0x0,  // timestamp
366                    0x32, // (field_number << 3) | wire_type
367                    // remaining fields (chain ID):
368                    0x1, 0x41,
369                ];
370                assert_eq!(got, want);
371            }
372        }
373
374        #[test]
375        fn test_deserialization() {
376            let encoded = vec![
377                10, 188, 1, 8, 1, 16, 185, 96, 24, 2, 34, 74, 10, 32, 222, 173, 190, 239, 222, 173,
378                190, 239, 186, 251, 175, 186, 251, 175, 186, 250, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
379                0, 0, 0, 0, 18, 38, 8, 192, 132, 61, 18, 32, 0, 34, 68, 102, 136, 170, 204, 238, 17,
380                51, 85, 119, 153, 187, 221, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 42,
381                11, 8, 177, 211, 129, 210, 5, 16, 128, 157, 202, 111, 50, 20, 163, 178, 204, 221, 113,
382                134, 241, 104, 95, 33, 242, 72, 42, 244, 251, 52, 70, 168, 75, 53, 56, 213, 187, 3, 66,
383                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,
384                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,
385                1, 1, 1, 1, 1, 1, 1, 18, 13, 116, 101, 115, 116, 95, 99, 104, 97, 105, 110, 95, 105,
386                100,
387            ]; // Todo: Double-check the Go implementation, this was self-generated.
388            let dt = datetime!(2017-12-25 03:00:01.234 UTC);
389            let vote = Vote {
390                validator_address: AccountId::try_from(vec![
391                    0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, 0xf4,
392                    0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35,
393                ])
394                .unwrap(),
395                validator_index: ValidatorIndex::try_from(56789).unwrap(),
396                height: Height::from(12345_u32),
397                round: Round::from(2_u16),
398                timestamp: Some(dt.try_into().unwrap()),
399                vote_type: Type::Prevote,
400                block_id: Some(BlockId {
401                    hash: Hash::from_hex_upper(Algorithm::Sha256, "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA")
402                        .unwrap(),
403                    part_set_header: Header::new(
404                        1_000_000,
405                        Hash::from_hex_upper(Algorithm::Sha256, "0022446688AACCEE1133557799BBDDFF")
406                            .unwrap(),
407                    )
408                    .unwrap(),
409                }),
410                signature: Signature::new(vec![1; Ed25519Signature::BYTE_SIZE]).unwrap(),
411                // TODO: test deserialization of extensions in 0.38
412                extension: vec![],
413                extension_signature: None,
414            };
415            let want = SignVoteRequest {
416                vote,
417                chain_id: ChainId::from_str("test_chain_id").unwrap(),
418            };
419            let got = <SignVoteRequest as Protobuf<pb::privval::SignVoteRequest>>::decode_vec(
420                &encoded
421            ).unwrap();
422            assert_eq!(got, want);
423        }
424
425        #[test]
426        fn test_vote_rountrip_with_sig() {
427            let dt = datetime!(2017-12-25 03:00:01.234 UTC);
428            let vote = Vote {
429                validator_address: AccountId::try_from(vec![
430                    0xa3, 0xb2, 0xcc, 0xdd, 0x71, 0x86, 0xf1, 0x68, 0x5f, 0x21, 0xf2, 0x48, 0x2a, 0xf4,
431                    0xfb, 0x34, 0x46, 0xa8, 0x4b, 0x35,
432                ])
433                .unwrap(),
434                validator_index: ValidatorIndex::try_from(56789).unwrap(),
435                height: Height::from(12345_u32),
436                round: Round::from(2_u16),
437                timestamp: Some(dt.try_into().unwrap()),
438                vote_type: Type::Prevote,
439                block_id: Some(BlockId {
440                    hash: Hash::from_hex_upper(Algorithm::Sha256, "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA")
441                        .unwrap(), // Hash::new(Algorithm::Sha256,
442                    // b"hash".to_vec().as_slice()).unwrap(),
443                    part_set_header: Header::new(
444                        1_000_000,
445                        Hash::from_hex_upper(Algorithm::Sha256, "DEADBEEFDEADBEEFBAFBAFBAFBAFBAFA")
446                            .unwrap(),
447                    )
448                    .unwrap(),
449                }),
450                // signature: None,
451                signature: Signature::new(vec![
452                    130u8, 246, 183, 50, 153, 248, 28, 57, 51, 142, 55, 217, 194, 24, 134, 212, 233,
453                    100, 211, 10, 24, 174, 179, 117, 41, 65, 141, 134, 149, 239, 65, 174, 217, 42, 6,
454                    184, 112, 17, 7, 97, 255, 221, 252, 16, 60, 144, 30, 212, 167, 39, 67, 35, 118,
455                    192, 133, 130, 193, 115, 32, 206, 152, 91, 173, 10,
456                ])
457                .unwrap(),
458                // TODO: test deserialization of extensions in 0.38
459                extension: vec![],
460                extension_signature: None,
461            };
462            let got = Protobuf::<pb::types::Vote>::encode_vec(vote.clone());
463            let v = <Vote as Protobuf::<pb::types::Vote>>::decode_vec(&got).unwrap();
464
465            assert_eq!(v, vote);
466            // SignVoteRequest
467            {
468                let svr = SignVoteRequest {
469                    vote,
470                    chain_id: ChainId::from_str("test_chain_id").unwrap(),
471                };
472                let mut got = vec![];
473                let _have = Protobuf::<pb::privval::SignVoteRequest>::encode(svr.clone(), &mut got);
474
475                let svr2 = <SignVoteRequest as Protobuf<pb::privval::SignVoteRequest>>::decode(
476                    got.as_ref()
477                ).unwrap();
478                assert_eq!(svr, svr2);
479            }
480        }
481    }
482}