Skip to main content

hotmint_types/
vote.rs

1use serde::{Deserialize, Serialize};
2
3use crate::block::BlockHash;
4use crate::crypto::Signature;
5use crate::epoch::EpochNumber;
6use crate::validator::ValidatorId;
7use crate::view::ViewNumber;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum VoteType {
11    /// First-phase vote (step 3)
12    Vote,
13    /// Second-phase vote (step 5)
14    Vote2,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Vote {
19    pub block_hash: BlockHash,
20    pub view: ViewNumber,
21    pub validator: ValidatorId,
22    pub signature: Signature,
23    pub vote_type: VoteType,
24    /// Optional vote extension data (ABCI++ Vote Extensions).
25    /// Only meaningful for Vote2 (second-phase votes).
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub extension: Option<Vec<u8>>,
28}
29
30impl Vote {
31    /// Canonical bytes for signing: domain_tag || chain_id_hash || epoch || view || block_hash || vote_type
32    ///
33    /// The domain tag, chain_id_hash, and epoch prevent cross-chain, cross-epoch,
34    /// and cross-message-type signature replay attacks.
35    pub fn signing_bytes(
36        chain_id_hash: &[u8; 32],
37        epoch: EpochNumber,
38        view: ViewNumber,
39        block_hash: &BlockHash,
40        vote_type: VoteType,
41    ) -> Vec<u8> {
42        let tag = b"HOTMINT_VOTE_V2\0";
43        let mut buf = Vec::with_capacity(tag.len() + 32 + 8 + 8 + 32 + 1);
44        buf.extend_from_slice(tag);
45        buf.extend_from_slice(chain_id_hash);
46        buf.extend_from_slice(&epoch.as_u64().to_le_bytes());
47        buf.extend_from_slice(&view.as_u64().to_le_bytes());
48        buf.extend_from_slice(&block_hash.0);
49        buf.push(vote_type as u8);
50        buf
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57    use crate::epoch::EpochNumber;
58
59    const TEST_CHAIN: [u8; 32] = [0u8; 32];
60
61    #[test]
62    fn test_signing_bytes_deterministic() {
63        let hash = BlockHash([42u8; 32]);
64        let a = Vote::signing_bytes(
65            &TEST_CHAIN,
66            EpochNumber(0),
67            ViewNumber(5),
68            &hash,
69            VoteType::Vote,
70        );
71        let b = Vote::signing_bytes(
72            &TEST_CHAIN,
73            EpochNumber(0),
74            ViewNumber(5),
75            &hash,
76            VoteType::Vote,
77        );
78        assert_eq!(a, b);
79    }
80
81    #[test]
82    fn test_signing_bytes_differ_by_type() {
83        let hash = BlockHash([1u8; 32]);
84        let a = Vote::signing_bytes(
85            &TEST_CHAIN,
86            EpochNumber(0),
87            ViewNumber(1),
88            &hash,
89            VoteType::Vote,
90        );
91        let b = Vote::signing_bytes(
92            &TEST_CHAIN,
93            EpochNumber(0),
94            ViewNumber(1),
95            &hash,
96            VoteType::Vote2,
97        );
98        assert_ne!(a, b);
99    }
100
101    #[test]
102    fn test_signing_bytes_differ_by_view() {
103        let hash = BlockHash([1u8; 32]);
104        let a = Vote::signing_bytes(
105            &TEST_CHAIN,
106            EpochNumber(0),
107            ViewNumber(1),
108            &hash,
109            VoteType::Vote,
110        );
111        let b = Vote::signing_bytes(
112            &TEST_CHAIN,
113            EpochNumber(0),
114            ViewNumber(2),
115            &hash,
116            VoteType::Vote,
117        );
118        assert_ne!(a, b);
119    }
120
121    #[test]
122    fn test_signing_bytes_differ_by_chain() {
123        let hash = BlockHash([1u8; 32]);
124        let chain_a = [1u8; 32];
125        let chain_b = [2u8; 32];
126        let a = Vote::signing_bytes(
127            &chain_a,
128            EpochNumber(0),
129            ViewNumber(1),
130            &hash,
131            VoteType::Vote,
132        );
133        let b = Vote::signing_bytes(
134            &chain_b,
135            EpochNumber(0),
136            ViewNumber(1),
137            &hash,
138            VoteType::Vote,
139        );
140        assert_ne!(a, b);
141    }
142
143    #[test]
144    fn test_signing_bytes_differ_by_epoch() {
145        let hash = BlockHash([1u8; 32]);
146        let a = Vote::signing_bytes(
147            &TEST_CHAIN,
148            EpochNumber(0),
149            ViewNumber(1),
150            &hash,
151            VoteType::Vote,
152        );
153        let b = Vote::signing_bytes(
154            &TEST_CHAIN,
155            EpochNumber(1),
156            ViewNumber(1),
157            &hash,
158            VoteType::Vote,
159        );
160        assert_ne!(a, b);
161    }
162}