commonware_consensus/simplex/signing_scheme/
utils.rs

1//! Utilities for simplex signing schemes.
2
3use bytes::{Buf, BufMut};
4use commonware_codec::{EncodeSize, Error, Read, Write};
5use commonware_utils::bitmap::BitMap;
6
7/// Bitmap wrapper that tracks which validators signed a certificate.
8///
9/// Internally, it stores bits in 1-byte chunks for compact encoding.
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub struct Signers {
12    bitmap: BitMap<1>,
13}
14
15impl Signers {
16    /// Builds [`Signers`] from an iterator of signer indices.
17    ///
18    /// # Panics
19    ///
20    /// Panics if the sequence contains indices larger than the size of the participant set
21    /// or duplicates.
22    pub fn from(participants: usize, signers: impl IntoIterator<Item = u32>) -> Self {
23        let mut bitmap = BitMap::zeroes(participants as u64);
24        for signer in signers.into_iter() {
25            assert!(
26                !bitmap.get(signer as u64),
27                "duplicate signer index: {signer}",
28            );
29            // We opt to not assert order here because some signing schemes allow
30            // for commutative aggregation of signatures (and sorting is unnecessary
31            // overhead).
32
33            bitmap.set(signer as u64, true);
34        }
35
36        Self { bitmap }
37    }
38
39    /// Returns the length of the bitmap (the size of the participant set).
40    #[allow(clippy::len_without_is_empty)]
41    pub fn len(&self) -> usize {
42        self.bitmap.len() as usize
43    }
44
45    /// Returns how many validators are marked as signers.
46    pub fn count(&self) -> usize {
47        self.bitmap.count_ones() as usize
48    }
49
50    /// Iterates over signer indices in ascending order.
51    pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
52        self.bitmap
53            .iter()
54            .enumerate()
55            .filter_map(|(index, bit)| bit.then_some(index as u32))
56    }
57}
58
59impl Write for Signers {
60    fn write(&self, writer: &mut impl BufMut) {
61        self.bitmap.write(writer);
62    }
63}
64
65impl EncodeSize for Signers {
66    fn encode_size(&self) -> usize {
67        self.bitmap.encode_size()
68    }
69}
70
71impl Read for Signers {
72    type Cfg = usize;
73
74    fn read_cfg(reader: &mut impl Buf, max_participants: &usize) -> Result<Self, Error> {
75        let bitmap = BitMap::read_cfg(reader, &(*max_participants as u64))?;
76        // The participant count is treated as an upper bound for decoding flexibility, e.g. one
77        // might use `Scheme::certificate_codec_config_unbounded` for decoding certificates from
78        // local storage.
79        //
80        // Exact length validation **must** be enforced at verification time by the signing schemes
81        // against the actual participant set size.
82        Ok(Self { bitmap })
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89    use commonware_codec::{Decode, Encode};
90
91    #[test]
92    fn test_from_signers() {
93        let signers = Signers::from(6, [0, 3, 5]);
94        let collected: Vec<_> = signers.iter().collect();
95        assert_eq!(collected, vec![0, 3, 5]);
96        assert_eq!(signers.count(), 3);
97    }
98
99    #[test]
100    #[should_panic(expected = "bit 4 out of bounds (len: 4)")]
101    fn test_from_out_of_bounds() {
102        Signers::from(4, [0, 4]);
103    }
104
105    #[test]
106    #[should_panic(expected = "duplicate signer index: 0")]
107    fn test_from_duplicate() {
108        Signers::from(4, [0, 0, 1]);
109    }
110
111    #[test]
112    fn test_from_not_increasing() {
113        Signers::from(4, [2, 1]);
114    }
115
116    #[test]
117    fn test_codec_round_trip() {
118        let signers = Signers::from(9, [1, 6]);
119        let encoded = signers.encode();
120        let decoded = Signers::decode_cfg(encoded, &9).unwrap();
121        assert_eq!(decoded, signers);
122    }
123
124    #[test]
125    fn test_decode_respects_participant_limit() {
126        let signers = Signers::from(8, [0, 3, 7]);
127        let encoded = signers.encode();
128        // More participants than expected should fail.
129        assert!(Signers::decode_cfg(encoded.clone(), &2).is_err());
130        // Exact participant bound succeeds.
131        assert!(Signers::decode_cfg(encoded.clone(), &8).is_ok());
132        // Less participants than expected succeeds (upper bound).
133        assert!(Signers::decode_cfg(encoded.clone(), &10).is_ok());
134    }
135}