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