Skip to main content

commonware_utils/
lib.rs

1//! Leverage common functionality across multiple primitives.
2
3#![doc(
4    html_logo_url = "https://commonware.xyz/imgs/rustdoc_logo.svg",
5    html_favicon_url = "https://commonware.xyz/favicon.ico"
6)]
7#![cfg_attr(not(any(feature = "std", test)), no_std)]
8
9commonware_macros::stability_scope!(ALPHA, cfg(feature = "std") {
10    pub use rng::{test_rng, FuzzRng, TestRng};
11});
12commonware_macros::stability_scope!(BETA {
13    #[cfg(not(feature = "std"))]
14    extern crate alloc;
15
16    #[cfg(not(feature = "std"))]
17    use alloc::{boxed::Box, vec::Vec};
18    use bytes::{BufMut, BytesMut};
19    use core::time::Duration;
20    pub mod faults;
21    pub use faults::{Faults, N3f1, N5f1};
22
23    pub mod sequence;
24    pub use sequence::{Array, Span};
25
26    pub mod hostname;
27    pub use hostname::Hostname;
28
29    pub mod bitmap;
30    pub mod cache;
31    pub mod ordered;
32    pub mod range;
33
34    use bytes::Buf;
35    use commonware_codec::{varint::UInt, EncodeSize, Error as CodecError, Read, ReadExt, Write};
36
37    /// 64-bit golden-ratio-derived odd mixing constant.
38    ///
39    /// Equal to `floor(2^64 / phi)`. Because it is odd, multiplication by it
40    /// is a bijection modulo `2^64`.
41    pub const GOLDEN_RATIO: u64 = 0x9e37_79b9_7f4a_7c15;
42
43    /// Represents a participant/validator index within a consensus committee.
44    ///
45    /// Participant indices are used to identify validators in attestations,
46    /// votes, and certificates. The index corresponds to the position of the
47    /// validator's public key in the ordered participant set.
48    #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
49    #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
50    pub struct Participant(u32);
51
52    impl Participant {
53        /// Creates a new participant from a u32 index.
54        pub const fn new(index: u32) -> Self {
55            Self(index)
56        }
57
58        /// Creates a new participant from a usize index.
59        ///
60        /// # Panics
61        ///
62        /// Panics if `index` exceeds `u32::MAX`.
63        pub fn from_usize(index: usize) -> Self {
64            Self(u32::try_from(index).expect("participant index exceeds u32::MAX"))
65        }
66
67        /// Returns the underlying u32 index.
68        pub const fn get(self) -> u32 {
69            self.0
70        }
71    }
72
73    impl From<Participant> for usize {
74        fn from(p: Participant) -> Self {
75            p.0 as Self
76        }
77    }
78
79    impl core::fmt::Display for Participant {
80        fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
81            write!(f, "{}", self.0)
82        }
83    }
84
85    impl Read for Participant {
86        type Cfg = ();
87
88        fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, CodecError> {
89            let value: u32 = UInt::read(buf)?.into();
90            Ok(Self(value))
91        }
92    }
93
94    impl Write for Participant {
95        fn write(&self, buf: &mut impl bytes::BufMut) {
96            UInt(self.0).write(buf);
97        }
98    }
99
100    impl EncodeSize for Participant {
101        fn encode_size(&self) -> usize {
102            UInt(self.0).encode_size()
103        }
104    }
105
106    /// A type that can be constructed from an iterator, possibly failing.
107    pub trait TryFromIterator<T>: Sized {
108        /// The error type returned when construction fails.
109        type Error;
110
111        /// Attempts to construct `Self` from an iterator.
112        fn try_from_iter<I: IntoIterator<Item = T>>(iter: I) -> Result<Self, Self::Error>;
113    }
114
115    /// Extension trait for iterators that provides fallible collection.
116    pub trait TryCollect: Iterator + Sized {
117        /// Attempts to collect elements into a collection that may fail.
118        fn try_collect<C: TryFromIterator<Self::Item>>(self) -> Result<C, C::Error> {
119            C::try_from_iter(self)
120        }
121    }
122
123    impl<I: Iterator> TryCollect for I {}
124
125    /// Alias for boxed errors that are `Send` and `Sync`.
126    pub type BoxedError = Box<dyn core::error::Error + Send + Sync>;
127
128    /// Computes the union of two byte slices.
129    pub fn union(a: &[u8], b: &[u8]) -> Vec<u8> {
130        let mut union = Vec::with_capacity(a.len() + b.len());
131        union.extend_from_slice(a);
132        union.extend_from_slice(b);
133        union
134    }
135
136    /// Concatenate a namespace and a message, prepended by a varint encoding of the namespace length.
137    ///
138    /// This produces a unique byte sequence (i.e. no collisions) for each `(namespace, msg)` pair.
139    pub fn union_unique(namespace: &[u8], msg: &[u8]) -> Vec<u8> {
140        use commonware_codec::EncodeSize;
141        let len_prefix = namespace.len();
142        let mut buf =
143            BytesMut::with_capacity(len_prefix.encode_size() + namespace.len() + msg.len());
144        len_prefix.write(&mut buf);
145        BufMut::put_slice(&mut buf, namespace);
146        BufMut::put_slice(&mut buf, msg);
147        buf.into()
148    }
149
150    /// Compute the modulo of bytes interpreted as a big-endian integer.
151    ///
152    /// This function is used to select a random entry from an array when the bytes are a random seed.
153    ///
154    /// # Panics
155    ///
156    /// Panics if `n` is zero.
157    pub fn modulo(bytes: &[u8], n: u64) -> u64 {
158        assert_ne!(n, 0, "modulus must be non-zero");
159
160        let n = n as u128;
161        let mut result = 0u128;
162        for &byte in bytes {
163            result = (result << 8) | (byte as u128);
164            result %= n;
165        }
166
167        // Result is either 0 or modulo `n`, so we can safely cast to u64
168        result as u64
169    }
170
171    /// A wrapper around `Duration` that guarantees the duration is non-zero.
172    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
173    pub struct NonZeroDuration(Duration);
174
175    impl NonZeroDuration {
176        /// Creates a `NonZeroDuration` if the given duration is non-zero.
177        pub fn new(duration: Duration) -> Option<Self> {
178            if duration == Duration::ZERO {
179                None
180            } else {
181                Some(Self(duration))
182            }
183        }
184
185        /// Creates a `NonZeroDuration` from the given duration, panicking if it's zero.
186        pub fn new_panic(duration: Duration) -> Self {
187            Self::new(duration).expect("duration must be non-zero")
188        }
189
190        /// Returns the wrapped `Duration`.
191        pub const fn get(self) -> Duration {
192            self.0
193        }
194    }
195
196    impl From<NonZeroDuration> for Duration {
197        fn from(nz_duration: NonZeroDuration) -> Self {
198            nz_duration.0
199        }
200    }
201});
202commonware_macros::stability_scope!(BETA, cfg(feature = "std") {
203    pub mod rng;
204    pub use rng::sys_rng;
205
206    pub mod acknowledgement;
207    pub use acknowledgement::Acknowledgement;
208
209    pub mod net;
210    pub use net::IpAddrExt;
211
212    pub mod time;
213    pub use time::{DurationExt, SystemTimeExt};
214
215    pub mod rational;
216    pub use rational::BigRationalExt;
217
218    mod priority_set;
219    pub use priority_set::PrioritySet;
220
221    pub mod channel;
222    pub mod concurrency;
223    pub mod futures;
224    pub mod sync;
225
226    pub mod thread_local;
227    pub use thread_local::Cached;
228});
229#[cfg(not(any(
230    commonware_stability_GAMMA,
231    commonware_stability_DELTA,
232    commonware_stability_EPSILON,
233    commonware_stability_RESERVED
234)))] // BETA
235pub mod vec;
236
237/// A macro to create a `NonZeroUsize` from a value, panicking if the value is zero.
238/// For literal values, validation occurs at compile time. For expressions, validation
239/// occurs at runtime.
240#[macro_export]
241macro_rules! NZUsize {
242    ($val:literal) => {
243        const { ::core::num::NonZeroUsize::new($val).expect("value must be non-zero") }
244    };
245    ($val:expr) => {
246        // This will panic at runtime if $val is zero.
247        ::core::num::NonZeroUsize::new($val).expect("value must be non-zero")
248    };
249}
250
251/// A macro to create a `NonZeroU8` from a value, panicking if the value is zero.
252/// For literal values, validation occurs at compile time. For expressions, validation
253/// occurs at runtime.
254#[macro_export]
255macro_rules! NZU8 {
256    ($val:literal) => {
257        const { ::core::num::NonZeroU8::new($val).expect("value must be non-zero") }
258    };
259    ($val:expr) => {
260        // This will panic at runtime if $val is zero.
261        ::core::num::NonZeroU8::new($val).expect("value must be non-zero")
262    };
263}
264
265/// A macro to create a `NonZeroU16` from a value, panicking if the value is zero.
266/// For literal values, validation occurs at compile time. For expressions, validation
267/// occurs at runtime.
268#[macro_export]
269macro_rules! NZU16 {
270    ($val:literal) => {
271        const { ::core::num::NonZeroU16::new($val).expect("value must be non-zero") }
272    };
273    ($val:expr) => {
274        // This will panic at runtime if $val is zero.
275        ::core::num::NonZeroU16::new($val).expect("value must be non-zero")
276    };
277}
278
279/// A macro to create a `NonZeroU32` from a value, panicking if the value is zero.
280/// For literal values, validation occurs at compile time. For expressions, validation
281/// occurs at runtime.
282#[macro_export]
283macro_rules! NZU32 {
284    ($val:literal) => {
285        const { ::core::num::NonZeroU32::new($val).expect("value must be non-zero") }
286    };
287    ($val:expr) => {
288        // This will panic at runtime if $val is zero.
289        ::core::num::NonZeroU32::new($val).expect("value must be non-zero")
290    };
291}
292
293/// A macro to create a `NonZeroU64` from a value, panicking if the value is zero.
294/// For literal values, validation occurs at compile time. For expressions, validation
295/// occurs at runtime.
296#[macro_export]
297macro_rules! NZU64 {
298    ($val:literal) => {
299        const { ::core::num::NonZeroU64::new($val).expect("value must be non-zero") }
300    };
301    ($val:expr) => {
302        // This will panic at runtime if $val is zero.
303        ::core::num::NonZeroU64::new($val).expect("value must be non-zero")
304    };
305}
306
307/// Re-export of `commonware_formatting` so that the `fixed_bytes!` macro's
308/// expansion can resolve `hex!` in any caller's namespace.
309#[doc(hidden)]
310pub use ::commonware_formatting as __formatting;
311
312/// Macro for converting sequence of string literals containing hex-encoded data
313/// into a [`crate::sequence::FixedBytes`] type.
314#[cfg(not(any(
315    commonware_stability_GAMMA,
316    commonware_stability_DELTA,
317    commonware_stability_EPSILON,
318    commonware_stability_RESERVED
319)))] // BETA
320#[macro_export]
321macro_rules! fixed_bytes {
322    ($s:tt) => {
323        const { $crate::sequence::FixedBytes::new($crate::__formatting::hex!($s)) }
324    };
325}
326
327/// A macro to create a `NonZeroDuration` from a duration, panicking if the duration is zero.
328#[macro_export]
329macro_rules! NZDuration {
330    ($val:expr) => {
331        // This will panic at runtime if $val is zero.
332        $crate::NonZeroDuration::new_panic($val)
333    };
334}
335
336#[cfg(test)]
337mod tests {
338    use super::*;
339    use crate::TestRng;
340    use commonware_formatting::hex;
341    use num_bigint::BigUint;
342    use rand::RngExt as _;
343
344    #[test]
345    fn test_union() {
346        // Test case 0: empty slices
347        assert_eq!(union(&[], &[]), Vec::<u8>::new());
348
349        // Test case 1: empty and non-empty slices
350        assert_eq!(union(&[], &hex!("0x010203")), hex!("0x010203"));
351
352        // Test case 2: non-empty and non-empty slices
353        assert_eq!(
354            union(&hex!("0x010203"), &hex!("0x040506")),
355            hex!("0x010203040506")
356        );
357    }
358
359    #[test]
360    fn test_union_unique() {
361        let namespace = b"namespace";
362        let msg = b"message";
363
364        let length_encoding = vec![0b0000_1001];
365        let mut expected = Vec::with_capacity(length_encoding.len() + namespace.len() + msg.len());
366        expected.extend_from_slice(&length_encoding);
367        expected.extend_from_slice(namespace);
368        expected.extend_from_slice(msg);
369
370        let result = union_unique(namespace, msg);
371        assert_eq!(result, expected);
372        assert_eq!(result.len(), result.capacity());
373    }
374
375    #[test]
376    fn test_union_unique_zero_length() {
377        let namespace = b"";
378        let msg = b"message";
379
380        let length_encoding = vec![0];
381        let mut expected = Vec::with_capacity(length_encoding.len() + namespace.len() + msg.len());
382        expected.extend_from_slice(&length_encoding);
383        expected.extend_from_slice(msg);
384
385        let result = union_unique(namespace, msg);
386        assert_eq!(result, expected);
387        assert_eq!(result.len(), result.capacity());
388    }
389
390    #[test]
391    fn test_union_unique_long_length() {
392        // Use a namespace of over length 127.
393        let namespace = &b"n".repeat(256);
394        let msg = b"message";
395
396        let length_encoding = vec![0b1000_0000, 0b0000_0010];
397        let mut expected = Vec::with_capacity(length_encoding.len() + namespace.len() + msg.len());
398        expected.extend_from_slice(&length_encoding);
399        expected.extend_from_slice(namespace);
400        expected.extend_from_slice(msg);
401
402        let result = union_unique(namespace, msg);
403        assert_eq!(result, expected);
404        assert_eq!(result.len(), result.capacity());
405    }
406
407    #[test]
408    fn test_modulo() {
409        // Test case 0: empty bytes
410        assert_eq!(modulo(&[], 1), 0);
411
412        // Test case 1: single byte
413        assert_eq!(modulo(&hex!("0x01"), 1), 0);
414
415        // Test case 2: multiple bytes
416        assert_eq!(modulo(&hex!("0x010203"), 10), 1);
417
418        // Test case 3: check equivalence with BigUint
419        for i in 0..100 {
420            let mut rng = TestRng::new(i);
421            let bytes: [u8; 32] = rng.random();
422
423            // 1-byte modulus
424            let n = 11u64;
425            let big_modulo = BigUint::from_bytes_be(&bytes) % n;
426            let utils_modulo = modulo(&bytes, n);
427            assert_eq!(big_modulo, BigUint::from(utils_modulo));
428
429            // 2-byte modulus
430            let n = 11_111u64;
431            let big_modulo = BigUint::from_bytes_be(&bytes) % n;
432            let utils_modulo = modulo(&bytes, n);
433            assert_eq!(big_modulo, BigUint::from(utils_modulo));
434
435            // 8-byte modulus
436            let n = 0xDFFFFFFFFFFFFFFD;
437            let big_modulo = BigUint::from_bytes_be(&bytes) % n;
438            let utils_modulo = modulo(&bytes, n);
439            assert_eq!(big_modulo, BigUint::from(utils_modulo));
440        }
441    }
442
443    #[test]
444    #[should_panic]
445    fn test_modulo_zero_panics() {
446        modulo(&hex!("0x010203"), 0);
447    }
448
449    #[test]
450    fn test_non_zero_macros_compile_time() {
451        // Literal values are validated at compile time.
452        // NZU32!(0) would be a compile error.
453        assert_eq!(NZUsize!(1).get(), 1);
454        assert_eq!(NZU8!(2).get(), 2);
455        assert_eq!(NZU16!(3).get(), 3);
456        assert_eq!(NZU32!(4).get(), 4);
457        assert_eq!(NZU64!(5).get(), 5);
458
459        // Literals can be used in const contexts
460        const _: core::num::NonZeroUsize = NZUsize!(1);
461        const _: core::num::NonZeroU8 = NZU8!(2);
462        const _: core::num::NonZeroU16 = NZU16!(3);
463        const _: core::num::NonZeroU32 = NZU32!(4);
464        const _: core::num::NonZeroU64 = NZU64!(5);
465    }
466
467    #[test]
468    fn test_non_zero_macros_runtime() {
469        // Runtime variables are validated at runtime
470        let one_usize: usize = 1;
471        let two_u8: u8 = 2;
472        let three_u16: u16 = 3;
473        let four_u32: u32 = 4;
474        let five_u64: u64 = 5;
475
476        assert_eq!(NZUsize!(one_usize).get(), 1);
477        assert_eq!(NZU8!(two_u8).get(), 2);
478        assert_eq!(NZU16!(three_u16).get(), 3);
479        assert_eq!(NZU32!(four_u32).get(), 4);
480        assert_eq!(NZU64!(five_u64).get(), 5);
481
482        // Zero runtime values panic
483        let zero_usize: usize = 0;
484        let zero_u8: u8 = 0;
485        let zero_u16: u16 = 0;
486        let zero_u32: u32 = 0;
487        let zero_u64: u64 = 0;
488
489        assert!(std::panic::catch_unwind(|| NZUsize!(zero_usize)).is_err());
490        assert!(std::panic::catch_unwind(|| NZU8!(zero_u8)).is_err());
491        assert!(std::panic::catch_unwind(|| NZU16!(zero_u16)).is_err());
492        assert!(std::panic::catch_unwind(|| NZU32!(zero_u32)).is_err());
493        assert!(std::panic::catch_unwind(|| NZU64!(zero_u64)).is_err());
494
495        // NZDuration is runtime-only since Duration has no literal syntax
496        assert!(std::panic::catch_unwind(|| NZDuration!(Duration::ZERO)).is_err());
497        assert_eq!(
498            NZDuration!(Duration::from_secs(1)).get(),
499            Duration::from_secs(1)
500        );
501    }
502
503    #[test]
504    fn test_non_zero_duration() {
505        // Test case 0: zero duration
506        assert!(NonZeroDuration::new(Duration::ZERO).is_none());
507
508        // Test case 1: non-zero duration
509        let duration = Duration::from_millis(100);
510        let nz_duration = NonZeroDuration::new(duration).unwrap();
511        assert_eq!(nz_duration.get(), duration);
512        assert_eq!(Duration::from(nz_duration), duration);
513
514        // Test case 2: panic on zero
515        assert!(std::panic::catch_unwind(|| NonZeroDuration::new_panic(Duration::ZERO)).is_err());
516
517        // Test case 3: ordering
518        let d1 = NonZeroDuration::new(Duration::from_millis(100)).unwrap();
519        let d2 = NonZeroDuration::new(Duration::from_millis(200)).unwrap();
520        assert!(d1 < d2);
521    }
522
523    #[test]
524    fn test_participant_constructors() {
525        assert_eq!(Participant::new(0).get(), 0);
526        assert_eq!(Participant::new(42).get(), 42);
527        assert_eq!(Participant::from_usize(0).get(), 0);
528        assert_eq!(Participant::from_usize(42).get(), 42);
529        assert_eq!(Participant::from_usize(u32::MAX as usize).get(), u32::MAX);
530    }
531
532    #[test]
533    #[should_panic(expected = "participant index exceeds u32::MAX")]
534    fn test_participant_from_usize_overflow() {
535        Participant::from_usize((u32::MAX as usize) + 1);
536    }
537
538    #[test]
539    fn test_participant_display() {
540        assert_eq!(format!("{}", Participant::new(0)), "0");
541        assert_eq!(format!("{}", Participant::new(42)), "42");
542        assert_eq!(format!("{}", Participant::new(1000)), "1000");
543    }
544
545    #[test]
546    fn test_participant_ordering() {
547        assert!(Participant::new(0) < Participant::new(1));
548        assert!(Participant::new(5) < Participant::new(10));
549        assert!(Participant::new(10) > Participant::new(5));
550        assert_eq!(Participant::new(42), Participant::new(42));
551    }
552
553    #[test]
554    fn test_participant_encode_decode() {
555        use commonware_codec::{DecodeExt, Encode};
556
557        let cases = vec![0u32, 1, 127, 128, 255, 256, u32::MAX];
558        for value in cases {
559            let participant = Participant::new(value);
560            let encoded = participant.encode();
561            assert_eq!(encoded.len(), participant.encode_size());
562            let decoded = Participant::decode(encoded).unwrap();
563            assert_eq!(participant, decoded);
564        }
565    }
566
567    #[cfg(feature = "arbitrary")]
568    mod conformance {
569        use super::*;
570        use commonware_codec::conformance::CodecConformance;
571
572        commonware_conformance::conformance_tests! {
573            CodecConformance<Participant>,
574        }
575    }
576}