Skip to main content

hotmint_types/
vote.rs

1use serde::{Deserialize, Serialize};
2
3use crate::block::BlockHash;
4use crate::crypto::Signature;
5use crate::validator::ValidatorId;
6use crate::view::ViewNumber;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub enum VoteType {
10    /// First-phase vote (step 3)
11    Vote,
12    /// Second-phase vote (step 5)
13    Vote2,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Vote {
18    pub block_hash: BlockHash,
19    pub view: ViewNumber,
20    pub validator: ValidatorId,
21    pub signature: Signature,
22    pub vote_type: VoteType,
23}
24
25impl Vote {
26    /// Canonical bytes for signing: domain_tag || chain_id_hash || view || block_hash || vote_type
27    ///
28    /// The domain tag and chain_id_hash prevent cross-chain and cross-message-type
29    /// signature replay attacks.
30    pub fn signing_bytes(
31        chain_id_hash: &[u8; 32],
32        view: ViewNumber,
33        block_hash: &BlockHash,
34        vote_type: VoteType,
35    ) -> Vec<u8> {
36        let tag = b"HOTMINT_VOTE_V1\0";
37        let mut buf = Vec::with_capacity(tag.len() + 32 + 8 + 32 + 1);
38        buf.extend_from_slice(tag);
39        buf.extend_from_slice(chain_id_hash);
40        buf.extend_from_slice(&view.as_u64().to_le_bytes());
41        buf.extend_from_slice(&block_hash.0);
42        buf.push(vote_type as u8);
43        buf
44    }
45}
46
47#[cfg(test)]
48mod tests {
49    use super::*;
50
51    const TEST_CHAIN: [u8; 32] = [0u8; 32];
52
53    #[test]
54    fn test_signing_bytes_deterministic() {
55        let hash = BlockHash([42u8; 32]);
56        let a = Vote::signing_bytes(&TEST_CHAIN, ViewNumber(5), &hash, VoteType::Vote);
57        let b = Vote::signing_bytes(&TEST_CHAIN, ViewNumber(5), &hash, VoteType::Vote);
58        assert_eq!(a, b);
59    }
60
61    #[test]
62    fn test_signing_bytes_differ_by_type() {
63        let hash = BlockHash([1u8; 32]);
64        let a = Vote::signing_bytes(&TEST_CHAIN, ViewNumber(1), &hash, VoteType::Vote);
65        let b = Vote::signing_bytes(&TEST_CHAIN, ViewNumber(1), &hash, VoteType::Vote2);
66        assert_ne!(a, b);
67    }
68
69    #[test]
70    fn test_signing_bytes_differ_by_view() {
71        let hash = BlockHash([1u8; 32]);
72        let a = Vote::signing_bytes(&TEST_CHAIN, ViewNumber(1), &hash, VoteType::Vote);
73        let b = Vote::signing_bytes(&TEST_CHAIN, ViewNumber(2), &hash, VoteType::Vote);
74        assert_ne!(a, b);
75    }
76
77    #[test]
78    fn test_signing_bytes_differ_by_chain() {
79        let hash = BlockHash([1u8; 32]);
80        let chain_a = [1u8; 32];
81        let chain_b = [2u8; 32];
82        let a = Vote::signing_bytes(&chain_a, ViewNumber(1), &hash, VoteType::Vote);
83        let b = Vote::signing_bytes(&chain_b, ViewNumber(1), &hash, VoteType::Vote);
84        assert_ne!(a, b);
85    }
86}