1use ethnum::U256;
7use solana_keccak_hasher::hashv;
8
9use crate::error::DataUpdateError;
10
11const SELECTION_DOMAIN: [u8; 32] = [
13 0x49, 0x28, 0x48, 0xfe, 0x5e, 0x85, 0xd4, 0xce, 0x22, 0x31, 0xd6, 0x93, 0xa5, 0x8f, 0x08, 0x20,
14 0xa4, 0x05, 0x6e, 0x28, 0x22, 0xfe, 0x5d, 0xca, 0xd7, 0xc7, 0x56, 0xaf, 0xe0, 0x44, 0xb7, 0x0b,
15];
16
17const DERIVE_GROUP_BITMAP_MAX_ROUNDS: u64 = 65_536;
19
20#[inline]
22pub fn bitmap_load(bytes: &[u8; 32]) -> U256 {
23 U256::from_be_bytes(*bytes)
24}
25
26#[inline]
28pub fn bitmap_store(value: U256) -> [u8; 32] {
29 value.to_be_bytes()
30}
31
32#[inline]
34pub fn bitmap_bit_set(bitmap: &[u8; 32], pos: usize) -> bool {
35 debug_assert!(pos < 256);
36 (bitmap_load(bitmap) >> pos) & U256::from(1u8) != U256::ZERO
37}
38
39#[inline]
40pub fn bitmap_set_bit(bitmap: &mut [u8; 32], pos: usize) {
41 debug_assert!(pos < 256);
42 let mut v = bitmap_load(bitmap);
43 v |= U256::from(1u8) << pos;
44 *bitmap = bitmap_store(v);
45}
46
47#[inline]
48pub fn bitmap_clear_bit(bitmap: &mut [u8; 32], pos: usize) {
49 debug_assert!(pos < 256);
50 let mut v = bitmap_load(bitmap);
51 v &= !(U256::from(1u8) << pos);
52 *bitmap = bitmap_store(v);
53}
54
55pub fn bitmap_popcount_evm(bitmap: &[u8; 32]) -> u32 {
56 bitmap_load(bitmap).count_ones()
57}
58
59#[inline]
61pub fn bitmap_is_subset_u256(sub: U256, sup: U256) -> bool {
62 (sub & !sup) == U256::ZERO
63}
64
65pub fn validate_bitmap_upper_bits_clear_u256(
67 bitmap: U256,
68 node_count: u32,
69) -> Result<(), DataUpdateError> {
70 if node_count > 256 {
71 return Err(DataUpdateError::InvalidSignersBitmap);
72 }
73 let mask = if node_count == 256 {
74 U256::MAX
75 } else {
76 (U256::from(1u8) << node_count) - U256::from(1u8)
77 };
78 if (bitmap & !mask) != U256::ZERO {
79 return Err(DataUpdateError::InvalidSignersBitmap);
80 }
81 Ok(())
82}
83
84pub fn for_each_set_bit_u256<F, E>(mut bm: U256, mut f: F) -> Result<U256, E>
86where
87 F: FnMut(usize) -> Result<(), E>,
88{
89 while bm != U256::ZERO {
90 let bit_pos = bm.trailing_zeros() as usize;
91 bm &= bm - U256::from(1u8);
92 f(bit_pos)?;
93 }
94 Ok(bm)
95}
96
97pub fn get_index(bitmap: &[u8; 32], pos: usize) -> Option<usize> {
100 let mut bm = bitmap_load(bitmap);
101 let mut rank = 0usize;
102 while bm != U256::ZERO {
103 let bit_pos = bm.trailing_zeros() as usize;
104 if rank == pos {
105 return Some(bit_pos);
106 }
107 bm &= bm - U256::from(1u8);
108 rank += 1;
109 }
110 None
111}
112
113pub fn for_each_set_bit<F>(bitmap: &[u8; 32], mut f: F)
115where
116 F: FnMut(usize),
117{
118 let mut bm = bitmap_load(bitmap);
119 while bm != U256::ZERO {
120 let bit_pos = bm.trailing_zeros() as usize;
121 f(bit_pos);
122 bm &= bm - U256::from(1u8);
123 }
124}
125
126fn u256_be_from_u64(counter: u64) -> [u8; 32] {
127 let mut out = [0u8; 32];
128 out[24..32].copy_from_slice(&counter.to_be_bytes());
129 out
130}
131
132fn selection_hash_round(seed: &[u8; 32], counter: u64) -> [u8; 32] {
134 let counter_word = u256_be_from_u64(counter);
135 hashv(&[seed.as_ref(), &SELECTION_DOMAIN, &counter_word]).to_bytes()
136}
137
138#[inline]
139fn full_mask_u256(node_count: u32) -> U256 {
140 if node_count == 256 {
141 U256::MAX
142 } else {
143 (U256::from(1u8) << node_count) - U256::from(1u8)
144 }
145}
146
147fn sample_without_replacement(
149 seed: &[u8; 32],
150 node_count: u32,
151 group_size: u32,
152) -> Result<U256, DataUpdateError> {
153 let limit = (u64::from(u32::MAX) / u64::from(node_count)) * u64::from(node_count);
154 let mut bitmap = U256::ZERO;
155 let mut selected = 0u32;
156 let mut counter = 0u64;
157
158 while selected < group_size {
159 if counter >= DERIVE_GROUP_BITMAP_MAX_ROUNDS {
160 return Err(DataUpdateError::GroupBitmapDerivationFailed);
161 }
162
163 let digest = selection_hash_round(seed, counter);
164 counter += 1;
165
166 let mut word = bitmap_load(&digest);
167 for _ in 0..8 {
168 if selected >= group_size {
169 break;
170 }
171
172 let limb = u32::try_from(word >> 224).unwrap_or(u32::MAX);
173 word <<= 32;
174
175 if u64::from(limb) < limit {
176 let pos = (limb % node_count) as usize;
177 let bit = U256::from(1u8) << pos;
178 if (bitmap & bit) == U256::ZERO {
179 bitmap |= bit;
180 selected += 1;
181 }
182 }
183 }
184 }
185
186 Ok(bitmap)
187}
188
189pub fn derive_group_bitmap(
191 seed: &[u8; 32],
192 node_count: u32,
193 group_size: u32,
194) -> Result<[u8; 32], DataUpdateError> {
195 if node_count == 0 {
196 return Err(DataUpdateError::GroupBitmapDerivationFailed);
197 }
198 if node_count > 256 {
199 return Err(DataUpdateError::GroupBitmapDerivationFailed);
200 }
201 if group_size > node_count {
202 return Err(DataUpdateError::GroupBitmapDerivationFailed);
203 }
204 if group_size == 0 {
205 return Ok([0u8; 32]);
206 }
207 if group_size == node_count {
208 return Ok(bitmap_store(full_mask_u256(node_count)));
209 }
210
211 let bitmap = if group_size > node_count / 2 {
212 let excluded = sample_without_replacement(seed, node_count, node_count - group_size)?;
213 full_mask_u256(node_count) ^ excluded
214 } else {
215 sample_without_replacement(seed, node_count, group_size)?
216 };
217
218 Ok(bitmap_store(bitmap))
219}
220
221pub fn bitmap_is_subset(sub: &[u8; 32], sup: &[u8; 32]) -> bool {
222 let sub = bitmap_load(sub);
223 let sup = bitmap_load(sup);
224 (sub & !sup) == U256::ZERO
225}
226
227#[inline]
229pub fn effective_selection_size(
230 signatures_required: u8,
231 redundancy_buffer: u8,
232 node_count: u32,
233) -> u32 {
234 (signatures_required as u32)
235 .saturating_add(redundancy_buffer as u32)
236 .min(node_count)
237}
238
239pub fn validate_bitmap_upper_bits_clear(
240 bitmap: &[u8; 32],
241 node_count: u32,
242) -> Result<(), DataUpdateError> {
243 validate_bitmap_upper_bits_clear_u256(bitmap_load(bitmap), node_count)
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn get_index_orders_signers_by_ascending_bit() {
252 let bm: [u8; 32] = [
254 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
255 0, 0, 0x07,
256 ];
257 assert_eq!(get_index(&bm, 0), Some(0));
258 assert_eq!(get_index(&bm, 1), Some(1));
259 assert_eq!(get_index(&bm, 2), Some(2));
260 assert_eq!(get_index(&bm, 3), None);
261 }
262
263 #[test]
264 fn get_index_skips_gaps() {
265 let mut bm = [0u8; 32];
266 bitmap_set_bit(&mut bm, 5);
267 bitmap_set_bit(&mut bm, 10);
268 assert_eq!(get_index(&bm, 0), Some(5));
269 assert_eq!(get_index(&bm, 1), Some(10));
270 assert_eq!(get_index(&bm, 2), None);
271 }
272
273 #[test]
274 fn validate_bitmap_upper_bits_clear_rejects_high_bits() {
275 let mut bm = [0u8; 32];
276 bitmap_set_bit(&mut bm, 10);
277 assert!(validate_bitmap_upper_bits_clear(&bm, 8).is_err());
278 assert!(validate_bitmap_upper_bits_clear(&bm, 11).is_ok());
279 }
280
281 #[test]
282 fn u256_bit_ops_match_byte_layout() {
283 let mut bm = [0u8; 32];
284 bitmap_set_bit(&mut bm, 0);
285 bitmap_set_bit(&mut bm, 7);
286 bitmap_set_bit(&mut bm, 255);
287 assert!(bitmap_bit_set(&bm, 0));
288 assert!(bitmap_bit_set(&bm, 7));
289 assert!(bitmap_bit_set(&bm, 255));
290 assert!(!bitmap_bit_set(&bm, 1));
291 assert_eq!(bitmap_popcount_evm(&bm), 3);
292 }
293
294 #[test]
295 fn effective_selection_size_caps_at_node_count() {
296 assert_eq!(effective_selection_size(8, 2, 8), 8);
297 assert_eq!(effective_selection_size(5, 2, 8), 7);
298 assert_eq!(effective_selection_size(10, 2, 8), 8);
299 }
300
301 #[test]
302 fn derive_group_bitmap_matches_evm_reference_vectors() {
303 let seed = [0x11u8; 32];
304 let cases: &[(u32, u32, &str)] = &[
305 (
306 8,
307 3,
308 "0000000000000000000000000000000000000000000000000000000000000038",
309 ),
310 (
311 10,
312 7,
313 "00000000000000000000000000000000000000000000000000000000000003d3",
314 ),
315 (
316 16,
317 5,
318 "0000000000000000000000000000000000000000000000000000000000002c30",
319 ),
320 (
321 32,
322 20,
323 "00000000000000000000000000000000000000000000000000000000d7ddb3a1",
324 ),
325 ];
326 for (node_count, group_size, want_hex) in cases {
327 let got = derive_group_bitmap(&seed, *node_count, *group_size).unwrap();
328 let want = hex_to_bytes32(want_hex);
329 assert_eq!(got, want, "n={node_count} g={group_size}");
330 }
331 }
332
333 #[test]
334 fn derive_group_bitmap_complement_path_matches_direct_sample() {
335 let seed = [0x22u8; 32];
336 let node_count = 10u32;
337 let group_size = 7u32;
338 let direct = derive_group_bitmap(&seed, node_count, group_size).unwrap();
339 let excluded = derive_group_bitmap(&seed, node_count, node_count - group_size).unwrap();
340 let mut full = [0u8; 32];
341 for pos in 0..node_count as usize {
342 bitmap_set_bit(&mut full, pos);
343 }
344 let excluded_bm = bitmap_load(&excluded);
345 let complement = bitmap_store(bitmap_load(&full) ^ excluded_bm);
346 assert_eq!(direct, complement);
347 }
348
349 fn hex_to_bytes32(hex: &str) -> [u8; 32] {
350 let hex = hex.strip_prefix("0x").unwrap_or(hex);
351 let mut out = [0u8; 32];
352 for (i, chunk) in hex.as_bytes().chunks(2).enumerate() {
353 let s = std::str::from_utf8(chunk).unwrap();
354 out[i] = u8::from_str_radix(s, 16).unwrap();
355 }
356 out
357 }
358}