Skip to main content

solana_signer_store/
lib.rs

1//! Provides a space-efficient encoding scheme for one or two boolean vectors,
2//! primarily used to compactly encode the set of signers in an aggregate signature.
3//!
4//! This module implements compression algorithms to encode boolean vectors
5//! into a single byte vector (`Vec<u8>`). It currently supports two distinct
6//! schemes based on the number of input vectors.
7//!
8//! # Encoding Schemes
9//!
10//! ## Base2 Encoding (Single Vector)
11//! When a single boolean vector is provided, it is encoded directly.
12//! The format is:
13//! 1.  **Version Byte (1 byte)**: `Version::Base2` as a `u8`.
14//! 2.  **Length Prefix (2 bytes)**: A `u16` in little-endian format storing the
15//!     original number of bits in the input vector (not length of the final
16//!     vector).
17//! 3.  **Data Payload**: The raw byte data of the boolean vector.
18//!
19//! ## Base3 Encoding (Two Vectors)
20//! When two boolean vectors of the same length are provided, they are compressed
21//! together. This scheme assumes that for any given index, the bits in both
22//! vectors will not both be `1`.
23//!
24//! The pairs of booleans are mapped to a single ternary (base-3) digit:
25//! - `(false, false)` -> `0`
26//! - `(true, false)`  -> `1`
27//! - `(false, true)`  -> `2`
28//!
29//! The combination `(true, true)` is considered invalid. These ternary digits are
30//! packed five at a time into a single `u8`, since `3^5 < 2^8`.
31//!
32//! The format is:
33//! 1.  **Version Byte (1 byte)**: `Version::Base3` as a `u8`.
34//! 2.  **Length Prefix (2 bytes)**: A `u16` in little-endian format storing the
35//!     original number of bits (i.e., the length of the input vectors; not the
36//!     length of the final vector).
37//! 3.  **Data Payload**: A sequence of bytes containing the packed base-3 digits.
38
39use {
40    bitvec::prelude::*,
41    num_derive::{FromPrimitive, ToPrimitive},
42    num_traits::FromPrimitive,
43};
44
45const VERSION_BYTE_LEN: usize = 1;
46const LENGTH_PREFIX_LEN: usize = 2;
47const HEADER_LEN: usize = VERSION_BYTE_LEN + LENGTH_PREFIX_LEN;
48
49/// Represents the encoding version, used as the first byte in the output.
50#[derive(Debug, PartialEq, Eq, FromPrimitive, ToPrimitive)]
51#[repr(u8)]
52pub enum Version {
53    Base2 = 0,
54    Base3 = 1,
55}
56
57/// An error that can occur during the encoding process.
58#[derive(Debug, PartialEq, Eq)]
59pub enum EncodeError {
60    /// In Base3 encoding, the provided bit-vectors have unmatching lengths.
61    MismatchedLengths,
62    /// In Base3 encoding, the invalid combination `(true, true)` was found.
63    InvalidBitCombination,
64    /// The length of the input vectors exceeds `u16::MAX` (65,535).
65    LengthExceedsLimit,
66    /// An arithmetic operation resulted in an overflow.
67    ArithmeticOverflow,
68}
69
70// Each u8 can hold 5 base-3 symbols (3^5 = 243).
71const BASE3_SYMBOLS_PER_BYTE: usize = 5;
72
73/// Encodes a single boolean vector using Base2 encoding.
74///
75/// The output `Vec<u8>` is prefixed with the `Version::Base2` byte.
76pub fn encode_base2(bit_vec: &BitVec<u8, Lsb0>) -> Result<Vec<u8>, EncodeError> {
77    let num_bits = bit_vec.len();
78    if num_bits > u16::MAX as usize {
79        return Err(EncodeError::LengthExceedsLimit);
80    }
81
82    let raw_slice = bit_vec.as_raw_slice();
83    let capacity = HEADER_LEN
84        .checked_add(raw_slice.len())
85        .ok_or(EncodeError::ArithmeticOverflow)?;
86    let mut result = Vec::with_capacity(capacity);
87    result.push(Version::Base2 as u8);
88    result.extend_from_slice(&(num_bits as u16).to_le_bytes());
89    result.extend_from_slice(raw_slice);
90
91    Ok(result)
92}
93
94/// Encodes two boolean vectors using Base3 encoding.
95///
96/// This function assumes that for any given index, `bit_vec_base` and
97/// `bit_vec_fallback` will not both have a bit set to `1`.
98/// The output `Vec<u8>` is prefixed with the `Version::Base3` byte.
99pub fn encode_base3(
100    bit_vec_base: &BitVec<u8, Lsb0>,
101    bit_vec_fallback: &BitVec<u8, Lsb0>,
102) -> Result<Vec<u8>, EncodeError> {
103    if bit_vec_base.len() != bit_vec_fallback.len() {
104        return Err(EncodeError::MismatchedLengths);
105    }
106    let num_bits = bit_vec_base.len();
107    if num_bits > u16::MAX as usize {
108        return Err(EncodeError::LengthExceedsLimit);
109    }
110
111    let base_bytes = bit_vec_base.as_raw_slice();
112    let fallback_bytes = bit_vec_fallback.as_raw_slice();
113
114    let num_chunks = num_bits.div_ceil(BASE3_SYMBOLS_PER_BYTE);
115    let capacity = HEADER_LEN
116        .checked_add(num_chunks)
117        .ok_or(EncodeError::ArithmeticOverflow)?;
118    let mut result = Vec::with_capacity(capacity);
119
120    result.push(Version::Base3 as u8);
121    result.extend_from_slice(&(num_bits as u16).to_le_bytes());
122
123    for chunk_index in 0..num_chunks {
124        let mut block_num: u8 = 0;
125        let start_bit = chunk_index
126            .checked_mul(BASE3_SYMBOLS_PER_BYTE)
127            .ok_or(EncodeError::ArithmeticOverflow)?;
128        let end_bit = start_bit
129            .checked_add(BASE3_SYMBOLS_PER_BYTE)
130            .ok_or(EncodeError::ArithmeticOverflow)?
131            .min(num_bits);
132
133        // Process bits in reverse order to simplify packing
134        for i in (start_bit..end_bit).rev() {
135            let byte_idx = i / 8;
136            let bit_idx = i % 8;
137
138            let base_bit = (base_bytes.get(byte_idx).unwrap_or(&0) >> bit_idx) & 1 == 1;
139            let fallback_bit = (fallback_bytes.get(byte_idx).unwrap_or(&0) >> bit_idx) & 1 == 1;
140
141            let chunk_num = match (base_bit, fallback_bit) {
142                (false, false) => 0u8,
143                (true, false) => 1u8,
144                (false, true) => 2u8,
145                (true, true) => return Err(EncodeError::InvalidBitCombination),
146            };
147
148            block_num = block_num
149                .checked_mul(3)
150                .and_then(|n| n.checked_add(chunk_num))
151                .ok_or(EncodeError::ArithmeticOverflow)?;
152        }
153        result.push(block_num);
154    }
155    Ok(result)
156}
157
158/// Represents the result of a decoding operation.
159#[derive(Debug, PartialEq, Eq)]
160pub enum Decoded {
161    /// A single vector from Base2 decoding.
162    Base2(BitVec<u8, Lsb0>),
163    /// Two vectors from Base3 decoding.
164    Base3(BitVec<u8, Lsb0>, BitVec<u8, Lsb0>),
165}
166
167/// An error that can occur during the decoding process.
168#[derive(Debug, PartialEq, Eq)]
169pub enum DecodeError {
170    /// The input slice is too short to be valid.
171    InputTooShort,
172    /// The encoding version byte is unsupported.
173    UnsupportedEncoding,
174    /// The data payload is not of the expected length.
175    CorruptDataPayload,
176    /// An arithmetic operation resulted in an overflow.
177    ArithmeticOverflow,
178}
179
180/// Decodes an encoded byte slice into one or two boolean vectors.
181///
182/// It reads the first byte to determine the encoding scheme and then decodes
183/// the rest of the data accordingly.
184pub fn decode(bytes: &[u8], max_len: usize) -> Result<Decoded, DecodeError> {
185    if bytes.len() < 3 {
186        // Must have at least version (1) + length (2)
187        return Err(DecodeError::InputTooShort);
188    }
189
190    let version_byte = bytes[0];
191    let version = Version::from_u8(version_byte).ok_or(DecodeError::UnsupportedEncoding)?;
192
193    let mut len_arr = [0u8; 2];
194    len_arr.copy_from_slice(&bytes[1..3]);
195    let total_bits = u16::from_le_bytes(len_arr) as usize;
196
197    if total_bits > max_len {
198        return Err(DecodeError::CorruptDataPayload);
199    }
200
201    let data_bytes = &bytes[3..];
202
203    match version {
204        Version::Base2 => decode_impl_base2(data_bytes, total_bits),
205        Version::Base3 => decode_impl_base3(data_bytes, total_bits),
206    }
207}
208
209// Internal function to handle Base2 decoding logic
210fn decode_impl_base2(data_bytes: &[u8], total_bits: usize) -> Result<Decoded, DecodeError> {
211    let expected_byte_len = total_bits.div_ceil(8);
212    if data_bytes.len() != expected_byte_len {
213        return Err(DecodeError::CorruptDataPayload);
214    }
215
216    let mut bit_vec = BitVec::from_slice(data_bytes);
217    bit_vec.truncate(total_bits);
218
219    Ok(Decoded::Base2(bit_vec))
220}
221
222// Internal function to handle Base3 decoding logic
223fn decode_impl_base3(data_bytes: &[u8], total_bits: usize) -> Result<Decoded, DecodeError> {
224    let expected_num_chunks = total_bits.div_ceil(BASE3_SYMBOLS_PER_BYTE);
225
226    if data_bytes.len() != expected_num_chunks {
227        return Err(DecodeError::CorruptDataPayload);
228    }
229
230    let decoded_byte_len = total_bits.div_ceil(8);
231    let mut base_bytes = vec![0u8; decoded_byte_len];
232    let mut fallback_bytes = vec![0u8; decoded_byte_len];
233
234    for (chunk_index, &block_byte) in data_bytes.iter().enumerate() {
235        let mut block_num = block_byte;
236        let start_bit = chunk_index
237            .checked_mul(BASE3_SYMBOLS_PER_BYTE)
238            .ok_or(DecodeError::ArithmeticOverflow)?;
239        let end_bit = start_bit
240            .checked_add(BASE3_SYMBOLS_PER_BYTE)
241            .ok_or(DecodeError::ArithmeticOverflow)?
242            .min(total_bits);
243
244        for bit_index in start_bit..end_bit {
245            let remainder = block_num % 3;
246            block_num /= 3;
247
248            let byte_idx = bit_index / 8;
249            let bit_idx = bit_index % 8;
250
251            let (base_bit, fallback_bit) = match remainder {
252                0 => (false, false),
253                1 => (true, false),
254                2 => (false, true),
255                _ => unreachable!(), // Modulo 3 can't be > 2
256            };
257
258            if base_bit {
259                base_bytes[byte_idx] |= 1 << bit_idx;
260            }
261            if fallback_bit {
262                fallback_bytes[byte_idx] |= 1 << bit_idx;
263            }
264        }
265    }
266
267    let mut base_vec = BitVec::from_vec(base_bytes);
268    base_vec.truncate(total_bits);
269    let mut fallback_vec = BitVec::from_vec(fallback_bytes);
270    fallback_vec.truncate(total_bits);
271
272    Ok(Decoded::Base3(base_vec, fallback_vec))
273}
274
275#[cfg(test)]
276mod tests {
277    use super::*;
278
279    fn create_base3_test_data(len: usize) -> (BitVec<u8, Lsb0>, BitVec<u8, Lsb0>) {
280        let mut base = BitVec::with_capacity(len);
281        let mut fallback = BitVec::with_capacity(len);
282        for i in 0..len {
283            match i % 3 {
284                0 => {
285                    // (false, false) -> 0
286                    base.push(false);
287                    fallback.push(false);
288                }
289                1 => {
290                    // (true, false) -> 1
291                    base.push(true);
292                    fallback.push(false);
293                }
294                _ => {
295                    // (false, true) -> 2
296                    base.push(false);
297                    fallback.push(true);
298                }
299            }
300        }
301        (base, fallback)
302    }
303
304    #[test]
305    fn test_base2_round_trip() {
306        let original = bitvec![u8, Lsb0; 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1];
307        let original_len = original.len();
308        let encoded = encode_base2(&original).unwrap();
309
310        // Check header
311        assert_eq!(encoded[0], Version::Base2 as u8); // Version byte
312        assert_eq!(
313            u16::from_le_bytes(encoded[1..3].try_into().unwrap()),
314            original_len as u16
315        );
316
317        let decoded = decode(&encoded, original_len).unwrap();
318        if let Decoded::Base2(decoded_vec) = decoded {
319            assert_eq!(original, decoded_vec);
320        } else {
321            panic!("Decoded into the wrong type");
322        }
323    }
324
325    #[test]
326    fn test_base2_empty() {
327        let original = BitVec::<u8, Lsb0>::new();
328        let encoded = encode_base2(&original).unwrap();
329        assert_eq!(encoded, vec![Version::Base2 as u8, 0, 0]);
330        let decoded = decode(&encoded, 0).unwrap();
331        assert_eq!(decoded, Decoded::Base2(original));
332    }
333
334    #[test]
335    fn test_base3_round_trip() {
336        let (base, fallback) = create_base3_test_data(23); // Not a multiple of 5
337        let original_len = base.len();
338        let encoded = encode_base3(&base, &fallback).unwrap();
339
340        // Check header
341        assert_eq!(encoded[0], Version::Base3 as u8); // Version byte
342        assert_eq!(
343            u16::from_le_bytes(encoded[1..3].try_into().unwrap()),
344            original_len as u16
345        );
346
347        let decoded = decode(&encoded, original_len).unwrap();
348        if let Decoded::Base3(decoded_base, decoded_fallback) = decoded {
349            assert_eq!(base, decoded_base);
350            assert_eq!(fallback, decoded_fallback);
351        } else {
352            panic!("Decoded into the wrong type");
353        }
354    }
355
356    #[test]
357    fn test_base3_exact_bytes() {
358        let (base, fallback) = create_base3_test_data(10); // 2 full bytes
359        let encoded = encode_base3(&base, &fallback).unwrap();
360        let decoded = decode(&encoded, 10).unwrap();
361        assert_eq!(decoded, Decoded::Base3(base, fallback));
362    }
363
364    #[test]
365    fn test_base3_empty() {
366        let (base, fallback) = create_base3_test_data(0);
367        let encoded = encode_base3(&base, &fallback).unwrap();
368        assert_eq!(encoded, vec![Version::Base3 as u8, 0, 0]);
369        let decoded = decode(&encoded, 0).unwrap();
370        assert_eq!(decoded, Decoded::Base3(base, fallback));
371    }
372
373    #[test]
374    fn test_encode_base3_invalid_combination() {
375        let base = bitvec![u8, Lsb0; 0, 1];
376        let fallback = bitvec![u8, Lsb0; 0, 1];
377        let result = encode_base3(&base, &fallback);
378        assert_eq!(result, Err(EncodeError::InvalidBitCombination));
379    }
380
381    #[test]
382    fn test_encode_length_limit() {
383        let long_vec = BitVec::repeat(false, (u16::MAX as usize) + 1);
384        let result = encode_base2(&long_vec);
385        assert_eq!(result, Err(EncodeError::LengthExceedsLimit));
386    }
387
388    #[test]
389    fn test_decode_unsupported_encoding() {
390        let bytes = vec![2, 0, 0, 1, 2, 3]; // Invalid version byte '2'
391        let result = decode(&bytes, 10);
392        assert_eq!(result, Err(DecodeError::UnsupportedEncoding));
393    }
394
395    #[test]
396    fn test_decode_input_too_short() {
397        let bytes = vec![1, 0]; // Only 2 bytes, needs at least 3
398        let result = decode(&bytes, 10);
399        assert_eq!(result, Err(DecodeError::InputTooShort));
400    }
401
402    #[test]
403    fn test_decode_max_len_exceeded() {
404        let (base, fallback) = create_base3_test_data(20);
405        let encoded = encode_base3(&base, &fallback).unwrap();
406        // Try to decode with a max_len that is too small
407        let result = decode(&encoded, 19);
408        assert_eq!(result, Err(DecodeError::CorruptDataPayload));
409    }
410
411    #[test]
412    fn test_decode_corrupt_payload() {
413        let (base, fallback) = create_base3_test_data(10);
414        let mut encoded = encode_base3(&base, &fallback).unwrap();
415        encoded.pop(); // Corrupt the payload by removing a byte
416        let result = decode(&encoded, 10);
417        assert_eq!(result, Err(DecodeError::CorruptDataPayload));
418    }
419}