1use crate::bls12381::primitives::{Error, group::Scalar, variant::Variant};
2#[cfg(not(feature = "std"))]
3use alloc::sync::Arc;
4#[cfg(not(feature = "std"))]
5use alloc::vec::Vec;
6use cfg_if::cfg_if;
7use commonware_codec::{
8 EncodeSize, FixedSize, Mode as CodecMode, RangeCfg, Read, ReadExt, Write, mode,
9};
10use commonware_macros::stability;
11#[stability(ALPHA)]
12use commonware_math::algebra::{FieldNTT, Ring};
13use commonware_math::poly::{Interpolator, Poly};
14use commonware_parallel::Sequential;
15use commonware_utils::{NZU32, Participant, ordered::Set};
16#[stability(ALPHA)]
17use commonware_utils::{TryFromIterator, ordered::BiMap};
18#[cfg(feature = "std")]
19use core::iter;
20use core::num::NonZeroU32;
21#[cfg(feature = "std")]
22use std::sync::{Arc, OnceLock};
23#[cfg(feature = "std")]
24use std::vec::Vec;
25
26#[derive(Copy, Clone, PartialEq, Eq, Debug)]
31#[repr(u8)]
32pub enum Mode {
33 NonZeroCounter = 0,
34
35 #[cfg(not(any(
39 commonware_stability_BETA,
40 commonware_stability_GAMMA,
41 commonware_stability_DELTA,
42 commonware_stability_EPSILON,
43 commonware_stability_RESERVED
44 )))]
45 RootsOfUnity = 1,
46}
47
48impl From<Mode> for CodecMode {
49 fn from(mode: Mode) -> Self {
50 match mode {
51 Mode::NonZeroCounter => mode!(0),
52 #[cfg(not(any(
53 commonware_stability_BETA,
54 commonware_stability_GAMMA,
55 commonware_stability_DELTA,
56 commonware_stability_EPSILON,
57 commonware_stability_RESERVED
58 )))]
59 Mode::RootsOfUnity => mode!(1),
60 }
61 }
62}
63
64impl Mode {
65 pub(crate) fn scalar(self, total: NonZeroU32, i: Participant) -> Option<Scalar> {
69 if i.get() >= total.get() {
70 return None;
71 }
72 match self {
73 Self::NonZeroCounter => {
74 Some(Scalar::from_u64(i.get() as u64 + 1))
76 }
77 #[cfg(not(any(
78 commonware_stability_BETA,
79 commonware_stability_GAMMA,
80 commonware_stability_DELTA,
81 commonware_stability_EPSILON,
82 commonware_stability_RESERVED
83 )))]
84 Self::RootsOfUnity => {
85 let size = (total.get() as u64).next_power_of_two();
88 let lg_size = size.ilog2() as u8;
89 let w = Scalar::root_of_unity(lg_size).expect("domain too large for NTT");
90 Some(w.exp(&[i.get() as u64]))
91 }
92 }
93 }
94
95 #[cfg(feature = "std")]
97 pub(crate) fn all_scalars(self, total: NonZeroU32) -> Vec<Scalar> {
98 match self {
99 Self::NonZeroCounter => (0..total.get())
100 .map(|i| Scalar::from_u64(i as u64 + 1))
101 .collect(),
102 #[cfg(not(any(
103 commonware_stability_BETA,
104 commonware_stability_GAMMA,
105 commonware_stability_DELTA,
106 commonware_stability_EPSILON,
107 commonware_stability_RESERVED
108 )))]
109 Self::RootsOfUnity => {
110 let size = (total.get() as u64).next_power_of_two();
111 let lg_size = size.ilog2() as u8;
112 let w = Scalar::root_of_unity(lg_size).expect("domain too large for NTT");
113 (0..total.get())
114 .scan(Scalar::one(), |state, _| {
115 let val = state.clone();
116 *state *= &w;
117 Some(val)
118 })
119 .collect()
120 }
121 }
122 }
123
124 fn interpolator<I: Clone + Ord>(
135 self,
136 total: NonZeroU32,
137 indices: &Set<I>,
138 to_index: impl Fn(&I) -> Option<Participant>,
139 ) -> Option<Interpolator<I, Scalar>> {
140 match self {
141 Self::NonZeroCounter => {
142 let mut count = 0;
143 let iter = indices
144 .iter()
145 .filter_map(|i| {
146 let scalar = self.scalar(total, to_index(i)?)?;
147 Some((i.clone(), scalar))
148 })
149 .inspect(|_| {
150 count += 1;
151 });
152 let out = Interpolator::new(iter);
153 if count != indices.len() {
155 return None;
156 }
157 Some(out)
158 }
159 #[cfg(not(any(
160 commonware_stability_BETA,
161 commonware_stability_GAMMA,
162 commonware_stability_DELTA,
163 commonware_stability_EPSILON,
164 commonware_stability_RESERVED
165 )))]
166 Self::RootsOfUnity => {
167 let size = (total.get() as u64).next_power_of_two();
170 let ntt_total = NonZeroU32::new(u32::try_from(size).ok()?)?;
171
172 let mut count = 0;
173 let points: Vec<(I, u32)> = indices
174 .iter()
175 .filter_map(|i| {
176 let participant = to_index(i)?;
177 if participant.get() >= total.get() {
178 return None;
179 }
180 count += 1;
181 Some((i.clone(), participant.get()))
182 })
183 .collect();
184
185 if count != indices.len() {
187 return None;
188 }
189
190 let points = BiMap::try_from_iter(points).ok()?;
191 Some(Interpolator::roots_of_unity(ntt_total, points))
192 }
193 }
194 }
195
196 #[cfg(feature = "std")]
204 pub(crate) fn subset_interpolator<I: Clone + Ord>(
205 self,
206 set: &Set<I>,
207 subset: &Set<I>,
208 ) -> Option<Interpolator<I, Scalar>> {
209 let Ok(total) = NonZeroU32::try_from(set.len() as u32) else {
210 return Some(Interpolator::new(iter::empty()));
211 };
212 self.interpolator(total, subset, |i| {
213 set.position(i).map(Participant::from_usize)
214 })
215 }
216}
217
218impl FixedSize for Mode {
219 const SIZE: usize = 1;
220}
221
222impl Write for Mode {
223 fn write(&self, buf: &mut impl bytes::BufMut) {
224 buf.put_u8(CodecMode::from(*self).into());
225 }
226}
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
236pub struct ModeVersion(u8);
237
238impl ModeVersion {
239 pub const fn v0() -> Self {
243 Self(0)
244 }
245
246 #[stability(ALPHA)]
250 pub const fn v1() -> Self {
251 Self(1)
252 }
253
254 pub const fn supports(&self, mode: &Mode) -> bool {
256 match mode {
257 Mode::NonZeroCounter => true,
258 #[cfg(not(any(
259 commonware_stability_BETA,
260 commonware_stability_GAMMA,
261 commonware_stability_DELTA,
262 commonware_stability_EPSILON,
263 commonware_stability_RESERVED
264 )))]
265 Mode::RootsOfUnity => self.0 >= 1,
266 }
267 }
268}
269
270impl Read for Mode {
271 type Cfg = ModeVersion;
272
273 fn read_cfg(
274 buf: &mut impl bytes::Buf,
275 version: &Self::Cfg,
276 ) -> Result<Self, commonware_codec::Error> {
277 let tag: u8 = ReadExt::read(buf)?;
278 let mode = match tag {
279 0 => Self::NonZeroCounter,
280 #[cfg(not(any(
281 commonware_stability_BETA,
282 commonware_stability_GAMMA,
283 commonware_stability_DELTA,
284 commonware_stability_EPSILON,
285 commonware_stability_RESERVED
286 )))]
287 1 => Self::RootsOfUnity,
288 o => return Err(commonware_codec::Error::InvalidEnum(o)),
289 };
290 if !version.supports(&mode) {
291 return Err(commonware_codec::Error::Invalid(
292 "Mode",
293 "unsupported mode for version",
294 ));
295 }
296 Ok(mode)
297 }
298}
299
300#[derive(Clone, Debug)]
304pub struct Sharing<V: Variant> {
305 mode: Mode,
306 total: NonZeroU32,
307 poly: Arc<Poly<V::Public>>,
308 #[cfg(feature = "std")]
309 evals: Arc<Vec<OnceLock<V::Public>>>,
310}
311
312impl<V: Variant> PartialEq for Sharing<V> {
313 fn eq(&self, other: &Self) -> bool {
314 self.mode == other.mode && self.total == other.total && self.poly == other.poly
315 }
316}
317
318impl<V: Variant> Eq for Sharing<V> {}
319
320impl<V: Variant> Sharing<V> {
321 pub(crate) fn new(mode: Mode, total: NonZeroU32, poly: Poly<V::Public>) -> Self {
322 Self {
323 mode,
324 total,
325 poly: Arc::new(poly),
326 #[cfg(feature = "std")]
327 evals: Arc::new(vec![OnceLock::new(); total.get() as usize]),
328 }
329 }
330
331 #[cfg(feature = "std")]
333 pub(crate) const fn mode(&self) -> Mode {
334 self.mode
335 }
336
337 pub(crate) fn scalar(&self, i: Participant) -> Option<Scalar> {
338 self.mode.scalar(self.total, i)
339 }
340
341 #[cfg(feature = "std")]
342 fn all_scalars(&self) -> Vec<Scalar> {
343 self.mode.all_scalars(self.total)
344 }
345
346 pub fn required(&self) -> u32 {
350 self.poly.degree_exact() + 1
353 }
354
355 pub const fn total(&self) -> NonZeroU32 {
357 self.total
358 }
359
360 pub(crate) fn interpolator(
364 &self,
365 indices: &Set<Participant>,
366 ) -> Result<Interpolator<Participant, Scalar>, Error> {
367 self.mode
368 .interpolator(self.total, indices, |&x| Some(x))
369 .ok_or(Error::InvalidIndex)
370 }
371
372 #[cfg(feature = "std")]
380 pub fn precompute_partial_publics(&self) {
381 self.evals
383 .iter()
384 .zip(self.all_scalars())
385 .for_each(|(e, s)| {
386 e.get_or_init(|| self.poly.eval_msm(&s, &Sequential));
387 })
388 }
389
390 pub fn partial_public(&self, i: Participant) -> Result<V::Public, Error> {
395 cfg_if! {
396 if #[cfg(feature = "std")] {
397 self.evals
398 .get(usize::from(i))
399 .map(|e| {
400 *e.get_or_init(|| {
401 self.poly
402 .eval_msm(&self.scalar(i).expect("i < total"), &Sequential)
403 })
404 })
405 .ok_or(Error::InvalidIndex)
406 } else {
407 Ok(self
408 .poly
409 .eval_msm(&self.scalar(i).ok_or(Error::InvalidIndex)?, &Sequential))
410 }
411 }
412 }
413
414 pub fn public(&self) -> &V::Public {
418 self.poly.constant()
419 }
420}
421
422impl<V: Variant> EncodeSize for Sharing<V> {
423 fn encode_size(&self) -> usize {
424 self.mode.encode_size() + self.total.get().encode_size() + self.poly.encode_size()
425 }
426}
427
428impl<V: Variant> Write for Sharing<V> {
429 fn write(&self, buf: &mut impl bytes::BufMut) {
430 self.mode.write(buf);
431 self.total.get().write(buf);
432 self.poly.write(buf);
433 }
434}
435
436impl<V: Variant> Read for Sharing<V> {
437 type Cfg = (NonZeroU32, ModeVersion);
438
439 fn read_cfg(
440 buf: &mut impl bytes::Buf,
441 (max_participants, max_supported_mode): &Self::Cfg,
442 ) -> Result<Self, commonware_codec::Error> {
443 let mode = Read::read_cfg(buf, max_supported_mode)?;
444 let total = {
447 let out: u32 = ReadExt::read(buf)?;
448 if out == 0 || out > max_participants.get() {
449 return Err(commonware_codec::Error::Invalid(
450 "Sharing",
451 "total not in range",
452 ));
453 }
454 NZU32!(out)
456 };
457 let poly = Read::read_cfg(buf, &(RangeCfg::from(NZU32!(1)..=*max_participants), ()))?;
458 Ok(Self::new(mode, total, poly))
459 }
460}
461
462#[cfg(all(test, feature = "std"))]
463mod tests {
464 use super::*;
465 use crate::bls12381::primitives::variant::MinSig;
466 use commonware_invariants::minifuzz;
467 use commonware_utils::{TestRng, ordered::Map};
468
469 #[test]
470 fn test_roots_of_unity_interpolator_large_total_returns_none() {
471 let total = NonZeroU32::new(u32::MAX).expect("u32::MAX is non-zero");
472 let indices = Set::from_iter_dedup([Participant::new(0)]);
473 let interpolator =
474 Mode::RootsOfUnity.interpolator(total, &indices, |participant| Some(*participant));
475 assert!(
476 interpolator.is_none(),
477 "domain > u32::MAX should be rejected instead of panicking"
478 );
479 }
480
481 #[test]
482 fn test_mode_read_rejects_mode_above_max_supported_mode() {
483 let encoded = [1];
484 Mode::read_cfg(&mut &encoded[..], &ModeVersion::v0())
485 .expect_err("roots mode must be rejected when max mode is counter");
486 }
487
488 #[test]
489 fn test_all_scalars_matches_scalar() {
490 minifuzz::test(|u| {
491 let mode = match u.int_in_range(0u8..=1)? {
492 0 => Mode::NonZeroCounter,
493 1 => Mode::RootsOfUnity,
494 _ => unreachable!("range is 0..=1"),
495 };
496 let total = NonZeroU32::new(u.int_in_range(1u32..=512u32)?).expect("range is non-zero");
497 let index = u.int_in_range(0u32..=total.get() - 1)?;
498 let participant = Participant::new(index);
499
500 let scalars = mode.all_scalars(total);
501 assert_eq!(
502 scalars[usize::from(participant)].clone(),
503 mode.scalar(total, participant).expect("index is in range")
504 );
505 Ok(())
506 });
507 }
508
509 #[test]
510 fn test_required_uses_exact_degree() {
511 let polynomial = Poly::<Scalar>::new(TestRng::new(0), 2);
514 let padded_zero = polynomial.clone() - &polynomial;
515 assert_eq!(padded_zero.required().get(), 3);
516
517 let sharing =
520 Sharing::<MinSig>::new(Mode::NonZeroCounter, NZU32!(4), Poly::commit(padded_zero));
521 assert_eq!(sharing.required(), 1);
522 }
523
524 #[test]
525 fn test_subset_interpolation_recovers_constant() {
526 minifuzz::test(|u| {
527 let mode = match u.int_in_range(0u8..=1)? {
528 0 => Mode::NonZeroCounter,
529 1 => Mode::RootsOfUnity,
530 _ => unreachable!("range is 0..=1"),
531 };
532 let total = NonZeroU32::new(u.int_in_range(1u32..=64u32)?).expect("range is non-zero");
533
534 let mut subset_vec = Vec::new();
535 for i in 0..total.get() {
536 if u.arbitrary::<bool>()? {
537 subset_vec.push(Participant::new(i));
538 }
539 }
540 if subset_vec.is_empty() {
541 let i = u.int_in_range(0u32..=total.get() - 1)?;
542 subset_vec.push(Participant::new(i));
543 }
544 let subset = Set::from_iter_dedup(subset_vec);
545
546 let max_degree = u32::try_from(subset.len() - 1).expect("subset len fits in u32");
547 let degree = u.int_in_range(0u32..=max_degree)?;
548 let seed: u64 = u.arbitrary()?;
549 let poly: Poly<Scalar> = Poly::new(TestRng::new(seed), degree);
550
551 let all_shares = Map::from_iter_dedup((0..total.get()).map(|i| {
552 let participant = Participant::new(i);
553 let scalar = mode.scalar(total, participant).expect("in range");
554 let share = poly.eval(&scalar);
555 (participant, share)
556 }));
557
558 let subset_evals = Map::from_iter_dedup(subset.iter().map(|participant| {
559 (
560 *participant,
561 all_shares
562 .get_value(participant)
563 .expect("participant exists")
564 .clone(),
565 )
566 }));
567
568 let interpolator = mode
569 .interpolator(total, &subset, |participant| Some(*participant))
570 .expect("subset indices are valid");
571 let recovered = interpolator
572 .interpolate(&subset_evals, &Sequential)
573 .expect("subset should match interpolator domain");
574
575 assert_eq!(recovered, poly.constant().clone());
576 Ok(())
577 });
578 }
579}
580
581#[cfg(feature = "arbitrary")]
582mod fuzz {
583 use super::*;
584 use arbitrary::Arbitrary;
585 use commonware_utils::{Faults, N3f1, NZU32, TestRng};
586
587 impl<'a> Arbitrary<'a> for Mode {
588 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
589 match u.int_in_range(0u8..=1)? {
590 0 => Ok(Self::NonZeroCounter),
591 1 => Ok(Self::RootsOfUnity),
592 _ => Err(arbitrary::Error::IncorrectFormat),
593 }
594 }
595 }
596
597 impl<'a, V: Variant> Arbitrary<'a> for Sharing<V> {
598 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
599 let total: u32 = u.int_in_range(1..=100)?;
600 let mode: Mode = u.arbitrary()?;
601 let seed: u64 = u.arbitrary()?;
602 let poly = Poly::new(TestRng::new(seed), N3f1::quorum(total) - 1);
603 Ok(Self::new(
604 mode,
605 NZU32!(total),
606 Poly::<V::Public>::commit(poly),
607 ))
608 }
609 }
610}