Skip to main content

hotmint_crypto/
aggregate.rs

1use ruc::*;
2
3use hotmint_types::crypto::AggregateSignature;
4use hotmint_types::validator::ValidatorSet;
5use hotmint_types::vote::Vote;
6
7/// Collect individual signatures into an AggregateSignature
8pub fn aggregate_votes(vs: &ValidatorSet, votes: &[Vote]) -> Result<AggregateSignature> {
9    let mut agg = AggregateSignature::new(vs.validator_count());
10    for vote in votes {
11        let idx = vs
12            .index_of(vote.validator)
13            .c(d!("unknown validator in vote"))?;
14        agg.add(idx, vote.signature.clone()).c(d!())?;
15    }
16    Ok(agg)
17}
18
19/// Check if an aggregate has reached quorum
20pub fn has_quorum(vs: &ValidatorSet, agg: &AggregateSignature) -> bool {
21    let mut power = 0u64;
22    let validators = vs.validators();
23    for (i, signed) in agg.signers.iter().enumerate() {
24        if *signed && let Some(vi) = validators.get(i) {
25            power += vi.power;
26        }
27    }
28    power >= vs.quorum_threshold()
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34    use crate::Ed25519Signer;
35    use hotmint_types::epoch::EpochNumber;
36    use hotmint_types::validator::{ValidatorId, ValidatorInfo};
37    use hotmint_types::view::ViewNumber;
38    use hotmint_types::vote::VoteType;
39    use hotmint_types::{BlockHash, Signer};
40
41    const TEST_CHAIN: [u8; 32] = [0u8; 32];
42
43    fn make_env() -> (ValidatorSet, Vec<Ed25519Signer>) {
44        let signers: Vec<Ed25519Signer> = (0..4)
45            .map(|i| Ed25519Signer::generate(ValidatorId(i)))
46            .collect();
47        let infos: Vec<ValidatorInfo> = signers
48            .iter()
49            .map(|s| ValidatorInfo {
50                id: s.validator_id(),
51                public_key: s.public_key(),
52                power: 1,
53            })
54            .collect();
55        (ValidatorSet::new(infos), signers)
56    }
57
58    #[test]
59    fn test_aggregate_votes_basic() {
60        let (vs, signers) = make_env();
61        let hash = BlockHash([1u8; 32]);
62        let view = ViewNumber(1);
63        let votes: Vec<Vote> = signers
64            .iter()
65            .take(3)
66            .map(|s| {
67                let bytes =
68                    Vote::signing_bytes(&TEST_CHAIN, EpochNumber(0), view, &hash, VoteType::Vote);
69                Vote {
70                    block_hash: hash,
71                    view,
72                    validator: s.validator_id(),
73                    signature: s.sign(&bytes),
74                    vote_type: VoteType::Vote,
75                    extension: None,
76                }
77            })
78            .collect();
79
80        let agg = aggregate_votes(&vs, &votes).unwrap();
81        assert_eq!(agg.count(), 3);
82        assert!(has_quorum(&vs, &agg));
83    }
84
85    #[test]
86    fn test_no_quorum_with_too_few_votes() {
87        let (vs, signers) = make_env();
88        let hash = BlockHash([2u8; 32]);
89        let view = ViewNumber(1);
90        let votes: Vec<Vote> = signers
91            .iter()
92            .take(2)
93            .map(|s| {
94                let bytes =
95                    Vote::signing_bytes(&TEST_CHAIN, EpochNumber(0), view, &hash, VoteType::Vote);
96                Vote {
97                    block_hash: hash,
98                    view,
99                    validator: s.validator_id(),
100                    signature: s.sign(&bytes),
101                    vote_type: VoteType::Vote,
102                    extension: None,
103                }
104            })
105            .collect();
106
107        let agg = aggregate_votes(&vs, &votes).unwrap();
108        assert_eq!(agg.count(), 2);
109        assert!(!has_quorum(&vs, &agg));
110    }
111
112    #[test]
113    fn test_aggregate_unknown_validator() {
114        let (vs, _) = make_env();
115        let unknown_signer = Ed25519Signer::generate(ValidatorId(99));
116        let hash = BlockHash([3u8; 32]);
117        let bytes = Vote::signing_bytes(
118            &TEST_CHAIN,
119            EpochNumber(0),
120            ViewNumber(1),
121            &hash,
122            VoteType::Vote,
123        );
124        let vote = Vote {
125            block_hash: hash,
126            view: ViewNumber(1),
127            validator: ValidatorId(99),
128            signature: unknown_signer.sign(&bytes),
129            vote_type: VoteType::Vote,
130            extension: None,
131        };
132        assert!(aggregate_votes(&vs, &[vote]).is_err());
133    }
134}